<?php

namespace Drupal\acquia_contenthub_subscriber\Form;

use Drupal\acquia_contenthub\AcquiaContentHubEntityTrackerInterface;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub\Libs\ServiceQueue\ServiceQueueHandler;
use Drupal\acquia_contenthub\Libs\Traits\ResponseCheckerTrait;
use Drupal\acquia_contenthub\Settings\ConnectionDetailsInterface;
use Drupal\acquia_contenthub_subscriber\ContentHubImportQueue;
use Drupal\acquia_contenthub_subscriber\ContentHubImportQueueByFilter;
use Drupal\acquia_contenthub_subscriber\Libs\PullSyndication\ServiceQueueBatchHandler;
use Drupal\acquia_contenthub_subscriber\Libs\UserSyndicationSettingsInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * The form for content hub import queues.
 *
 * @package Drupal\acquia_contenthub\Form
 */
class ContentHubImportQueueForm extends ConfigFormBase {

  use ResponseCheckerTrait;

  public const ROUTE = 'acquia_contenthub_subscriber.import_queue';

  /**
   * The Import Queue Service.
   *
   * @var \Drupal\acquia_contenthub_subscriber\ContentHubImportQueue
   */
  protected ContentHubImportQueue $importQueue;

  /**
   * Content Hub import queue by filter service.
   *
   * @var \Drupal\acquia_contenthub_subscriber\ContentHubImportQueueByFilter
   */
  protected ContentHubImportQueueByFilter $importByFilter;

  /**
   * The Subscriber Tracker Service.
   *
   * @var \Drupal\acquia_contenthub\AcquiaContentHubEntityTrackerInterface
   */
  protected AcquiaContentHubEntityTrackerInterface $tracker;

  /**
   * Client factory.
   *
   * @var \Drupal\acquia_contenthub\Client\ClientFactory
   */
  protected ClientFactory $clientFactory;

  /**
   * The settings operator for user syndication.
   *
   * @var \Drupal\acquia_contenthub_subscriber\Libs\UserSyndicationSettingsInterface
   */
  protected UserSyndicationSettingsInterface $userSyndicationSettings;

  /**
   * The Acquia Content Hub connection details service.
   *
   * @var \Drupal\acquia_contenthub\Settings\ConnectionDetailsInterface
   */
  protected ConnectionDetailsInterface $achConnectionDetails;

  /**
   * The service queue handler service.
   *
   * @var \Drupal\acquia_contenthub\Libs\ServiceQueue\ServiceQueueHandler
   */
  protected ServiceQueueHandler $serviceQueueHandler;

  /**
   * The service queue batch handler service.
   *
   * @var \Drupal\acquia_contenthub_subscriber\Libs\PullSyndication\ServiceQueueBatchHandler
   */
  protected ServiceQueueBatchHandler $serviceQueueBatchHandler;

  /**
   * ContentHubImportQueueForm constructor.
   *
   * @param \Drupal\acquia_contenthub_subscriber\ContentHubImportQueue $import_queue
   *   The Import Queue Service.
   * @param \Drupal\acquia_contenthub\Client\ClientFactory $client_factory
   *   Acquia Content Hub Client factory.
   * @param \Drupal\acquia_contenthub_subscriber\ContentHubImportQueueByFilter $import_by_filter
   *   Content Hub import queue by filter service.
   * @param \Drupal\acquia_contenthub\AcquiaContentHubEntityTrackerInterface $tracker
   *   Acquia Content Hub Subscriber Tracker.
   * @param \Drupal\acquia_contenthub_subscriber\Libs\UserSyndicationSettingsInterface $user_settings
   *   The user syndication settings operator.
   * @param \Drupal\acquia_contenthub\Settings\ConnectionDetailsInterface $ach_connection_details
   *   The Acquia Content Hub connection details service.
   * @param \Drupal\acquia_contenthub\Libs\ServiceQueue\ServiceQueueHandler $service_queue_handler
   *   The service queue handler service.
   * @param \Drupal\acquia_contenthub_subscriber\Libs\PullSyndication\ServiceQueueBatchHandler $service_queue_batch_handler
   *   The service queue batch handler service.
   */
  public function __construct(ContentHubImportQueue $import_queue, ClientFactory $client_factory, ContentHubImportQueueByFilter $import_by_filter, AcquiaContentHubEntityTrackerInterface $tracker, UserSyndicationSettingsInterface $user_settings, ConnectionDetailsInterface $ach_connection_details, ServiceQueueHandler $service_queue_handler, ServiceQueueBatchHandler $service_queue_batch_handler) {
    $this->importQueue = $import_queue;
    $this->clientFactory = $client_factory;
    $this->importByFilter = $import_by_filter;
    $this->tracker = $tracker;
    $this->userSyndicationSettings = $user_settings;
    $this->achConnectionDetails = $ach_connection_details;
    $this->serviceQueueHandler = $service_queue_handler;
    $this->serviceQueueBatchHandler = $service_queue_batch_handler;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('acquia_contenthub_subscriber.acquia_contenthub_import_queue'),
      $container->get('acquia_contenthub.client.factory'),
      $container->get('acquia_contenthub_subscriber.acquia_contenthub_import_queue_by_filter'),
      $container->get('acquia_contenthub_subscriber.tracker'),
      $container->get('acquia_contenthub_subscriber.user_syndication.settings'),
      $container->get('acquia_contenthub.connection_details_config'),
      $container->get('acquia_contenthub.service_queue_handler'),
      $container->get('acquia_contenthub_subscriber.service_queue_batch_handler')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'acquia_contenthub_import_queue_settings';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return ['acquia_contenthub_subscriber.import_settings'];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['description'] = [
      '#markup' => $this->t('Instruct the content hub module to manage content syndication with a queue.'),
    ];

    $this->extendFormWithImportControls($form, $form_state);

    $form['run_import_queue'] = [
      '#type' => 'details',
      '#title' => $this->t('Run the import queue'),
      '#description' => $this->t('<strong>For development & testing use only!</strong><br /> Running the import queue from the UI can cause php timeouts for large datasets.
                         A cronjob to run the queue should be used instead.'),
      '#open' => TRUE,
    ];

    $form['run_import_queue']['actions'] = [
      '#type' => 'action',
      '#weight' => 24,
    ];

    $queue_count = $this->importQueue->getQueueCount();

    $form['run_import_queue']['local_queue_list'] = [
      '#type' => 'item',
      '#title' => $this->t('Number of items in the local queue'),
      '#description' => $this->t('%num @items', [
        '%num' => $queue_count,
        '@items' => $queue_count == 1 ? 'item' : 'items',
      ]),
    ];

    if ($this->achConnectionDetails->getSyndicationMode() === ConnectionDetailsInterface::SYNDICATION_MODE_PULL) {
      $this->addServiceQueueCountDisplay($form);
    }

    $form['run_import_queue']['actions']['run'] = [
      '#type' => 'submit',
      '#name' => 'run_import_queue',
      '#value' => $this->t('Import Items'),
      '#op' => 'run',
    ];

    if ($queue_count > 0) {
      $form['run_import_queue']['purge'] = [
        '#type' => 'container',
        '#weight' => 25,
      ];

      $form['run_import_queue']['purge']['details'] = [
        '#type' => 'item',
        '#title' => $this->t('Purge existing queues'),
        '#description' => $this->t('In case there are stale / stuck items in the queue, press Purge button to clear the Import Queue.'),
      ];
      $form['run_import_queue']['purge']['action'] = [
        '#type' => 'submit',
        '#value' => $this->t('Purge'),
        '#name' => 'purge_import_queue',
      ];
    }

    $title = $this->t('Enqueue from filters');
    $form['queue_from_filters'] = [
      '#type' => 'details',
      '#title' => $title,
      '#description' => $this->t('Queue entities for import based on your custom filters'),
    ];

    $form['queue_from_filters']['actions'] = [
      '#type' => 'actions',
    ];

    $form['queue_from_filters']['actions']['import'] = [
      '#type' => 'submit',
      '#name' => 'queue_from_filters',
      '#value' => $title,
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();

    switch ($trigger['#name']) {
      case 'queue_from_filters':
        $this->handleQueueFromFilters();
        break;

      case 'run_import_queue':
        $this->handleRunImportQueue();
        break;

      case 'purge_import_queue':
        $this->handlePurgeImportQueue();
        break;

      case 'import_control_settings':
        $this->saveImportControlSettings($form_state);
        break;
    }
  }

  /**
   * Handles the queue from filters action.
   */
  protected function handleQueueFromFilters(): void {
    $messenger = $this->messenger();
    $filter_uuids = $this->getFilterUuids();

    if (!$filter_uuids) {
      $messenger->addMessage('No filters found!', 'warning');
      return;
    }

    $this->createAndProcessFilterQueueItems($filter_uuids);
    $messenger->addMessage('Entities got queued for import.', 'status');
  }

  /**
   * Handles the run import queue action.
   */
  protected function handleRunImportQueue(): void {
    $queue_count = $this->importQueue->getQueueCount();
    $messenger = $this->messenger();

    if (!empty($queue_count)) {
      $this->importQueue->processQueueItems();
    }

    if ($this->achConnectionDetails->getSyndicationMode() === ConnectionDetailsInterface::SYNDICATION_MODE_PULL) {
      $this->handlePullSyndicationQueue($messenger);
      return;
    }

    if (empty($queue_count)) {
      $messenger->addMessage('You cannot run the import queue because it is empty.', 'warning');
    }
  }

  /**
   * Handles pull syndication queue processing.
   *
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger service.
   */
  protected function handlePullSyndicationQueue($messenger): void {
    $client = $this->clientFactory->getClient();
    // @todo fetch limit and visibility timeout from config.
    $queue_items = [];

    do {
      $response = $client->receiveQueueItems(ServiceQueueHandler::LIMIT, ServiceQueueHandler::VISIBILITY_TIMEOUT);
      if (!$this->isResponseSuccessful($response)) {
        $this->logger('acquia_contenthub_subscriber')
          ->warning('There was an error communicating with Content Hub while fetching queue items. Error: {error}', [
            'error' => $response['error']['message'] ?? 'Unknown',
          ]);
        $messenger->addMessage('There was an error communicating with Content Hub while fetching queue items. Please check the logs.', 'error');
        return;
      }
      if (!empty($response['data'])) {
        $queue_items = array_merge($queue_items, $response['data']);
      }
    } while (count($response['data']) === ServiceQueueHandler::LIMIT);

    if (empty($queue_items)) {
      $messenger->addMessage('No entities found in Content Hub Service queue.', 'warning');
      return;
    }

    $this->serviceQueueBatchHandler->processQueueItemsInBatch($queue_items);
  }

  /**
   * Handles the purge import queue action.
   */
  protected function handlePurgeImportQueue(): void {
    $this->importQueue->purgeQueues();
    $this->tracker->delete('status', AcquiaContentHubEntityTrackerInterface::QUEUED);
    $this->messenger()->addMessage($this->t('Successfully purged Content Hub import queue.'));
  }

  /**
   * Return the cloud filters UUIDs.
   *
   * @return array
   *   Array contains UUIDs of cloud filters.
   *
   * @throws \Exception
   */
  protected function getFilterUuids(): array {
    $client = $this->clientFactory->getClient();

    $settings = $client->getSettings();
    $webhook_uuid = $settings->getWebhook('uuid');

    if (!$webhook_uuid) {
      return [];
    }

    $filters = $client->listFiltersForWebhook($webhook_uuid);

    return $filters['data'] ?? [];
  }

  /**
   * Creates queue items from passed filter uuids and starts the processing.
   *
   * @param array $filter_uuids
   *   The list of filter uuids to process.
   */
  protected function createAndProcessFilterQueueItems(array $filter_uuids): void {
    $queue = $this->importByFilter->getQueue();

    foreach ($filter_uuids as $filter_uuid) {
      $data = new \stdClass();
      $data->filter_uuid = $filter_uuid;
      $queue->createItem($data);
    }

    $this->importByFilter->processQueueItems();
  }

  /**
   * Gets the service queue count from Content Hub.
   *
   * @return int
   *   The number of items in the service queue.
   */
  protected function getServiceQueueCount(): int {
    try {
      $client = $this->clientFactory->getClient();
      $response = $client->getQueuedItems();

      if (!$this->isResponseSuccessful($response)) {
        $this->logger('acquia_contenthub_subscriber')
          ->warning('There was an error communicating with Content Hub while fetching service queue count. Error: {error}', [
            'error' => $response['error']['message'] ?? 'Unknown',
          ]);
        return 0;
      }

      return (int) ($response['total'] ?? 0);
    }
    catch (\Exception $exception) {
      $this->logger('acquia_contenthub_subscriber')
        ->error('Exception occurred while fetching service queue count: @error', [
          '@error' => $exception->getMessage(),
        ]);
      return 0;
    }
  }

  /**
   * Adds service queue count display to the form.
   *
   * @param array $form
   *   The form array to modify.
   */
  protected function addServiceQueueCountDisplay(array &$form): void {
    $service_queue_count = $this->getServiceQueueCount();

    $form['run_import_queue']['service_queue_list'] = [
      '#type' => 'item',
      '#title' => $this->t('Number of items in Content Hub service queue (Pull Syndication)'),
      '#description' => $this->t('%num @items', [
        '%num' => $service_queue_count,
        '@items' => $service_queue_count === 1 ? 'item' : 'items',
      ]),
    ];
  }

  /**
   * Adds form elements to the form related to import controls.
   *
   * @param array $form
   *   The form to extend.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form's state.
   */
  protected function extendFormWithImportControls(array &$form, FormStateInterface $form_state): void {
    $import_settings = $this->config('acquia_contenthub_subscriber.import_settings');
    $form['import_control'] = [
      '#type' => 'details',
      '#open' => 'true',
      '#title' => $this->t('Import control'),
      '#description' => $this->t('Control import behaviour by configuring the below settings.'),
      'disable_duplicate_path_aliases' => [
        '#type' => 'checkbox',
        '#title' => $this->t('Prevent duplicate path aliases'),
        '#default_value' => $import_settings->get('disable_duplicate_path_aliases'),
        '#description' => $this->t('Prevents duplicate path aliases from being imported.'),
      ],
      'disable_user_syndication' => [
        '#type' => 'checkbox',
        '#title' => $this->t('Disable <strong>user</strong> syndication'),
        '#default_value' => $this->userSyndicationSettings->isUserSyndicationDisabled(),
        '#description' => $this->t('Prevents user data from being overwritten e.g. name. Does not include user roles.'),
      ],
      'proxy_user' => [
        '#type' => 'entity_autocomplete',
        '#title' => $this->t('Choose a proxy user'),
        '#description' => $this->t('You can select a user which will serve as the author for all incoming entities. Leave it blank to keep the original author.'),
        '#target_type' => 'user',
        '#selection_handler' => 'default',
        '#default_value' => $this->userSyndicationSettings->getProxyUser(),
        '#states' => [
          'visible' => [
            'input[name="disable_user_syndication"]' => ['checked' => TRUE],
          ],
        ],
      ],
      'disable_user_role_syndication' => [
        '#type' => 'checkbox',
        '#title' => $this->t('Disable <strong>user role</strong> syndication'),
        '#default_value' => $this->userSyndicationSettings->isUserRoleSyndicationDisabled(),
        '#description' => $this->t('Prevents user roles i.e. permissions, from being overwritten. Does not include user data.'),
      ],
      'save' => [
        '#type' => 'submit',
        '#value' => $this->t('Save Import Settings'),
        '#name' => 'import_control_settings',
      ],
    ];
  }

  /**
   * Persists import control settings.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current form's state.
   */
  protected function saveImportControlSettings(FormStateInterface $form_state): void {
    $this->userSyndicationSettings->toggleUserSyndication(
      $form_state->getValue('disable_user_syndication') ?? FALSE,
    );
    $this->userSyndicationSettings->toggleUserRoleSyndication(
      $form_state->getValue('disable_user_role_syndication') ?? FALSE,
    );
    $proxy_user = $form_state->getValue('proxy_user') ?? NULL;
    if (!is_null($proxy_user)) {
      $this->userSyndicationSettings->setProxyUser($proxy_user);
    }
    else {
      $this->userSyndicationSettings->resetProxyUser();
    }
    $this->userSyndicationSettings->save();
    $import_settings = $this->config('acquia_contenthub_subscriber.import_settings');
    $import_settings->set('disable_duplicate_path_aliases', $form_state->getValue('disable_duplicate_path_aliases') ?? FALSE);
    $import_settings->save();
    $this->messenger()->addStatus('Import control settings have been saved!');
  }

}
