<?php

namespace Drupal\Tests\acquia_contenthub_subscriber\Kernel\Libs\PullSyndication;

use Acquia\ContentHubClient\ContentHubClient;
use Acquia\ContentHubClient\Settings;
use Acquia\ContentHubClient\Syndication\SyndicationStatus;
use Drupal\acquia_contenthub\Client\ClientFactory;
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\ServiceQueueEntityActions;
use Drupal\acquia_contenthub\Settings\ConfigSettingsInterface;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drupal\acquia_contenthub_subscriber\EntityImportService;
use Drupal\acquia_contenthub_subscriber\Libs\PullSyndication\EntityUpsertAction;
use Drupal\acquia_contenthub_subscriber\SubscriberTracker;
use Drupal\depcalc\DependencyStack;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\LoggerInterface;

/**
 * Tests entity upsert action.
 *
 * @group acquia_contenthub_subscriber
 *
 * @coversDefaultClass \Drupal\acquia_contenthub_subscriber\Libs\PullSyndication\EntityUpsertAction
 *
 * @requires module depcalc
 *
 * @package Drupal\Tests\acquia_contenthub_subscriber\Kernel\Libs\PullSyndication
 */
class EntityUpsertActionTest extends EntityKernelTestBase {

  use ProphecyTrait;

  /**
   * Entity upsert action instance.
   *
   * @var \Drupal\acquia_contenthub_subscriber\Libs\PullSyndication\EntityUpsertAction
   */
  protected EntityUpsertAction $entityUpsertAction;

  /**
   * SubscriberTracker.
   *
   * @var \Drupal\acquia_contenthub_subscriber\SubscriberTracker|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $tracker;

  /**
   * ContentHubConfigurationInterface.
   *
   * @var \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $achConfigurations;

  /**
   * InterestListStorageInterface.
   *
   * @var \Drupal\acquia_contenthub\Libs\InterestList\InterestListStorageInterface|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $interestListStorage;

  /**
   * ContentHubLoggerInterface.
   *
   * @var \Drupal\acquia_contenthub\Libs\Logging\ContentHubLoggerInterface|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $chLogger;

  /**
   * Logger Channel.
   *
   * @var \Psr\Log\LoggerInterface|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $loggerChannel;

  /**
   * EntityImportService.
   *
   * @var \Drupal\acquia_contenthub_subscriber\EntityImportService|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $entityImportService;

  /**
   * Mock client factory.
   *
   * @var \Drupal\acquia_contenthub\Client\ClientFactory|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $clientFactory;

  /**
   * ContentHubClient.
   *
   * @var \Acquia\ContentHubClient\ContentHubClient|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $client;

  /**
   * Settings.
   *
   * @var \Acquia\ContentHubClient\Settings|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $settings;

  /**
   * DependencyStack.
   *
   * @var \Drupal\depcalc\DependencyStack|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $dependencyStack;

  /**
   * Test webhook UUID.
   *
   * @var string
   */
  protected string $webhookUuid = 'webhook-uuid-123';

  /**
   * Test entity UUID.
   *
   * @var string
   */
  protected string $entityUuid = 'entity-uuid-123';

  /**
   * Modules to enable.
   *
   * @var array
   */
  protected static $modules = [
    'acquia_contenthub',
    'acquia_contenthub_subscriber',
    'depcalc',
  ];

  /**
   * {@inheritdoc}
   */
  public function setUp(): void {
    parent::setUp();

    $this->installSchema('acquia_contenthub_subscriber', ['acquia_contenthub_subscriber_import_tracking']);

    $this->setupMocks();
    $this->createEntityUpsertAction();
  }

  /**
   * Sets up all the mocks needed for testing.
   */
  protected function setupMocks(): void {
    $this->tracker = $this->prophesize(SubscriberTracker::class);
    $this->achConfigurations = $this->prophesize(ContentHubConfigurationInterface::class);
    $this->interestListStorage = $this->prophesize(InterestListStorageInterface::class);
    $this->loggerChannel = $this->prophesize(LoggerInterface::class);
    $this->chLogger = $this->prophesize(ContentHubLoggerInterface::class);
    $this->chLogger->getChannel()->willReturn($this->loggerChannel->reveal());
    $this->entityImportService = $this->prophesize(EntityImportService::class);
    $this->dependencyStack = $this->prophesize(DependencyStack::class);
    $this->settings = $this->prophesize(Settings::class);
    $this->settings->getUuid()->willReturn('client-uuid-123');
    $this->settings->getWebhook('uuid')->willReturn($this->webhookUuid);
    $this->client = $this->prophesize(ContentHubClient::class);
    $this->client->getSettings()->willReturn($this->settings->reveal());
    $this->clientFactory = $this->prophesize(ClientFactory::class);
    $this->clientFactory->getClient()->willReturn($this->client->reveal());
  }

  /**
   * Creates the EntityUpsertAction instance.
   */
  protected function createEntityUpsertAction(): void {
    $this->entityUpsertAction = new EntityUpsertAction(
      $this->tracker->reveal(),
      $this->achConfigurations->reveal(),
      $this->interestListStorage->reveal(),
      $this->chLogger->reveal(),
      $this->entityImportService->reveal(),
      $this->clientFactory->reveal()
    );
  }

  /**
   * @covers ::canHandle
   *
   * @dataProvider canHandleDataProvider
   */
  public function testCanHandle(string $action, bool $expected): void {
    $queue_item_data = [
      'id' => '123',
      'entity_uuid' => $this->entityUuid,
      'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb966',
      'state' => 'queued',
      'payload' => ['action' => $action],
      'visible_at' => '',
      'created_at' => '',
      'updated_at' => '',
    ];

    $queue_item = QueueItem::fromArray($queue_item_data);
    $this->assertEquals($expected, $this->entityUpsertAction->canHandle($queue_item));
  }

  /**
   * Data provider for testCanHandle.
   *
   * @return array
   *   Test data.
   */
  public static function canHandleDataProvider(): array {
    return [
      'CREATE action' => [ServiceQueueEntityActions::CREATE, TRUE],
      'UPDATE action' => [ServiceQueueEntityActions::UPDATE, TRUE],
      'DELETE action' => [ServiceQueueEntityActions::DELETE, FALSE],
      'unknown action' => ['unknown_action', FALSE],
    ];
  }

  /**
   * @covers ::execute
   * @covers ::isSupportedType
   *
   * @dataProvider supportedTypeDataProvider
   */
  public function testSupportedTypes(string $type, bool $expected): void {
    $queue_item = $this->createQueueItem($type);

    if (!$expected) {
      $this->loggerChannel->info(
        'Entity with UUID {uuid} was not imported because it has an unsupported type: {type}',
        [
          'uuid' => $this->entityUuid,
          'type' => $type,
        ]
      )->shouldBeCalled();
    }
    else {
      $this->entityImportService->addDependenciesToInterestList(
        $this->webhookUuid,
        [$this->entityUuid],
        'entity_upsert'
      )->shouldBeCalled();
      $this->tracker->getUntracked([$this->entityUuid])->willReturn([$this->entityUuid]);
      $this->setupSuccessfulProcessing();
    }

    $this->entityUpsertAction->execute([$queue_item]);
  }

  /**
   * Data provider for supported types.
   *
   * @return array
   *   Test data.
   */
  public static function supportedTypeDataProvider(): array {
    return [
      'drupal8_content_entity' => ['drupal8_content_entity', TRUE],
      'drupal8_config_entity' => ['drupal8_config_entity', TRUE],
      'unsupported_type' => ['client_entity', FALSE],
      'empty_type' => ['', FALSE],
    ];
  }

  /**
   * @covers ::execute
   */
  public function testExecuteSkipsTrackedEntityWithAutoUpdateDisabled(): void {
    $queue_item = $this->createQueueItem('drupal8_content_entity');

    $this->tracker->getUntracked([$this->entityUuid])->willReturn([]);
    $this->tracker->getStatusesByUuids([$this->entityUuid])->willReturn([
      $this->entityUuid => SubscriberTracker::AUTO_UPDATE_DISABLED,
    ]);

    $this->loggerChannel->info(
      'Entities not processed due to auto update disabled: {uuids}',
      ['uuids' => $this->entityUuid]
    )->shouldBeCalled();

    $this->entityUpsertAction->execute([$queue_item]);
  }

  /**
   * @covers ::execute
   */
  public function testExecuteProcessesTrackedEntityWithAutoUpdateEnabled(): void {
    $queue_item = $this->createQueueItem('drupal8_content_entity');

    $this->tracker->getUntracked([$this->entityUuid])->willReturn([]);
    $this->tracker->getStatusesByUuids([$this->entityUuid])->willReturn([
      $this->entityUuid => 'some_other_status',
    ]);

    $this->setupSuccessfulProcessing();
    $this->entityImportService->addDependenciesToInterestList(
      $this->webhookUuid,
      [$this->entityUuid],
      'entity_upsert'
    )->shouldBeCalled();

    $this->entityUpsertAction->execute([$queue_item]);
  }

  /**
   * @covers ::execute
   */
  public function testExecuteProcessesNonTrackedEntity(): void {
    $queue_item = $this->createQueueItem('drupal8_content_entity');

    $this->tracker->getUntracked([$this->entityUuid])->willReturn([$this->entityUuid]);

    $this->setupSuccessfulProcessing();
    $this->entityImportService->addDependenciesToInterestList(
      $this->webhookUuid,
      [$this->entityUuid],
      'entity_upsert'
    )->shouldBeCalled();

    $this->entityUpsertAction->execute([$queue_item]);
  }

  /**
   * @covers ::execute
   * @covers ::processMultipleEntityUpserts
   */
  public function testExecuteHandlesMissingWebhookUuid(): void {
    $queue_item = $this->createQueueItem('drupal8_content_entity');

    $this->tracker->getUntracked([$this->entityUuid])->willReturn([$this->entityUuid]);
    $this->settings->getWebhook('uuid')->willReturn(NULL);

    $this->loggerChannel->info(
      'Attempting to import entities with UUIDs: {uuids}',
      ['uuids' => $this->entityUuid]
    )->shouldBeCalled();
    $this->loggerChannel->error(
      'Webhook UUID not found. Cannot process entity upserts.'
    )->shouldBeCalled();

    $this->entityUpsertAction->execute([$queue_item]);
  }

  /**
   * @covers ::execute
   * @covers ::processMultipleEntityUpserts
   */
  public function testExecuteSkipsEntityNotInInterestList(): void {
    $queue_item = $this->createQueueItem('drupal8_content_entity');

    $this->tracker->getUntracked([$this->entityUuid])->willReturn([$this->entityUuid]);
    $this->interestListStorage->getInterestList(
      $this->webhookUuid,
      'subscriber',
      ['uuids' => [$this->entityUuid]]
    )->willReturn([]);

    $this->loggerChannel->info(
      'Attempting to import entities with UUIDs: {uuids}',
      ['uuids' => $this->entityUuid]
    )->shouldBeCalled();
    $this->loggerChannel->info(
      'Entities not in interest list, skipping import: {uuids}',
      ['uuids' => $this->entityUuid]
    )->shouldBeCalled();

    $this->entityUpsertAction->execute([$queue_item]);
  }

  /**
   * @covers ::execute
   * @covers ::processMultipleEntityUpserts
   */
  public function testExecuteHandlesImportFailure(): void {
    $queue_item = $this->createQueueItem('drupal8_content_entity');

    $this->tracker->getUntracked([$this->entityUuid])->willReturn([$this->entityUuid]);
    $this->interestListStorage->getInterestList(
      $this->webhookUuid,
      'subscriber',
      ['uuids' => [$this->entityUuid]]
    )->willReturn([$this->entityUuid => 'some_data']);

    $this->entityImportService->addDependenciesToInterestList(
      $this->webhookUuid,
      [$this->entityUuid],
      'entity_upsert'
    )->shouldBeCalled();

    $this->entityImportService->importEntities(
      [$this->entityUuid],
      $this->webhookUuid
    )->willReturn(NULL);

    $this->entityUpsertAction->execute([$queue_item]);
  }

  /**
   * @covers ::execute
   * @covers ::processMultipleEntityUpserts
   */
  public function testExecuteSuccessfulImportWithoutDependencies(): void {
    $queue_item = $this->createQueueItem('drupal8_content_entity');

    $this->tracker->getUntracked([$this->entityUuid])->willReturn([$this->entityUuid]);
    $this->interestListStorage->getInterestList(
      $this->webhookUuid,
      'subscriber',
      ['uuids' => [$this->entityUuid]]
    )->willReturn([$this->entityUuid => 'some_data']);

    $this->interestListStorage->filterDisabledEntities(
      $this->webhookUuid,
      [$this->entityUuid],
      'subscriber'
    )->willReturn([$this->entityUuid]);

    $this->dependencyStack->getDependencies()->willReturn([]);

    $this->entityImportService->importEntities(
      [$this->entityUuid],
      $this->webhookUuid
    )->willReturn($this->dependencyStack->reveal());

    $config = $this->prophesize(ConfigSettingsInterface::class);
    $config->shouldSendContentHubUpdates()->willReturn(TRUE);
    $this->achConfigurations->getContentHubConfig()->willReturn($config->reveal());

    $this->entityImportService->addDependenciesToInterestList(
      $this->webhookUuid,
      [$this->entityUuid],
      'entity_upsert'
    )->shouldBeCalled();

    $this->interestListStorage->isEntityDisabled(
      $this->webhookUuid,
      $this->entityUuid,
      'subscriber'
    )->willReturn(FALSE);

    $this->entityImportService->updateInterestList(
      $this->webhookUuid,
      [$this->entityUuid],
      SyndicationStatus::IMPORT_SUCCESSFUL,
    )->shouldBeCalled();

    $this->loggerChannel->info(
      'Attempting to import entities with UUIDs: {uuids}',
      ['uuids' => $this->entityUuid]
    )->shouldBeCalled();

    $this->loggerChannel->info(
      'Entities imported successfully: {uuids}',
      ['uuids' => $this->entityUuid]
    )->shouldBeCalled();

    $this->entityUpsertAction->execute([$queue_item]);
  }

  /**
   * @covers ::execute
   * @covers ::processMultipleEntityUpserts
   */
  public function testExecuteSkipsContentHubUpdatesWhenDisabled(): void {
    $queue_item = $this->createQueueItem('drupal8_content_entity');

    $this->tracker->getUntracked([$this->entityUuid])->willReturn([$this->entityUuid]);
    $this->interestListStorage->getInterestList(
      $this->webhookUuid,
      'subscriber',
      ['uuids' => [$this->entityUuid]]
    )->willReturn([$this->entityUuid => 'some_data']);

    $this->interestListStorage->filterDisabledEntities(
      $this->webhookUuid,
      [$this->entityUuid],
      'subscriber'
    )->willReturn([$this->entityUuid]);

    $this->dependencyStack->getDependencies()->willReturn([]);

    $this->entityImportService->importEntities(
      [$this->entityUuid],
      $this->webhookUuid
    )->willReturn($this->dependencyStack->reveal());

    $config = $this->prophesize(ConfigSettingsInterface::class);
    $config->shouldSendContentHubUpdates()->willReturn(FALSE);
    $this->achConfigurations->getContentHubConfig()->willReturn($config->reveal());

    // Should not call interest list methods when CH updates are disabled.
    $this->interestListStorage->isEntityDisabled(Argument::cetera())->shouldNotBeCalled();
    $this->entityImportService->updateInterestList(Argument::cetera())->shouldNotBeCalled();

    $this->loggerChannel->info(
      'Attempting to import entities with UUIDs: {uuids}',
      ['uuids' => $this->entityUuid]
    )->shouldBeCalled();

    $this->loggerChannel->info(
      'Entities imported successfully: {uuids}',
      ['uuids' => $this->entityUuid]
    )->shouldBeCalled();
    $this->entityImportService->addDependenciesToInterestList(
      $this->webhookUuid,
      [$this->entityUuid],
      'entity_upsert'
    )->shouldBeCalled();

    $this->entityUpsertAction->execute([$queue_item]);
  }

  /**
   * Creates a QueueItem for testing.
   *
   * @param string $type
   *   The CDF type.
   * @param string|null $reason
   *   The optional reason/filter UUID.
   *
   * @return \Drupal\acquia_contenthub\Libs\ServiceQueue\QueueItem
   *   The queue item.
   */
  protected function createQueueItem(string $type, ?string $reason = NULL): QueueItem {
    $payload = [
      'action' => ServiceQueueEntityActions::CREATE,
      'type' => $type,
    ];

    if ($reason !== NULL) {
      $payload['reason'] = $reason;
    }

    $queue_item_data = [
      'id' => '123',
      'entity_uuid' => $this->entityUuid,
      'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb966',
      'state' => 'queued',
      'payload' => $payload,
      'visible_at' => '',
      'created_at' => '',
      'updated_at' => '',
    ];

    return QueueItem::fromArray($queue_item_data);
  }

  /**
   * Sets up mocks for successful batch processing path.
   */
  protected function setupSuccessfulProcessing(): void {
    $this->interestListStorage->getInterestList(
      $this->webhookUuid,
      'subscriber',
      ['uuids' => [$this->entityUuid]]
    )->willReturn([$this->entityUuid => ['status' => 'queued']]);

    $this->dependencyStack->getDependencies()->willReturn([]);

    $this->entityImportService->importEntities(
      [$this->entityUuid],
      $this->webhookUuid
    )->willReturn($this->dependencyStack->reveal());

    $config = $this->prophesize(ConfigSettingsInterface::class);
    $config->shouldSendContentHubUpdates()->willReturn(TRUE);
    $this->achConfigurations->getContentHubConfig()->willReturn($config->reveal());

    $this->interestListStorage->filterDisabledEntities(
      $this->webhookUuid,
      [$this->entityUuid],
      'subscriber'
    )->willReturn([$this->entityUuid]);

    $this->entityImportService->updateInterestList(
      $this->webhookUuid,
      [$this->entityUuid],
      SyndicationStatus::IMPORT_SUCCESSFUL
    )->shouldBeCalled();

    $this->loggerChannel->info(
      'Attempting to import entities with UUIDs: {uuids}',
      ['uuids' => $this->entityUuid]
    )->shouldBeCalled();
    $this->loggerChannel->info(
      'Entities imported successfully: {uuids}',
      ['uuids' => $this->entityUuid]
    )->shouldBeCalled();
  }

}
