<?php

namespace Drupal\acquia_contenthub_subscriber\EventSubscriber\HandleWebhook;

use Acquia\ContentHubClient\ContentHubClient;
use Acquia\ContentHubClient\Syndication\SyndicationStatus;
use Drupal\acquia_contenthub\AcquiaContentHubEvents;
use Drupal\acquia_contenthub\Client\CdfMetricsManager;
use Drupal\acquia_contenthub\Event\HandleWebhookEvent;
use Drupal\acquia_contenthub\Libs\InterestList\InterestListStorageInterface;
use Drupal\acquia_contenthub\Libs\InterestList\InterestListTrait;
use Drupal\acquia_contenthub\QueueInspectorInterface;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drupal\acquia_contenthub_subscriber\SubscriberTracker;
use Drupal\acquia_contenthub_subscriber\Traits\ImportQueueTrait;
use Drupal\Component\Uuid\Uuid;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\Queue\QueueInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Imports and updates multiple assets.
 *
 * @package Drupal\acquia_contenthub_subscriber\EventSubscriber\HandleWebhook
 */
class ImportUpdateMultipleAssets implements EventSubscriberInterface {

  use InterestListTrait;
  use ImportQueueTrait;

  /**
   * The Subscriber Import Queue Inspector.
   *
   * @var \Drupal\acquia_contenthub\QueueInspectorInterface
   */
  protected QueueInspectorInterface $queueInspector;

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

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

  /**
   * The acquia_contenthub logger channel.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected LoggerInterface $channel;

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

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

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

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

  /**
   * ImportUpdateAssets constructor.
   *
   * @param \Drupal\Core\Queue\QueueFactory $queue
   *   The queue factory.
   * @param \Drupal\acquia_contenthub_subscriber\SubscriberTracker $tracker
   *   The subscription tracker.
   * @param \Psr\Log\LoggerInterface $logger_channel
   *   The logger channel factory.
   * @param \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ch_configuration
   *   CH configuration.
   * @param \Drupal\acquia_contenthub\Client\CdfMetricsManager $cdf_metrics_manager
   *   Cdf metrics manager.
   * @param \Drupal\acquia_contenthub\QueueInspectorInterface $queue_inspector
   *   Queue inspector.
   * @param \Drupal\acquia_contenthub\Libs\InterestList\InterestListStorageInterface $interest_list_storage
   *   Interest list storage.
   */
  public function __construct(QueueFactory $queue, SubscriberTracker $tracker, LoggerInterface $logger_channel, ContentHubConfigurationInterface $ch_configuration, CdfMetricsManager $cdf_metrics_manager, QueueInspectorInterface $queue_inspector, InterestListStorageInterface $interest_list_storage) {
    $this->queue = $queue->get('acquia_contenthub_subscriber_import');
    $this->tracker = $tracker;
    $this->channel = $logger_channel;
    $this->achConfigurations = $ch_configuration;
    $this->cdfMetricsManager = $cdf_metrics_manager;
    $this->queueInspector = $queue_inspector;
    $this->interestListStorage = $interest_list_storage;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[AcquiaContentHubEvents::HANDLE_WEBHOOK][] = 'onHandleWebhook';
    return $events;
  }

  /**
   * Handles webhook events.
   *
   * @param \Drupal\acquia_contenthub\Event\HandleWebhookEvent $event
   *   The HandleWebhookEvent object.
   *
   * @throws \Exception
   */
  public function onHandleWebhook(HandleWebhookEvent $event) {
    // @todo Would be nice to have one place with statuses list - $payload['status'].
    // @todo The same regarding $payload['crud'] and supported types ($asset['type']).
    $payload = $event->getPayload();
    $this->client = $event->getClient();

    if (!isset($payload['crud']) || $payload['crud'] !== 'update' || !$event->isWebhookV2()) {
      return;
    }

    if ($payload['status'] !== 'successful' || !isset($payload['assets']) || !count($payload['assets'])) {
      $this->channel
        ->info('Payload will not be processed because it is not successful or it does not have assets.
        Payload data: {payload}', ['payload' => print_r($payload, TRUE)]);
      return;
    }

    $uuids = [];
    $values = [];
    $types = ['drupal8_content_entity', 'drupal8_config_entity'];
    $uuids_with_reasons = [];
    foreach ($payload['assets'] as $asset) {
      if (!empty($asset['origin']) && $asset['origin'] === $this->client->getSettings()->getUuid()) {
        if ($asset['type'] !== 'client') {
          $this->channel
            ->info('Entity will not be processed because its origin is the existing client.
            Asset data: {asset}', ['asset' => print_r($asset, TRUE)]);
        }
        continue;
      }
      $uuid = $asset['uuid'];
      $type = $asset['type'];
      if (!in_array($type, $types)) {
        $this->channel
          ->info('Entity with UUID {uuid} was not added to the import queue because it has an unsupported type: {type}',
            ['uuid' => $uuid, 'type' => $type]
          );
        continue;
      }
      if ($this->tracker->isTracked($uuid)) {
        $status = $this->tracker->getStatusByUuid($uuid);
        if ($status === SubscriberTracker::AUTO_UPDATE_DISABLED) {
          $this->channel
            ->info('Entity with UUID {uuid} was not added to the import queue because it has auto update disabled.',
              ['uuid' => $uuid]
            );
          continue;
        }
      }

      $uuids[] = $uuid;
      $values[] = ['entity_uuid' => $uuid, 'hash' => ''];
      if (!empty($asset['reason'])) {
        $uuids_with_reasons[$uuid] = $asset['reason'];
      }
      $this->channel
        ->info('Attempting to add entity with UUID {uuid} to the import queue.',
        ['uuid' => $uuid]
        );

    }
    if (!empty($values)) {
      $this->tracker->queueMultiple($values);
    }
    $uuids = $this->filterByAlreadyEnqueuedItems($uuids, $this->queueInspector);
    if (empty($uuids)) {
      return;
    }
    if (!empty($uuids_with_reasons)) {
      $this->createQueueItems($uuids_with_reasons, $uuids);
    }
    $send_contenthub_updates = $this->achConfigurations->getContentHubConfig()->shouldSendContentHubUpdates();
    if ($send_contenthub_updates) {
      $webhook_uuid = $this->client->getSettings()->getWebhook('uuid');
      if ($this->isWebhookLandingManual($payload)) {
        $disabled_entities = $this->interestListStorage->getInterestList($webhook_uuid, 'subscriber', [
          'uuids' => $uuids,
          'disable_syndication' => TRUE,
        ]);
        $uuids = $this->filterDisabledEntities($uuids, array_keys($disabled_entities));
      }
      $this->addEntitiesToInterestList($webhook_uuid, $uuids, $uuids_with_reasons);
    }

    $this->cdfMetricsManager->sendClientCdfUpdates();
  }

  /**
   * Adds uuids to the interest list for webhook v2.
   *
   * @param string $webhook_uuid
   *   Webhook uuids.
   * @param array $uuids
   *   Uuids to be added interest list.
   * @param array $uuids_with_reasons
   *   Key is uuid and value is reason.
   */
  public function addEntitiesToInterestList(string $webhook_uuid, array $uuids, array $uuids_with_reasons): void {
    if (empty($uuids_with_reasons)) {
      return;
    }
    $reasons_with_uuids = $this->filterReasonsWithUuids($uuids_with_reasons, $uuids);
    foreach ($reasons_with_uuids as $reason => $uuids_per_reason) {
      $this->client->addEntitiesToInterestListBySiteRole(
        $webhook_uuid,
        'subscriber',
        $this->buildInterestList(
          $uuids_per_reason,
          SyndicationStatus::QUEUED_TO_IMPORT,
          $reason === 'empty_reason' ? 'manual' : $reason
        )
      );
    }
  }

  /**
   * Creates queue items for webhook V2 assets.
   *
   * @param array $reasons
   *   Key is uuid and value is reason.
   * @param array $uuids
   *   Uuids.
   *
   * @throws \Exception
   */
  protected function createQueueItems(array $reasons, array $uuids) {
    $filtered_reasons = $this->filterReasonsWithUuids($reasons, $uuids);
    foreach ($filtered_reasons as $reason => $sub_uuids) {
      $item = new \stdClass();
      if ($reason !== 'empty_reason') {
        $item->filter_uuid = $reason;
      }
      $item->uuids = implode(', ', $sub_uuids);
      $queue_id = $this->queue->createItem($item);
      if (empty($queue_id)) {
        continue;
      }
      $this->tracker->setQueueItemByUuids($sub_uuids, $queue_id);

      $reason === 'empty_reason' ?
        $this->channel
          ->info('Entities with UUIDs {uuids} added to the import queue and to the tracking table.',
            ['uuids' => print_r($sub_uuids, TRUE)]) :
        $this->channel
          ->info('Entities with UUIDs {uuids} added to the import queue and to the tracking table. Reason: {reason}',
            [
              'uuids' => print_r($sub_uuids, TRUE),
              'reason' => $reason,
            ]);
    }
  }

  /**
   * Provides filtered and formatted reasons with uuids.
   *
   * @param array $reasons
   *   Key is uuid and value is reason.
   * @param array $uuids
   *   Uuids.
   *
   * @return array
   *   Array of formatted reasons.
   */
  protected function filterReasonsWithUuids(array $reasons, array $uuids): array {
    $filtered_reasons = [];
    foreach ($uuids as $uuid) {
      if (empty($reasons[$uuid])) {
        $filtered_reasons['empty_reason'][] = $uuid;
        continue;
      }
      $filtered_reasons[$reasons[$uuid]][] = $uuid;
    }
    return $filtered_reasons;
  }

  /**
   * Filters uuids of disabled entities.
   *
   * @param array $uuids
   *   Source uuids.
   * @param array $disabled_entities
   *   Array of disabled entities.
   *
   * @return array
   *   List of entity uuids without disabled entity uuids.
   */
  protected function filterDisabledEntities(array $uuids, array $disabled_entities): array {
    if (empty($disabled_entities)) {
      return $uuids;
    }

    return array_values(array_diff($uuids, $disabled_entities));
  }

  /**
   * Checks if webhook's landing is manual or automatic.
   *
   * @param array $payload
   *   The payload.
   *
   * @return bool
   *   True if webhook landing is manual.
   */
  protected function isWebhookLandingManual(array $payload): bool {
    return empty($payload['uuid']) || !Uuid::isValid($payload['uuid']);
  }

}
