<?php

namespace Drupal\acquia_contenthub_subscriber\Libs\PullSyndication;

use Acquia\ContentHubClient\Syndication\SyndicationStatus;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub\Libs\Common\EnsureContentHubClientTrait;
use Drupal\acquia_contenthub\Libs\InterestList\InterestListStorageInterface;
use Drupal\acquia_contenthub\Libs\Logging\ContentHubLoggerInterface;
use Drupal\acquia_contenthub\Libs\ServiceQueue\QueueItem;
use Drupal\acquia_contenthub\Libs\ServiceQueue\QueueItemActionInterface;
use Drupal\acquia_contenthub\Libs\ServiceQueue\ServiceQueueEntityActions;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drupal\acquia_contenthub_subscriber\EntityImportService;
use Drupal\acquia_contenthub_subscriber\SubscriberTracker;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\depcalc\DependencyStack;

/**
 * Action for upserting (create/update) entities.
 *
 * Handles both entity creation and entity update operations
 * during pull syndication.
 *
 * @internal
 * @package Drupal\acquia_contenthub_subscriber\Libs\PullSyndication
 */
class EntityUpsertAction implements QueueItemActionInterface {

  use DependencySerializationTrait;
  use EnsureContentHubClientTrait;

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

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

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

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

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

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

  /**
   * Constructs an EntityUpsertAction object.
   *
   * @param \Drupal\acquia_contenthub_subscriber\SubscriberTracker $tracker
   *   The subscription tracker.
   * @param \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ch_configuration
   *   CH configuration.
   * @param \Drupal\acquia_contenthub\Libs\InterestList\InterestListStorageInterface $interest_list_storage
   *   Interest list storage.
   * @param \Drupal\acquia_contenthub\Libs\Logging\ContentHubLoggerInterface $ch_logger
   *   Content Hub logger service.
   * @param \Drupal\acquia_contenthub_subscriber\EntityImportService $entity_import_service
   *   Entity Import Service.
   * @param \Drupal\acquia_contenthub\Client\ClientFactory $client_factory
   *   The Content Hub Client factory service.
   */
  public function __construct(SubscriberTracker $tracker, ContentHubConfigurationInterface $ch_configuration, InterestListStorageInterface $interest_list_storage, ContentHubLoggerInterface $ch_logger, EntityImportService $entity_import_service, ClientFactory $client_factory) {
    $this->tracker = $tracker;
    $this->achConfigurations = $ch_configuration;
    $this->interestListStorage = $interest_list_storage;
    $this->chLogger = $ch_logger;
    $this->entityImportService = $entity_import_service;
    $this->clientFactory = $client_factory;
  }

  /**
   * {@inheritdoc}
   */
  public function execute(array $queue_items): void {
    $client = $this->getClient();
    $client_uuid = $client->getSettings()->getUuid();
    $valid_queue_items = [];
    $entity_uuids = [];

    foreach ($queue_items as $queue_item) {
      $type = $queue_item->getCdfType();
      if ($queue_item->getInitiator() === $client_uuid) {
        if ($type !== 'client') {
          $this->chLogger->getChannel()
            ->info('Service queue item will not be processed because its initiator is the existing client.
        Queue item data: {queue_item}', ['queue_item' => $queue_item]);
        }
        continue;
      }

      $entity_uuid = $queue_item->getEntityUuid();
      if (!$this->isSupportedType($type)) {
        $this->chLogger->getChannel()->info('Entity with UUID {uuid} was not imported because it has an unsupported type: {type}', [
          'uuid' => $entity_uuid,
          'type' => $type,
        ]);
        continue;
      }
      $valid_queue_items[$entity_uuid] = $queue_item;
      $entity_uuids[] = $entity_uuid;
    }
    if (empty($entity_uuids)) {
      return;
    }

    $untracked_entities = $this->tracker->getUntracked($entity_uuids);
    $tracked_entities = array_diff($entity_uuids, $untracked_entities);
    $auto_update_disabled_uuids = [];
    if (!empty($tracked_entities)) {
      $statuses = $this->tracker->getStatusesByUuids($tracked_entities);
      foreach ($statuses as $uuid => $status) {
        if ($status === SubscriberTracker::AUTO_UPDATE_DISABLED) {
          $auto_update_disabled_uuids[] = $uuid;
        }
      }
      if (!empty($auto_update_disabled_uuids)) {
        $this->chLogger->getChannel()->info('Entities not processed due to auto update disabled: {uuids}', ['uuids' => implode(', ', $auto_update_disabled_uuids)]);
      }
    }

    $entities_to_process = array_merge($untracked_entities, array_diff($tracked_entities, $auto_update_disabled_uuids));
    if (empty($entities_to_process)) {
      return;
    }

    $this->chLogger->getChannel()->info('Attempting to import entities with UUIDs: {uuids}', ['uuids' => implode(', ', $entities_to_process)]);
    $this->processMultipleEntityUpserts($entities_to_process, $valid_queue_items);
  }

  /**
   * Processes multiple entity upserts.
   *
   * @param array $entity_uuids
   *   Array of entity UUIDs to process.
   * @param array $valid_queue_items
   *   Array of valid queue items keyed by entity UUID.
   *
   * @throws \Drupal\acquia_contenthub\Exception\ContentHubClientException
   * @throws \Drupal\acquia_contenthub_subscriber\Exception\ContentHubImportException|\Drupal\acquia_contenthub\Exception\ContentHubException
   */
  protected function processMultipleEntityUpserts(array $entity_uuids, array $valid_queue_items): void {
    $settings = $this->getClient()->getSettings();
    $webhook_uuid = $settings->getWebhook('uuid');
    if (!$webhook_uuid) {
      $this->chLogger->getChannel()->error('Webhook UUID not found. Cannot process entity upserts.');
      return;
    }

    // @todo CHS-9037 move adding to interest list to PubDash.
    $this->entityImportService->addDependenciesToInterestList($webhook_uuid, $entity_uuids, 'entity_upsert');

    $query = ['uuids' => $entity_uuids];
    $interest_list = $this->interestListStorage->getInterestList($webhook_uuid, static::SITE_ROLE, $query);
    $entities_in_interest_list = array_keys($interest_list);
    $entities_not_in_interest_list = array_diff($entity_uuids, $entities_in_interest_list);

    if (!empty($entities_not_in_interest_list)) {
      $this->chLogger->getChannel()->info('Entities not in interest list, skipping import: {uuids}',
        ['uuids' => implode(', ', $entities_not_in_interest_list)]);
    }

    if (empty($entities_in_interest_list)) {
      return;
    }

    $stack = $this->entityImportService->importEntities($entities_in_interest_list, $webhook_uuid);
    if (!$stack) {
      return;
    }

    if ($this->achConfigurations->getContentHubConfig()->shouldSendContentHubUpdates()) {
      $this->handlePostImportOperations($entities_in_interest_list, $webhook_uuid, $stack, $valid_queue_items);
    }

    $this->chLogger->getChannel()->info('Entities imported successfully: {uuids}', ['uuids' => implode(', ', $entities_in_interest_list)]);
  }

  /**
   * Handles post-import operations like updating interest lists.
   *
   * @param array $entity_uuids
   *   Array of successfully imported entity UUIDs.
   * @param string $webhook_uuid
   *   The webhook UUID.
   * @param \Drupal\depcalc\DependencyStack $stack
   *   The import stack containing dependencies.
   * @param array $valid_queue_items
   *   Array of valid queue items keyed by entity UUID.
   */
  protected function handlePostImportOperations(array $entity_uuids, string $webhook_uuid, DependencyStack $stack, array $valid_queue_items): void {
    $enabled_entities = $this->interestListStorage->filterDisabledEntities($webhook_uuid, $entity_uuids, static::SITE_ROLE);
    if (!empty($enabled_entities)) {
      $this->entityImportService->updateInterestList($webhook_uuid, $enabled_entities, SyndicationStatus::IMPORT_SUCCESSFUL);
    }

    $dependency_uuids = array_keys($stack->getDependencies());
    if (empty($dependency_uuids)) {
      return;
    }

    $enabled_dependencies = $this->interestListStorage->filterDisabledEntities($webhook_uuid, $dependency_uuids, static::SITE_ROLE);
    if (empty($enabled_dependencies)) {
      return;
    }

    $dependencies_by_reason = [];
    foreach ($entity_uuids as $entity_uuid) {
      if (isset($valid_queue_items[$entity_uuid])) {
        $reason = $valid_queue_items[$entity_uuid]->getReason();
        $dependencies_by_reason[$reason] = array_merge($dependencies_by_reason[$reason] ?? [], $enabled_dependencies);
      }
    }
    foreach ($dependencies_by_reason as $reason => $deps) {
      $unique_deps = array_unique($deps);
      $this->entityImportService->addDependenciesToInterestList($webhook_uuid, $unique_deps, $reason);
    }
  }

  /**
   * Determines if given entity type is supported.
   *
   * @param string $type
   *   The CDF type.
   *
   * @return bool
   *   TRUE if is supported type; FALSE otherwise.
   */
  protected function isSupportedType(string $type): bool {
    if (!$type) {
      return FALSE;
    }

    $supported_types = [
      'drupal8_content_entity',
      'drupal8_config_entity',
    ];

    return in_array($type, $supported_types, TRUE);
  }

  /**
   * {@inheritdoc}
   */
  public function canHandle(QueueItem $queue_item): bool {
    return $queue_item->isActionOneOf([
      ServiceQueueEntityActions::CREATE,
      ServiceQueueEntityActions::UPDATE,
    ]);
  }

}
