<?php

namespace Drupal\acquia_contenthub_publisher;

use Acquia\ContentHubClient\ContentHubClient;
use Acquia\ContentHubClient\Syndication\SyndicationStatus;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub\Exception\ContentHubClientException;
use Drupal\acquia_contenthub\Libs\InterestList\InterestListTrait;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drupal\acquia_contenthub_publisher\Event\ContentHubEntityEligibilityEvent;
use Drupal\acquia_contenthub_publisher\Libs\ExportSettingsInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Queue\QueueFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Enqueues candidate entities for publishing.
 */
class ContentHubEntityEnqueuer {

  use InterestListTrait;

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

  /**
   * CH export settings.
   *
   * @var \Drupal\acquia_contenthub_publisher\Libs\ExportSettingsInterface
   */
  protected ExportSettingsInterface $exportSettings;

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

  /**
   * Logger Channel.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

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

  /**
   * The publisher exporting queue.
   *
   * @var \Drupal\Core\Queue\QueueInterface
   */
  protected $queue;

  /**
   * The publisher tracker.
   *
   * @var \Drupal\acquia_contenthub_publisher\PublisherTracker
   */
  protected $publisherTracker;

  /**
   * ContentHubEntityEnqueuer constructor.
   *
   * @param \Drupal\acquia_contenthub\Client\ClientFactory $client_factory
   *   The client factory.
   * @param \Psr\Log\LoggerInterface $logger_channel
   *   The logger channel.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
   *   The event dispatcher.
   * @param \Drupal\Core\Queue\QueueFactory $queue_factory
   *   The queue factory.
   * @param \Drupal\acquia_contenthub_publisher\PublisherTracker $publisher_tracker
   *   The Publisher Tracker.
   * @param \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ach_configurations
   *   Acquia contenthub configurations.
   * @param \Drupal\acquia_contenthub_publisher\Libs\ExportSettingsInterface $export_settings
   *   Acquia contenthub export settings.
   *
   * @throws \ReflectionException
   */
  public function __construct(ClientFactory $client_factory, LoggerInterface $logger_channel, EventDispatcherInterface $dispatcher, QueueFactory $queue_factory, PublisherTracker $publisher_tracker, ContentHubConfigurationInterface $ach_configurations, ExportSettingsInterface $export_settings) {
    $this->clientFactory = $client_factory;
    $this->logger = $logger_channel;
    $this->dispatcher = $dispatcher;
    $this->queue = $queue_factory->get('acquia_contenthub_publish_export');
    $this->publisherTracker = $publisher_tracker;
    $this->achConfigurations = $ach_configurations;
    $this->exportSettings = $export_settings;
  }

  /**
   * Enqueues candidate entities for publishing.
   *
   * @param string $op
   *   Entity operation.
   * @param \Drupal\Core\Entity\EntityInterface ...$entities
   *   The entity to enqueue to ContentHub.
   *
   * @throws \Exception
   */
  public function enqueueEntities(string $op, EntityInterface ...$entities): void {
    if (!$this->achConfigurations->isConfigurationSet()) {
      return;
    }

    $interest_list_entities = [];
    $ready_to_queue_entities_data = [];
    $entities_data_to_queue = [];
    foreach ($entities as $entity) {
      $entity_type_id = $entity->getEntityTypeId();
      $uuid = $entity->uuid();
      $this->logger->info(
        'Attempting to add entity with (UUID: {uuid}, Entity type: {entity_type}) to the export queue after operation: {op}.',
        [
          'uuid' => $uuid,
          'entity_type' => $entity_type_id,
          'op' => $op,
        ]
      );

      $event = $this->dispatchChEntityEligibilityEvent($entity, $op);
      if (!$event->getEligibility()) {
        $this->publisherTracker->updateEntityLabel($entity);
        $reason = $event->getReason();
        $this->logger->info('Entity with (UUID: {uuid}, Entity type: {entity_type}) not eligible to be added to the export queue. Reason: {reason}', [
          'uuid' => $uuid,
          'entity_type' => $entity_type_id,
          'reason' => $reason,
        ]);
        continue;
      }
      if ($this->exportSettings->isManualExportModeEnabled()) {
        $ready_to_queue_entities_data[$entity->uuid()] = [
          'entity' => $entity,
          'hash' => '',
          'queue_id' => '',
        ];
        $this->logger->info(
          'Entity with (UUID: {uuid}, Entity type: {entity_type}) is ready to queue.',
          [
            'uuid' => $uuid,
            'entity_type' => $entity_type_id,
          ]
        );
        continue;
      }

      $this->logger->info(
        'Attempting to add Entity with (UUID: {uuid}, Entity type: {entity_type}) to the export queue and to the tracking table.',
        [
          'uuid' => $entity->uuid(),
          'entity_type' => $entity->getEntityTypeId(),
        ]
      );
      $entities_data_to_queue[$entity->uuid()] = [
        'entity' => $entity,
        'hash' => '',
        'queue_id' => $this->addItemsToExportQueue($entity, $event),
      ];
      $interest_list_entities[$uuid] = "$entity_type_id : $uuid";
    }

    if (!empty($ready_to_queue_entities_data)) {
      $this->publisherTracker->readyToQueueMultiple($ready_to_queue_entities_data);
    }
    if (!empty($entities_data_to_queue)) {
      $this->publisherTracker->queueMultiple($entities_data_to_queue);
    }

    if (!empty($interest_list_entities)) {
      try {
        $this->sendUpdatesToInterestList($interest_list_entities);
      }
      catch (ContentHubClientException $e) {
        $this->logger->error("Enqueuing: Could not add the following entities to the site's interest list: {entities}",
          ['entities' => implode(', ', $interest_list_entities)]
        );
      }
    }
  }

  /**
   * Adds entities queued for export to interest list.
   *
   * @param array $entities_to_log
   *   Entities to log.
   *   Format uuid => entity_type_id : uuid.
   *   Keys will be used for sending updates to interest list.
   *   Values will be used for logging.
   *
   * @throws \Drupal\acquia_contenthub\Exception\ContentHubClientException
   */
  public function sendUpdatesToInterestList(array $entities_to_log): void {
    $send_update = $this->achConfigurations->getContentHubConfig()->shouldSendContentHubUpdates();
    if (!$send_update) {
      return;
    }
    $uuids = array_keys($entities_to_log);
    $log_entities = array_values($entities_to_log);
    $interest_list = $this->buildInterestList($uuids, SyndicationStatus::QUEUED_TO_EXPORT);
    $client = $this->clientFactory->getClient();
    if (!$client instanceof ContentHubClient) {
      $msg = 'Error trying to connect to the Content Hub. Make sure this site is registered to Content hub.';
      $this->logger->error($msg);
      throw new ContentHubClientException($msg);
    }
    $webhook = $client->getSettings()->getWebhook('uuid');
    try {
      $client->addEntitiesToInterestListBySiteRole($webhook, 'PUBLISHER', $interest_list);
      $this->logger
        ->info('Entities ({entities}) have been added to the interest list with status "{syndication_status}" for webhook: {webhook}.',
          [
            'entities' => implode(', ', $log_entities),
            'syndication_status' => SyndicationStatus::QUEUED_TO_EXPORT,
            'webhook' => $webhook,
          ]
        );
    }
    catch (\Exception $e) {
      $this->logger
        ->error('Error adding entities (entities) to the interest list with status "{syndication_status}" for webhook: {webhook}. Error message: {exception}.',
          [
            'entities' => implode(', ', $log_entities),
            'syndication_status' => SyndicationStatus::QUEUED_TO_EXPORT,
            'webhook' => $webhook,
            'exception' => $e->getMessage(),
          ]
        );
    }
  }

  /**
   * Adds items to publisher export queue.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to add to publisher export queue.
   * @param \Drupal\acquia_contenthub_publisher\Event\ContentHubEntityEligibilityEvent $event
   *   The ContentHubEntityEligibilityEvent.
   *
   * @return int|null
   *   Queue id.
   */
  public function addItemsToExportQueue(EntityInterface $entity, ContentHubEntityEligibilityEvent $event): ?int {
    $item = new \stdClass();
    $item->type = $entity->getEntityTypeId();
    $item->uuid = $entity->uuid();

    if ($event->getCalculateDependencies() === FALSE) {
      $item->calculate_dependencies = FALSE;
    }

    return $this->queue->createItem($item);
  }

  /**
   * Dispatches event.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to enqueue to ContentHub.
   * @param string $op
   *   Entity operation.
   *
   * @return \Drupal\acquia_contenthub_publisher\Event\ContentHubEntityEligibilityEvent
   *   ContentHubEntityEligibilityEvent instance.
   */
  public function dispatchChEntityEligibilityEvent(EntityInterface $entity, string $op): ContentHubEntityEligibilityEvent {
    $event = new ContentHubEntityEligibilityEvent($entity, $op);
    $this->dispatcher->dispatch($event, ContentHubPublisherEvents::ENQUEUE_CANDIDATE_ENTITY);

    return $event;
  }

}
