<?php

namespace Drupal\Tests\acquia_contenthub\Kernel;

use Drupal\acquia_contenthub_subscriber\SubscriberTracker;
use Drupal\Core\Entity\EntityInterface;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\node\Traits\NodeCreationTrait;
use Prophecy\PhpUnit\ProphecyTrait;

/**
 * Tests subscriber tracker methods.
 *
 * @group acquia_contenthub
 *
 * @coversDefaultClass \Drupal\acquia_contenthub_subscriber\SubscriberTracker
 *
 * @requires module depcalc
 *
 * @package Drupal\Tests\acquia_contenthub\Kernel
 */
class SubscriberTrackerTest extends EntityKernelTestBase {

  use NodeCreationTrait;
  use ProphecyTrait;

  /**
   * Import entity tracking Table.
   */
  const IMPORT_TABLE_NAME = 'acquia_contenthub_subscriber_import_tracking';

  /**
   * Queue name.
   */
  const IMPORT_QUEUE_NAME = 'acquia_contenthub_subscriber_import';

  /**
   * The name of the tracking table.
   */
  const IMPORT_TRACKING_TABLE = 'acquia_contenthub_subscriber_import_tracking';

  /**
   * Node 1 object.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected $node1;

  /**
   * Node 2 object.
   *
   * @var \Drupal\node\NodeInterface
   */
  protected $node2;

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

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

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

  /**
   * Queue worker.
   *
   * @var \Drupal\Core\Queue\QueueWorkerInterface
   */
  protected $queueWorker;

  /**
   * {@inheritDoc}
   *
   * @throws \Exception
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('user');
    $this->installSchema('user', ['users_data']);
    $this->installEntitySchema('node');
    $this->installSchema('node', ['node_access']);
    $this->installConfig([
      'field',
      'filter',
      'node',
    ]);
    $this->node1 = $this->createNode();
    $this->node2 = $this->createNode();
    $this->installSchema('acquia_contenthub_subscriber', [SubscriberTracker::IMPORT_TRACKING_TABLE]);
    $this->tracker = $this->container->get('acquia_contenthub_subscriber.tracker');
    $this->tracker->track($this->node2, sha1(json_encode($this->node2->toArray())));

    // Setup queue.
    $queue_factory = $this->container->get('queue');
    $queue_worker_manager = $this->container->get('plugin.manager.queue_worker');
    $this->queueWorker = $queue_worker_manager->createInstance(self::IMPORT_QUEUE_NAME);
    $this->queue = $queue_factory->get(self::IMPORT_QUEUE_NAME);
  }

  /**
   * Tests listing of tracked entities in subscriber tracker.
   *
   * @covers \Drupal\acquia_contenthub_subscriber\SubscriberTracker::listTrackedEntities
   */
  public function testListTrackedEntities(): void {
    $this->tracker->queue($this->node1->uuid());
    // Works with single status.
    $queued_entities = array_column($this->tracker->listTrackedEntities(SubscriberTracker::QUEUED), 'entity_uuid');
    $this->assertContains($this->node1->uuid(), $queued_entities);

    $tracked_entities = array_column($this->tracker->listTrackedEntities(SubscriberTracker::IMPORTED), 'entity_uuid');
    $this->assertContains($this->node2->uuid(), $tracked_entities);

    // Works with multiple statuses.
    $queued_tracked_entities = array_column($this->tracker->listTrackedEntities([
      SubscriberTracker::QUEUED,
      SubscriberTracker::IMPORTED,
    ]), 'entity_uuid');
    $this->assertContains($this->node1->uuid(), $queued_tracked_entities);
    $this->assertContains($this->node2->uuid(), $queued_tracked_entities);
  }

  /**
   * Test case to nullify hashes.
   *
   * @covers \Drupal\acquia_contenthub_subscriber\SubscriberTracker::nullifyHashes
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   * @throws \Exception
   */
  public function testNullifyHashes(): void {
    while ($item = $this->queue->claimItem()) {
      $this->queueWorker->processItem($item->data);
    }

    $hash_before = $this->getTrackingTableColByUuid($this->node2->uuid(), 'hash');
    $this->assertNotEmpty($hash_before);

    $status = $this->getTrackingTableColByUuid($this->node2->uuid(), 'status');
    // Nullifies hashes in the Subscriber Tracker.
    $this->tracker->nullifyHashes([$status], ['node'], [$this->node2->uuid()]);

    $hash_after = $this->getTrackingTableColByUuid($this->node2->uuid(), 'hash');
    $this->assertEmpty($hash_after);
  }

  /**
   * Tests fetching of multiple entities by uuid and hash.
   *
   * @covers \Drupal\acquia_contenthub_subscriber\SubscriberTracker::getEntitiesByRemoteIdAndHash
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   * @throws \Exception
   */
  public function testGetEntitiesByRemoteIdAndHash(): void {
    $hash1 = sha1(json_encode($this->node1->toArray()));
    $hash2 = sha1(json_encode($this->node2->toArray()));
    $dummy_entity = $this->prophesize(EntityInterface::class);
    $dummy_entity->uuid()->willReturn('invalid-uuid');
    $dummy_entity->getEntityTypeId()->willReturn('node');
    $dummy_entity->id()->willReturn('invalid-id');
    $invalid_hash = sha1(json_encode(['invalid-entity']));
    $this->tracker->track($this->node1, $hash1);
    $this->tracker->track($this->node2, $hash2);
    $this->tracker->track($dummy_entity->reveal(), $invalid_hash);

    $uuid1 = $this->node1->uuid();
    $uuid2 = $this->node2->uuid();
    $invalid_uuid = 'invalid-uuid';
    $entities = $this->tracker->getEntitiesByRemoteIdAndHash([
      $uuid1 => $hash1,
      $uuid2 => $hash2,
      $invalid_uuid => $invalid_hash,
    ]);

    $this->assertCount(2, $entities);
    $this->assertArrayHasKey($uuid1, $entities);
    $this->assertArrayHasKey($uuid2, $entities);
    $this->assertSame($this->node1->getTitle(), $entities[$uuid1]->getTitle());
    $this->assertSame($this->node2->getTitle(), $entities[$uuid2]->getTitle());
  }

  /**
   * Tests deletion of multiple entities by uuid.
   *
   * @covers \Drupal\acquia_contenthub_subscriber\SubscriberTracker::deleteMultipleByUuids
   */
  public function testDeleteMultipleByUuids(): void {
    $hash1 = sha1(json_encode($this->node1->toArray()));
    $hash2 = sha1(json_encode($this->node2->toArray()));
    $this->tracker->track($this->node1, $hash1);
    $this->tracker->track($this->node2, $hash2);

    $uuids = [$this->node1->uuid(), $this->node2->uuid()];
    $is_tracked = $this->tracker->isTracked($uuids);
    $this->assertEquals(TRUE, $is_tracked, 'Entities should be tracked');
    $this->tracker->deleteMultipleByUuids($uuids);
    $is_tracked = $this->tracker->isTracked($uuids);
    $this->assertEquals(FALSE, $is_tracked, 'Entities should not be tracked');
  }

  /**
   * Tests tracking for multiple entities in a self::QUEUED state.
   *
   * @covers \Drupal\acquia_contenthub_subscriber\SubscriberTracker::queueMultiple
   */
  public function testQueueMultiple(): void {
    $values = [
      [
        'entity_uuid' => $this->node1->uuid(),
        'hash' => sha1(json_encode($this->node1->toArray())),
      ],
      [
        'entity_uuid' => $this->node2->uuid(),
        'hash' => sha1(json_encode($this->node2->toArray())),
      ],
    ];

    $this->assertEmpty($this->getTrackingTableColByUuid($this->node1->uuid()), 'Initially uuid cannot be tracked');
    $this->assertNotEmpty($this->getTrackingTableColByUuid($this->node2->uuid()), 'Initially uuid can be tracked');
    $time = date('c');
    $this->tracker->queueMultiple($values);

    $this->assertEquals($this->node1->uuid(), $this->getTrackingTableColByUuid($this->node1->uuid()), 'UUID should match');
    $this->assertEquals($time, $this->getTrackingTableColByUuid($this->node1->uuid(), 'first_imported'), 'First imported date should match');
    $this->assertEquals($this->node2->uuid(), $this->getTrackingTableColByUuid($this->node2->uuid()), 'UUID should match');

    $date = $this->getTrackingTableColByUuid($this->node1->uuid(), 'first_imported');
    $values = [
      [
        'entity_uuid' => $this->node1->uuid(),
        'hash' => sha1(json_encode($this->node2->toArray())),
      ],
    ];
    $this->tracker->queueMultiple($values);
    $this->assertEquals($date, $this->getTrackingTableColByUuid($this->node1->uuid(), 'first_imported'), 'First imported date should match');

    $values = [];
    $this->expectException("Exception");
    $this->expectExceptionMessage('Can not insert/update empty values');
    $this->tracker->queueMultiple($values);
  }

  /**
   * Tests fetching of multiple statuses by UUIDs.
   *
   * @covers \Drupal\acquia_contenthub_subscriber\SubscriberTracker::getStatusesByUuids
   */
  public function testGetStatusesByUuids(): void {
    $this->tracker->queue($this->node1->uuid());
    $this->tracker->track($this->node2, sha1(json_encode($this->node2->toArray())));

    // Test with multiple UUIDs.
    $uuids = [$this->node1->uuid(), $this->node2->uuid()];
    $statuses = $this->tracker->getStatusesByUuids($uuids);
    $this->assertCount(2, $statuses);
    $this->assertArrayHasKey($this->node1->uuid(), $statuses);
    $this->assertArrayHasKey($this->node2->uuid(), $statuses);
    $this->assertEquals(SubscriberTracker::QUEUED, $statuses[$this->node1->uuid()]);
    $this->assertEquals(SubscriberTracker::IMPORTED, $statuses[$this->node2->uuid()]);

    // Test with single UUID.
    $single_status = $this->tracker->getStatusesByUuids([$this->node1->uuid()]);
    $this->assertCount(1, $single_status);
    $this->assertEquals(SubscriberTracker::QUEUED, $single_status[$this->node1->uuid()]);

    // Test with non-existent UUID.
    $non_existent_uuid = 'non-existent-uuid';
    $empty_statuses = $this->tracker->getStatusesByUuids([$non_existent_uuid]);
    $this->assertEmpty($empty_statuses);

    // Test with empty array.
    $empty_result = $this->tracker->getStatusesByUuids([]);
    $this->assertEmpty($empty_result);
  }

  /**
   * Fetch tracking table column for a given uuid.
   *
   * @param string $entity_uuid
   *   Entity Id.
   * @param string $col_name
   *   Column name.
   *
   * @return string|bool
   *   The tracking table respective data.
   */
  protected function getTrackingTableColByUuid(string $entity_uuid, string $col_name = 'entity_uuid'): ?string {
    $query = \Drupal::database()->select(self::IMPORT_TABLE_NAME, 't');
    $query->fields('t', [$col_name]);
    $query->condition('entity_uuid', $entity_uuid);

    return $query->execute()->fetchField();
  }

}
