<?php

namespace Drupal\acquia_contenthub_subscriber\Plugin\QueueWorker;

use Acquia\ContentHubClient\Syndication\SyndicationStatus;
use Drupal\acquia_contenthub\AcquiaContentHubEvents;
use Drupal\acquia_contenthub\Client\CdfMetricsManager;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub\Event\Queue\QueueItemProcessFinishedEvent;
use Drupal\acquia_contenthub\Libs\InterestList\InterestListStorageInterface;
use Drupal\acquia_contenthub\Libs\Logging\ContentHubLoggerInterface;
use Drupal\acquia_contenthub\Libs\Traits\ResponseCheckerTrait;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drupal\acquia_contenthub_subscriber\CdfImporter;
use Drupal\acquia_contenthub_subscriber\EntityImportService;
use Drupal\acquia_contenthub_subscriber\SubscriberTracker;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Queue\QueueWorkerBase;
use Drupal\Core\Queue\SuspendQueueException;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Queue worker for importing entities.
 *
 * @QueueWorker(
 *   id = "acquia_contenthub_subscriber_import",
 *   title = "Queue Worker to import entities from contenthub."
 * )
 */
class ContentHubImportQueueWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface {

  use ResponseCheckerTrait;

  /**
   * The role of the site.
   */
  protected const SITE_ROLE = 'subscriber';

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $dispatcher;

  /**
   * The CDF importer object.
   *
   * @var \Drupal\acquia_contenthub_subscriber\CdfImporter
   */
  protected $importer;

  /**
   * The Content Hub Client.
   *
   * @var \Acquia\ContentHubClient\ContentHubClient
   */
  protected $client;

  /**
   * The Subscriber Tracker.
   *
   * @var \Drupal\acquia_contenthub_subscriber\SubscriberTracker
   */
  protected $tracker;

  /**
   * CH configurations.
   *
   * @var \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface
   */
  protected ContentHubConfigurationInterface $achConfigurations;

  /**
   * Content Hub logger.
   *
   * @var \Drupal\acquia_contenthub\Libs\Logging\ContentHubLoggerInterface
   */
  protected $chLogger;

  /**
   * Cdf Metrics Manager.
   *
   * @var \Drupal\acquia_contenthub\Client\CdfMetricsManager
   */
  protected $cdfMetricsManager;

  /**
   * The interest list storage.
   *
   * @var \Drupal\acquia_contenthub\Libs\InterestList\InterestListStorageInterface
   */
  protected InterestListStorageInterface $interestListStorage;

  /**
   * Entity Import Service.
   *
   * @var \Drupal\acquia_contenthub_subscriber\EntityImportService
   */
  protected EntityImportService $entityImportService;

  /**
   * ContentHubExportQueueWorker constructor.
   *
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
   *   Dispatcher.
   * @param \Drupal\acquia_contenthub_subscriber\CdfImporter $cdf_importer
   *   Cdf Importer.
   * @param \Drupal\acquia_contenthub\Client\ClientFactory $client_factory
   *   The client factory.
   * @param \Drupal\acquia_contenthub_subscriber\SubscriberTracker $tracker
   *   The Subscriber Tracker.
   * @param \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ach_configurations
   *   CH configurations.
   * @param \Drupal\acquia_contenthub\Libs\Logging\ContentHubLoggerInterface $ch_logger
   *   Content Hub logger service.
   * @param \Drupal\acquia_contenthub\Client\CdfMetricsManager $cdf_metrics_manager
   *   Cdf metrics manager.
   * @param array $configuration
   *   The plugin configuration.
   * @param string $plugin_id
   *   The plugin id.
   * @param mixed $plugin_definition
   *   The plugin definition.
   * @param \Drupal\acquia_contenthub\Libs\InterestList\InterestListStorageInterface $interest_storage
   *   The interest list storage.
   * @param \Drupal\acquia_contenthub_subscriber\EntityImportService $entity_import_service
   *   Entity Import Service.
   *
   * @throws \Exception
   */
  public function __construct(EventDispatcherInterface $dispatcher, CdfImporter $cdf_importer, ClientFactory $client_factory, SubscriberTracker $tracker, ContentHubConfigurationInterface $ach_configurations, ContentHubLoggerInterface $ch_logger, CdfMetricsManager $cdf_metrics_manager, array $configuration, $plugin_id, $plugin_definition, InterestListStorageInterface $interest_storage, EntityImportService $entity_import_service) {
    $this->importer = $cdf_importer;
    if (!empty($this->importer->getUpdateDbStatus())) {
      throw new \Exception("Site has pending database updates. Apply these updates before importing content.");
    }
    $this->dispatcher = $dispatcher;
    $this->client = $client_factory->getClient();
    $this->tracker = $tracker;
    $this->achConfigurations = $ach_configurations;
    $this->chLogger = $ch_logger;
    $this->cdfMetricsManager = $cdf_metrics_manager;
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->interestListStorage = $interest_storage;
    $this->entityImportService = $entity_import_service;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $container->get('event_dispatcher'),
      $container->get('acquia_contenthub_subscriber.cdf_importer'),
      $container->get('acquia_contenthub.client.factory'),
      $container->get('acquia_contenthub_subscriber.tracker'),
      $container->get('acquia_contenthub.configuration'),
      $container->get('acquia_contenthub_subscriber.ch_logger'),
      $container->get('acquia_contenthub.cdf_metrics_manager'),
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('acquia_contenthub.interest_list_storage'),
      $container->get('acquia_contenthub_subscriber.entity_import')
    );
  }

  /**
   * Processes acquia_contenthub_subscriber_import queue items.
   *
   * @param mixed $data
   *   The data in the queue.
   *
   * @throws \Exception
   */
  public function processItem($data): void {
    $process_items = explode(', ', $data->uuids);
    $result = $this->doProcess($process_items, $data->filter_uuid ?? NULL);
    $event = new QueueItemProcessFinishedEvent($this->getPluginId(), $process_items, $result);
    $this->dispatcher->dispatch($event, AcquiaContentHubEvents::QUEUE_ITEM_PROCESS_FINISHED);
  }

  /**
   * Executes the actual processing of items.
   *
   * @param array $process_items
   *   The uuids to process.
   * @param string|null $filter_uuid
   *   The filter uuid of the queue item if there's one.
   *
   * @return bool
   *   True if the processing was successful.
   *
   * @throws \Drupal\acquia_contenthub_subscriber\Exception\ContentHubImportException
   */
  protected function doProcess(array $process_items, ?string $filter_uuid): bool {
    if (!$this->client) {
      $this->chLogger->getChannel()->error('Acquia Content Hub client cannot be initialized.');
      throw new SuspendQueueException('Acquia Content Hub client cannot be initialized.');
    }

    $settings = $this->client->getSettings();
    $webhook = $settings->getWebhook('uuid');
    if (!$webhook) {
      return FALSE;
    }

    $interests = $this->getInterestList($webhook, $process_items);
    $interest_response = $this->client->getResponse();
    if (!$this->isSuccessful($interest_response)) {
      $msg = sprintf('Error during fetching interest lists: %s', $interest_response->getBody());
      if ($this->isMaintenanceError($interest_response)) {
        $msg = 'Service is under maintenance, suspending import queue';
      }
      throw new SuspendQueueException($msg);
    }

    if (is_null($interests)) {
      return FALSE;
    }

    $uuids = $this->filterDeletedItems($process_items, array_keys($interests));
    $this->logMissingUuids($uuids, $process_items);
    if (!$uuids) {
      return FALSE;
    }

    $stack = $this->entityImportService->importEntities($uuids, $webhook);
    if (!$stack) {
      return FALSE;
    }

    if (!$this->shouldSendUpdate()) {
      return TRUE;
    }
    $uuids = $this->interestListStorage->filterDisabledEntities($webhook, $uuids, static::SITE_ROLE);
    $this->entityImportService->updateInterestList($webhook, $uuids, SyndicationStatus::IMPORT_SUCCESSFUL);

    $this->entityImportService->addDependenciesToInterestList(
      $webhook,
      $this->interestListStorage->filterDisabledEntities($webhook, array_keys($stack->getDependencies()), static::SITE_ROLE),
      $filter_uuid
    );

    return TRUE;
  }

  /**
   * Returns the interest list related to webhook.
   *
   * @param string $webhook_uuid
   *   Webhook uuid.
   * @param array $process_items
   *   Array of uuids to be processed.
   *
   * @return array
   *   List of entity uuids.
   */
  protected function getInterestList(string $webhook_uuid, array $process_items): ?array {
    try {
      return $this->interestListStorage->getInterestList($webhook_uuid, static::SITE_ROLE, ['uuids' => $process_items]);
    }
    catch (\Exception $exception) {
      $this->chLogger->getChannel()->error(
        sprintf(
          'Following error occurred while we were trying to get the interest list: %s',
          $exception->getMessage()
        )
      );
    }
    return NULL;
  }

  /**
   * Filters potentially deleted items.
   *
   * @param array $process_items
   *   The entities being processed.
   * @param array $interests
   *   The list of entity uuids.
   *
   * @return array
   *   Filtered list of entity uuids.
   */
  protected function filterDeletedItems(array $process_items, array $interests): array {
    $uuids = array_intersect($process_items, $interests);
    if (!$uuids) {
      $this->chLogger->getChannel()
        ->info('There are no matching entities in the queues and the site interest list.');
    }
    return $uuids;
  }

  /**
   * Logs the uuids no longer on the interest list for this webhook.
   *
   * @param array $uuids
   *   Entity uuids.
   * @param array $process_items
   *   The entities being processed.
   */
  protected function logMissingUuids(array $uuids, array $process_items): void {
    if (count($uuids) !== count($process_items)) {
      $missing_uuids = array_diff($process_items, $uuids);
      $this->chLogger->getChannel()->info(
          sprintf(
            'Skipped importing the following missing entities: %s. This occurs when entities are deleted at the Publisher before importing.',
            implode(', ', $missing_uuids))
        );
    }
  }

  /**
   * Returns send_contenthub_updates config value.
   *
   * @return bool
   *   Setting of the configuration.
   */
  protected function shouldSendUpdate(): bool {
    return $this->achConfigurations->getContentHubConfig()->shouldSendContentHubUpdates();
  }

}
