<?php

namespace Drupal\Tests\acquia_contenthub\Kernel\Libs\ServiceQueue;

use Acquia\ContentHubClient\ContentHubClient;
use Acquia\ContentHubClient\Settings;
use Drupal\acquia_contenthub\AcquiaContentHubEvents;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub\Event\ServiceQueue\ServiceQueueItemsFailedEvent;
use Drupal\acquia_contenthub\Event\ServiceQueue\ServiceQueueItemsProcessingFinishedEvent;
use Drupal\acquia_contenthub\Libs\ServiceQueue\QueueItem;
use Drupal\acquia_contenthub\Libs\ServiceQueue\ServiceQueueEntityActions;
use Drupal\acquia_contenthub\Libs\ServiceQueue\ServiceQueueHandler;
use Drupal\acquia_contenthub_subscriber\Libs\PullSyndication\EntityDeleteAction;
use Drupal\acquia_contenthub_subscriber\Libs\PullSyndication\EntityUpsertAction;
use Drupal\KernelTests\KernelTestBase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Tests service queue handler.
 *
 * @coversDefaultClass \Drupal\acquia_contenthub\Libs\ServiceQueue\ServiceQueueHandler
 *
 * @group acquia_contenthub
 *
 * @package Drupal\Tests\acquia_contenthub\Kernel\Libs\ServiceQueue
 */
class ServiceQueueHandlerTest extends KernelTestBase {

  use ProphecyTrait;

  /**
   * SUT.
   *
   * @var \Drupal\acquia_contenthub\Libs\ServiceQueue\ServiceQueueHandler
   */
  protected ServiceQueueHandler $sut;

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

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

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

  /**
   * Mock event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $dispatcher;

  /**
   * Mock logger.
   *
   * @var \Psr\Log\LoggerInterface|\Prophecy\Prophecy\ObjectProphecy
   */
  protected $logger;

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

  /**
   * Entity delete action.
   *
   * @var \Drupal\acquia_contenthub_subscriber\Libs\PullSyndication\EntityDeleteAction
   */
  protected EntityDeleteAction $entityDeleteAction;

  /**
   * {@inheritDoc}
   */
  protected static $modules = [
    'acquia_contenthub',
    'acquia_contenthub_subscriber',
    'depcalc',
    'user',
  ];

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

    $this->clientFactory = $this->prophesize(ClientFactory::class);
    $this->settings = $this->prophesize(Settings::class);
    $this->settings->getUuid()->willReturn('client-uuid-123');
    $this->settings->getWebhook('uuid')->willReturn('webhook-uuid-456');
    $this->client = $this->prophesize(ContentHubClient::class);
    $this->client->getSettings()->willReturn($this->settings->reveal());
    $this->clientFactory->getClient()->willReturn($this->client->reveal());
    $this->container->set('acquia_contenthub.client.factory', $this->clientFactory->reveal());
    $this->dispatcher = $this->prophesize(EventDispatcherInterface::class);
    $this->logger = $this->prophesize(LoggerInterface::class);
    $this->entityUpsertAction = $this->container->get('acquia_contenthub_subscriber.queue_item_action.entity_upsert');
    $this->entityDeleteAction = $this->container->get('acquia_contenthub_subscriber.queue_item_action.entity_delete');

    $this->sut = new ServiceQueueHandler($this->clientFactory->reveal(), $this->dispatcher->reveal(), $this->logger->reveal());

    $this->sut->addAction($this->entityUpsertAction);
    $this->sut->addAction($this->entityDeleteAction);
  }

  /**
   * @covers ::processQueueItems
   */
  public function testProcessQueueItemsSuccessful(): void {
    $entities_data = [
      [
        'id' => '123',
        'entity_uuid' => '5f71af85-cbb0-48ac-84f3-97083bf16367',
        'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb966',
        'state' => 'queued',
        'payload' => ['action' => ServiceQueueEntityActions::CREATE],
      ],
      [
        'id' => '124',
        'entity_uuid' => '5f71af85-cbb0-48ac-84f3-97083bf16368',
        'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb967',
        'state' => 'queued',
        'payload' => ['action' => ServiceQueueEntityActions::UPDATE],
      ],
    ];

    $this->dispatcher->dispatch(
      Argument::type(ServiceQueueItemsProcessingFinishedEvent::class),
      AcquiaContentHubEvents::SERVICE_QUEUE_ITEMS_PROCESSING_FINISHED
    )->shouldBeCalledOnce();
    $this->dispatcher->dispatch(
      Argument::type(ServiceQueueItemsFailedEvent::class),
      AcquiaContentHubEvents::SERVICE_QUEUE_ITEMS_FAILED
    )->shouldNotBeCalled();

    $this->client->confirmProcessedQueueItems(
      ['5f71af85-cbb0-48ac-84f3-97083bf16367', '5f71af85-cbb0-48ac-84f3-97083bf16368'],
      []
    )->shouldBeCalledOnce()->willReturn(['success' => TRUE]);

    $this->logger->info('Processed queue items: @count items (@uuids)', [
      '@count' => 2,
      '@uuids' => '5f71af85-cbb0-48ac-84f3-97083bf16367, 5f71af85-cbb0-48ac-84f3-97083bf16368',
    ])->shouldBeCalledOnce();

    $this->logger->error(Argument::any(), Argument::any())->shouldNotBeCalled();

    $this->sut->processQueueItems($entities_data);
  }

  /**
   * @covers ::processQueueItems
   */
  public function testProcessQueueItemsFailure(): void {
    $entities_data = [
      [
        'id' => '123',
        'entity_uuid' => '5f71af85-cbb0-48ac-84f3-97083bf16367',
        'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb966',
        'state' => 'queued',
        'payload' => ['action' => ServiceQueueEntityActions::CREATE],
      ],
      [
        'id' => '124',
        'entity_uuid' => '5f71af85-cbb0-48ac-84f3-97083bf16368',
        'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb967',
        'state' => 'queued',
        'payload' => ['action' => 'entity_archive'],
      ],
    ];

    $this->dispatcher->dispatch(
      Argument::type(ServiceQueueItemsProcessingFinishedEvent::class),
      AcquiaContentHubEvents::SERVICE_QUEUE_ITEMS_PROCESSING_FINISHED
    )->shouldBeCalledOnce();
    $this->dispatcher->dispatch(
      Argument::type(ServiceQueueItemsFailedEvent::class),
      AcquiaContentHubEvents::SERVICE_QUEUE_ITEMS_FAILED
    )->shouldBeCalledOnce();

    $this->client->confirmProcessedQueueItems(
      ['5f71af85-cbb0-48ac-84f3-97083bf16367'],
      ['5f71af85-cbb0-48ac-84f3-97083bf16368']
    )->shouldBeCalledOnce()->willReturn(['success' => TRUE]);

    $this->logger->info('Processed queue items: @count items (@uuids)', [
      '@count' => 1,
      '@uuids' => '5f71af85-cbb0-48ac-84f3-97083bf16367',
    ])->shouldBeCalledOnce();

    $this->logger->error('Failed queue item @uuid: @reason', [
      '@uuid' => '5f71af85-cbb0-48ac-84f3-97083bf16368',
      '@reason' => 'Could not process queue item; associated handler not found for "entity_archive" action.',
    ])->shouldBeCalledOnce();

    $this->sut->processQueueItems($entities_data);
  }

  /**
   * @covers ::handleProcessedQueueItems
   */
  public function testHandleProcessedQueueItems(): void {
    $queue_item_data_1 = [
      'id' => '123',
      'entity_uuid' => '5f71af85-cbb0-48ac-84f3-97083bf16367',
      'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb966',
      'state' => 'queued',
      'payload' => ['action' => ServiceQueueEntityActions::CREATE],
      'visible_at' => '',
      'created_at' => '',
      'updated_at' => '',
    ];

    $queue_item_data_2 = [
      'id' => '124',
      'entity_uuid' => '5f71af85-cbb0-48ac-84f3-97083bf16368',
      'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb967',
      'state' => 'queued',
      'payload' => ['action' => ServiceQueueEntityActions::UPDATE],
      'visible_at' => '',
      'created_at' => '',
      'updated_at' => '',
    ];

    $queue_item_1 = QueueItem::fromArray($queue_item_data_1);
    $queue_item_2 = QueueItem::fromArray($queue_item_data_2);

    $processed_queue_items = [$queue_item_1, $queue_item_2];

    $this->dispatcher->dispatch(
      Argument::type(ServiceQueueItemsProcessingFinishedEvent::class),
      AcquiaContentHubEvents::SERVICE_QUEUE_ITEMS_PROCESSING_FINISHED
    )->shouldBeCalledOnce();

    $result = $this->sut->handleProcessedQueueItems($processed_queue_items);

    $this->assertInstanceOf(ServiceQueueItemsProcessingFinishedEvent::class, $result);
  }

  /**
   * @covers ::handleFailedQueueItems
   */
  public function testHandleFailedQueueItems(): void {
    $queue_item_data_1 = [
      'id' => '123',
      'entity_uuid' => '5f71af85-cbb0-48ac-84f3-97083bf16367',
      'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb966',
      'state' => 'queued',
      'payload' => ['action' => ServiceQueueEntityActions::CREATE],
      'visible_at' => '',
      'created_at' => '',
      'updated_at' => '',
    ];

    $queue_item_data_2 = [
      'id' => '124',
      'entity_uuid' => '5f71af85-cbb0-48ac-84f3-97083bf16368',
      'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb967',
      'state' => 'queued',
      'payload' => ['action' => ServiceQueueEntityActions::UPDATE],
      'visible_at' => '',
      'created_at' => '',
      'updated_at' => '',
    ];

    $queue_item_1 = QueueItem::fromArray($queue_item_data_1);
    $queue_item_2 = QueueItem::fromArray($queue_item_data_2);

    $failed_queue_items = [
      $queue_item_1->getEntityUuid() => [
        'queue_item' => $queue_item_1,
        'reason' => 'Test failure reason 1',
      ],
      $queue_item_2->getEntityUuid() => [
        'queue_item' => $queue_item_2,
        'reason' => 'Test failure reason 2',
      ],
    ];

    $this->dispatcher->dispatch(
      Argument::type(ServiceQueueItemsFailedEvent::class),
      AcquiaContentHubEvents::SERVICE_QUEUE_ITEMS_FAILED
    )->shouldBeCalledOnce();

    $result = $this->sut->handleFailedQueueItems($failed_queue_items);
    $this->assertInstanceOf(ServiceQueueItemsFailedEvent::class, $result);
  }

  /**
   * Tests processQueueItems when confirmProcessedQueueItems fails.
   *
   * @covers ::processQueueItems
   * @covers ::confirmQueueItems
   */
  public function testProcessQueueItemsConfirmationFailure(): void {
    $entities_data = [
      [
        'id' => '123',
        'entity_uuid' => '5f71af85-cbb0-48ac-84f3-97083bf16367',
        'client_uuid' => '8fb4c61c-bc0c-4451-a1aa-f576bf4eb966',
        'state' => 'queued',
        'payload' => ['action' => ServiceQueueEntityActions::CREATE],
      ],
    ];

    $this->dispatcher->dispatch(
      Argument::type(ServiceQueueItemsProcessingFinishedEvent::class),
      AcquiaContentHubEvents::SERVICE_QUEUE_ITEMS_PROCESSING_FINISHED
    )->shouldBeCalledOnce();
    $this->dispatcher->dispatch(
      Argument::type(ServiceQueueItemsFailedEvent::class),
      AcquiaContentHubEvents::SERVICE_QUEUE_ITEMS_FAILED
    )->shouldNotBeCalled();

    $error_response = [
      'error' => ['message' => 'Service unavailable'],
    ];

    $this->client->confirmProcessedQueueItems(
      ['5f71af85-cbb0-48ac-84f3-97083bf16367'],
      []
    )->shouldBeCalledOnce()->willReturn($error_response);

    $this->logger->error('Failed to confirm queue items status: @error', [
      '@error' => 'Service unavailable',
    ])->shouldBeCalledOnce();
    $this->logger->info(Argument::any(), Argument::any())->shouldNotBeCalled();

    $this->sut->processQueueItems($entities_data);
  }

}
