<?php

namespace Drupal\Tests\acquia_contenthub\Kernel\MetaData;

use Acquia\ContentHubClient\ContentHubClient;
use Acquia\ContentHubClient\MetaData\ClientMetaData;
use Acquia\ContentHubClient\Settings;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub\Client\ClientMetaDataManager;
use Drupal\acquia_contenthub\Exception\ContentHubClientException;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\acquia_contenthub\Unit\Helpers\LoggerMock;
use Prophecy\Argument;

/**
 * Tests client metadata manager.
 *
 * @coversDefaultClass \Drupal\acquia_contenthub\Client\ClientMetaDataManager
 *
 * @package acquia_contenthub
 */
class ClientMetaDataManagerTest extends KernelTestBase {

  /**
   * Test client uuid.
   */
  public const CLIENT_UUID = 'client-uuid';

  /**
   * Client metadata array.
   *
   * @var \Acquia\ContentHubClient\MetaData\ClientMetaData
   */
  public static ClientMetaData $metaData;

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

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

  /**
   * Logger mock.
   *
   * @var \Drupal\Tests\acquia_contenthub\Unit\Helpers\LoggerMock
   */
  protected LoggerMock $loggerMock;

  /**
   * SUT.
   *
   * @var \Drupal\acquia_contenthub\Client\ClientMetaDataManager
   */
  protected ClientMetaDataManager $sut;

  /**
   * Acquia Content Hub cache bin.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cache;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'acquia_contenthub',
    'acquia_contenthub_client_metadata_test',
    'depcalc',
    'user',
    'system',
  ];

  /**
   * {@inheritDoc}
   */
  public function setUp(): void {
    parent::setUp();
    $this->chClient = $this->prophesize(ContentHubClient::class);
    $this->clientFactory = $this->prophesize(ClientFactory::class);
    $this->clientFactory
      ->getClient()
      ->shouldBeCalled()
      ->willReturn($this->chClient->reveal());
    $this->container->set('acquia_contenthub.client.factory', $this->clientFactory->reveal());
    $this->loggerMock = new LoggerMock();
    $this->container->set('acquia_contenthub.logger_channel', $this->loggerMock);
    $this->cache = $this->container->get('cache.acquia_contenthub');
  }

  /**
   * Mocks settings endpoint.
   */
  protected function mockClient(array $remote_client = []): void {
    $this->chClient
      ->getSettings()
      ->shouldBeCalled()
      ->willReturn(new Settings(
        'client-name',
        self::CLIENT_UUID,
        'api-key',
        'secret-key',
        'https://ch-dev.acquia.com',
        'shared-secret',
        []
      ));
    $this->chClient
      ->getClientByUuid(self::CLIENT_UUID)
      ->shouldBeCalled()
      ->willReturn($remote_client);
  }

  /**
   * Tests client metadata updates with no metadata present in service.
   *
   * @covers ::updateClientMetadata
   *
   * @throws \Exception
   */
  public function testUpdateClientMetaDataWithNewMetaData(): void {
    $this->mockClient(
    [
      'name' => 'client-name',
      'uuid' => self::CLIENT_UUID,
      'metadata' => NULL,
    ]);
    $this->chClient
      ->updateClient(self::CLIENT_UUID, NULL, Argument::type(ClientMetaData::class))
      ->shouldBeCalled()
      ->will(function ($args) {
        self::$metaData = $args[2];
      });
    $this->sut = $this->container->get('acquia_contenthub.client_metadata_manager');
    $this->assertTrue($this->sut->updateClientMetadata());
    $local_metadata = $this->container->get('acquia_contenthub.client_metadata_builder')->buildClientMetadata();
    $this->assertEquals($local_metadata->toArray(), self::$metaData->toArray());
    $this->assertNotEmpty($this->loggerMock->getInfoMessages());
    $log_message = sprintf('Client metadata has been updated. Metadata: %s', json_encode($local_metadata->toArray()));
    $this->assertEquals($log_message, $this->loggerMock->getInfoMessages()[0]);
    $this->assertArrayHasKey('api_key', self::$metaData->toArray()['config']);
    $this->assertEquals('client-api-key', self::$metaData->toArray()['config']['api_key']);
    $this->assertEquals($local_metadata->toArray(), $this->cache->get('client-uuid:remote_metadata')->data, 'Remote metdata has been cached after update.');
  }

  /**
   * Tests that no updates are sent if metadata is same in service.
   *
   * @covers ::updateClientMetadata
   *
   * @throws \Exception
   */
  public function testUpdateClientMetaDataWithExistingMetaData(): void {
    $this->mockClient([
      'name' => 'client-name',
      'uuid' => self::CLIENT_UUID,
      'metadata' => $this->container->get('acquia_contenthub.client_metadata_builder')->buildClientMetadata()->toArray(),
    ]);
    $this->chClient
      ->updateClient(self::CLIENT_UUID, NULL, Argument::type(ClientMetaData::class))
      ->shouldNotBeCalled();
    $this->sut = $this->container->get('acquia_contenthub.client_metadata_manager');
    $this->assertFalse($this->sut->updateClientMetadata());
    $this->assertEmpty($this->loggerMock->getInfoMessages());
  }

  /**
   * Tests remote metadata retrieval from cache.
   *
   * @covers ::getRemoteMetaData
   */
  public function testRemoteDataRetrievalFromCache(): void {
    $metadata = ['client' => 'drupal'];
    $this->cache->set(self::CLIENT_UUID . ':remote_metadata', $metadata, time() + 3600);
    $this->sut = $this->container->get('acquia_contenthub.client_metadata_manager');
    $remote_metadata = $this->sut->getRemoteMetaData(self::CLIENT_UUID);
    $this->chClient
      ->getClientByUuid(self::CLIENT_UUID)
      ->shouldNotBeCalled();
    $this->assertEquals($metadata, $remote_metadata);
  }

  /**
   * Tests that exception is thrown when client is not available.
   *
   * @throws \Exception
   */
  public function testExceptionOnClientConnectionUnavailability(): void {
    $this->clientFactory
      ->getClient()
      ->shouldBeCalled()
      ->willReturn(FALSE);
    $this->sut = $this->container->get('acquia_contenthub.client_metadata_manager');
    $this->expectException(ContentHubClientException::class);
    $this->expectExceptionMessage('Error trying to connect to the Content Hub. Make sure this site is registered to Content hub.');
    $this->sut->updateClientMetadata();
  }

  /**
   * Tests that exception is thrown when remote client is not available.
   *
   * @throws \Exception
   */
  public function testExceptionOnRemoteClientMissing(): void {
    $this->mockClient();
    $this->sut = $this->container->get('acquia_contenthub.client_metadata_manager');
    $this->expectException(\RuntimeException::class);
    $this->expectExceptionMessage(sprintf('Client uuid: %s not available on remote.', self::CLIENT_UUID));
    $this->sut->updateClientMetadata();
  }

}
