<?php

namespace Drupal\Tests\acquia_contenthub\Kernel;

use Acquia\ContentHubClient\ContentHubClient;
use Acquia\ContentHubClient\ContentHubLoggingClient;
use Acquia\ContentHubClient\Settings as AcquiaSettings;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub\Client\ProjectVersionClient;
use Drupal\acquia_contenthub\Exception\EventServiceUnreachableException;
use Drupal\Core\Site\Settings;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\acquia_contenthub\Kernel\Traits\AcquiaContentHubAdminSettingsTrait;
use Drupal\Tests\acquia_contenthub\Kernel\Traits\WatchdogAssertsTrait;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use Prophecy\Argument;

/**
 * Tests the client factory.
 *
 * @group acquia_contenthub
 *
 * @coversDefaultClass \Drupal\acquia_contenthub\Client\ClientFactory
 *
 * @package Drupal\Tests\acquia_contenthub\Kernel
 */
class ClientFactoryTest extends EntityKernelTestBase {

  use AcquiaContentHubAdminSettingsTrait;
  use WatchdogAssertsTrait;

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

  /**
   * {@inheritDoc}
   */
  protected function setup(): void {
    parent::setUp();
    $pv_client = $this->prophesize(ProjectVersionClient::class);
    $pv_client
      ->getContentHubReleases()
      ->willReturn(['latest' => '8.x-2.25']);
    $pv_client
      ->getDrupalReleases(Argument::any())
      ->willReturn(['also_available' => '9.2.1', 'latest' => '8.9.16']);
    $this->container->set('acquia_contenthub.project_version_client', $pv_client->reveal());
  }

  /**
   * Test case when content hub configured via core config or UI.
   *
   * @param string $name
   *   Client name.
   * @param string $uuid
   *   Client UUID.
   * @param string $api_key
   *   API Key.
   * @param string $secret_key
   *   Secret Key.
   * @param string $url
   *   Hostname.
   * @param string $shared_secret
   *   Shared secret key.
   *
   * @dataProvider settingsDataProvider
   *
   * @see GetSettingsFromCoreConfig
   *
   * @throws \Exception
   */
  public function testGetClientConfiguredByCoreConfig(string $name, string $uuid, string $api_key, string $secret_key, string $url, string $shared_secret): void {
    $ch_settings = [
      'client_name' => $name,
      'origin' => $uuid,
      'api_key' => $api_key,
      'secret_key' => $secret_key,
      'hostname' => $url,
      'shared_secret' => $shared_secret,
    ];
    $this->createAcquiaContentHubAdminSettings($ch_settings);

    /** @var \Drupal\acquia_contenthub\Client\ClientFactory $clientFactory */
    $clientFactory = $this->container->get('acquia_contenthub.client.factory');
    $settings = $clientFactory->getClient()->getSettings();

    // Check all values.
    $this->assertEquals($settings->getName(), $name);
    $this->assertEquals($settings->getUuid(), $uuid);
    $this->assertEquals($settings->getApiKey(), $api_key);
    $this->assertEquals($settings->getSecretKey(), $secret_key);
    $this->assertEquals($settings->getUrl(), $url);
    $this->assertEquals($settings->getSharedSecret(), $shared_secret);
  }

  /**
   * Test case when content hub configured via system settings.
   *
   * @param string $name
   *   Client name.
   * @param string $uuid
   *   Client UUID.
   * @param string $api_key
   *   API Key.
   * @param string $secret_key
   *   Secret Key.
   * @param string $url
   *   Hostname.
   * @param string $shared_secret
   *   Shared secret key.
   *
   * @dataProvider settingsDataProvider
   *
   * @throws \ReflectionException
   */
  public function testGetClientConfiguredBySettings(string $name, string $uuid, string $api_key, string $secret_key, string $url, string $shared_secret) {
    // Get existing values from settings.php file.
    $system_settings = Settings::getAll();
    // Merge our settings.
    $system_settings['acquia_contenthub.settings'] = new AcquiaSettings(
      $name,
      $uuid,
      $api_key,
      $secret_key,
      $url,
      $shared_secret
    );

    // Re-initialize (update) settings.
    new Settings($system_settings);

    /** @var \Drupal\acquia_contenthub\Client\ClientFactory $clientFactory */
    $clientFactory = $this->container->get('acquia_contenthub.client.factory');
    $settings = $clientFactory->getClient()->getSettings();
    $this->assertEquals($settings->getName(), $name);
    $this->assertEquals($settings->getUuid(), $uuid);
    $this->assertEquals($settings->getApiKey(), $api_key);
    $this->assertEquals($settings->getSecretKey(), $secret_key);
    $this->assertEquals($settings->getUrl(), $url);
    $this->assertEquals($settings->getSharedSecret(), $shared_secret);
  }

  /**
   * Test case to verify event logging url.
   *
   * @covers ::getLoggingUrl
   *
   * @throws \Exception
   */
  public function testLoggingUrl() {
    $event_service_url = 'https://event-service.com';
    $this->createAcquiaContentHubAdminSettings(['event_service_url' => $event_service_url]);

    /** @var \Drupal\acquia_contenthub\Client\ClientFactory $client_factory */
    $client_factory = $this->container->get('acquia_contenthub.client.factory');

    $output = $client_factory->getLoggingUrl();
    $this->assertEquals($event_service_url, $output);
  }

  /**
   * Test case to verify event logging url exception.
   *
   * @param string $name
   *   Client name.
   * @param string $uuid
   *   Client UUID.
   * @param string $api_key
   *   API Key.
   * @param string $secret_key
   *   Secret Key.
   * @param string $url
   *   Hostname.
   * @param string $shared_secret
   *   Shared secret key.
   *
   * @covers ::getEventLoggingUrlFromRemoteSettings
   *
   * @dataProvider settingsDataProvider
   *
   * @throws \Exception
   */
  public function testGetEventLoggingUrlFromRemoteSettings(string $name, string $uuid, string $api_key, string $secret_key, string $url, string $shared_secret) {
    $this->enableModules(['dblog']);
    $this->installSchema('dblog', 'watchdog');
    $client = $this->prophesize(ContentHubClient::class);
    $client->getRemoteSettings()->willReturn([]);

    /** @var \Drupal\acquia_contenthub\Client\ClientFactory $client_factory */
    $client_factory = $this->container->get('acquia_contenthub.client.factory');
    $this->assertEquals('', $client_factory->getEventLoggingUrlFromRemoteSettings(),
      'If client is not set, empty string should return');

    $client_factory = $this->getMockBuilder(ClientFactory::class)
      ->setConstructorArgs([
        $this->container->get('event_dispatcher'),
        $this->container->get('acquia_contenthub.logger_channel'),
        $this->container->get('extension.list.module'),
        $this->container->get('acquia_contenthub.configuration'),
        $this->container->get('psr7.http_message_factory'),
        $this->container->get('acquia_contenthub.client_metadata_builder'),
      ])
      ->onlyMethods(['getClient'])
      ->getMock();
    $client_factory->method('getClient')
      ->willReturn($client->reveal());
    $this->container->set('acquia_contenthub.client.factory', $client_factory);

    $client_factory->getEventLoggingUrlFromRemoteSettings();
    $this->assertLogMessage('acquia_contenthub',
      'Event logging service url not found in remote settings.'
    );
  }

  /**
   * Test case to verify event logging url exception.
   *
   * @throws \Exception
   */
  public function testSaveAchAdminSettingsConfig() {
    $event_logging_url = 'https://example.com';

    /** @var \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ach_configuration */
    $ach_configuration = $this->container->get('acquia_contenthub.configuration');
    $ch_connection = $ach_configuration->getConnectionDetails();
    $this->assertEmpty($ch_connection->getEventServiceUrl());

    $ch_connection->setEventServiceUrl($event_logging_url);
    $this->assertEquals($event_logging_url, $ch_connection->getEventServiceUrl());
  }

  /**
   * Provides sample data for client's settings.
   *
   * @return array
   *   Settings.
   */
  public static function settingsDataProvider(): array {
    return [
      [
        'test-client',
        '00000000-0000-0001-0000-123456789123',
        '12312321312321',
        '12312321312321',
        'https://dev.content-hub.dev',
        '12312321312321',
        'https://example.com',
      ],
    ];
  }

  /**
   * Test case to check if event microservice is unreachable.
   *
   * For vague event logging url.
   *
   * @throws \Exception
   */
  public function testEventServiceNotReachable(): void {
    $this->createAcquiaContentHubAdminSettings();
    $error_msg = 'Error during contacting Event Micro Service: Client error:';
    $client_factory = $this->getMockBuilder(ClientFactory::class)
      ->setConstructorArgs([
        $this->container->get('event_dispatcher'),
        $this->container->get('acquia_contenthub.logger_channel'),
        $this->container->get('extension.list.module'),
        $this->container->get('acquia_contenthub.configuration'),
        $this->container->get('psr7.http_message_factory'),
        $this->container->get('acquia_contenthub.client_metadata_builder'),
      ])
      ->onlyMethods(['checkLoggingClient', 'getLoggingUrl'])
      ->getMock();
    $client_factory->method('getLoggingUrl')
      ->willReturn('');
    $client_factory->method('checkLoggingClient')
      ->with($this->anything())
      ->willThrowException(new EventServiceUnreachableException($error_msg));
    $this->container->set('acquia_contenthub.client.factory', $client_factory);

    /** @var \Drupal\acquia_contenthub\Client\ClientFactory $client_factory */
    $client_factory = $this->container->get('acquia_contenthub.client.factory');
    $this->assertNull($client_factory->getLoggingClient());
  }

  /**
   * Tests check logging client method.
   *
   * To assert event service is reachable or not.
   *
   * @covers ::checkLoggingClient
   */
  public function testCheckLoggingClient(): void {
    /** @var \Drupal\acquia_contenthub\Client\ClientFactory $client_factory */
    $client_factory = $this->container->get('acquia_contenthub.client.factory');
    $logging_client = $this->prophesize(ContentHubLoggingClient::class);
    $logging_client
      ->ping()
      ->shouldBeCalled()
      ->willThrow(new ClientException('', $this->prophesize(Request::class)->reveal(), new Response(404)));
    $this->expectException(EventServiceUnreachableException::class);
    $this->expectExceptionMessage('Error during contacting Event Micro Service:');
    $client_factory->checkLoggingClient($logging_client->reveal());
    $logging_client = $this->prophesize(ContentHubLoggingClient::class);
    $logging_client
      ->ping()
      ->shouldBeCalled()
      ->willReturn(new Response());
    $client_factory->checkLoggingClient($logging_client->reveal());
    // Empty assertion which means no exception was raised.
    $this->addToAssertionCount(1);
  }

  /**
   * Tests checkLoggingClient with logging client not set.
   *
   * @covers ::checkLoggingClient
   *
   * @throws \Exception
   */
  public function testLoggingClientNotSet(): void {
    /** @var \Drupal\acquia_contenthub\Client\ClientFactory $client_factory */
    $client_factory = $this->container->get('acquia_contenthub.client.factory');
    $this->expectException(\RuntimeException::class);
    $this->expectExceptionMessage('Content Hub Logging Client is not configured.');
    $client_factory->checkLoggingClient();
  }

}
