<?php

namespace Drupal\Tests\acquia_contenthub\Functional;

use Drupal\acquia_contenthub\Settings\ConnectionDetailsInterface;
use Drupal\acquia_contenthub_server_test\Client\ContentHubClientMock;
use Drupal\acquia_contenthub_test\MockDataProvider;
use Drupal\Tests\acquia_contenthub\Kernel\Traits\AcquiaContentHubAdminSettingsTrait;
use Drupal\Tests\acquia_contenthub\Kernel\Traits\ContentHubClientTestTrait;
use Drupal\Tests\BrowserTestBase;
use Symfony\Component\HttpFoundation\Request;

/**
 * Tests the Content Hub settings form.
 *
 * @coversDefaultClass \Drupal\acquia_contenthub\Form\ContentHubSettingsForm
 *
 * @group acquia_contenthub
 */
class ContentHubSettingsFormTest extends BrowserTestBase {

  use AcquiaContentHubAdminSettingsTrait;
  use ContentHubClientTestTrait;

  /**
   * User that has administer permission.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $authorizedUser;

  /**
   * Anonymous user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $unauthorizedUser;

  /**
   * Path to content hub settings form.
   */
  const CH_SETTINGS_FORM_PATH = '/admin/config/services/acquia-contenthub';

  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'stark';

  /**
   * Content Hub config.
   *
   * @var \Drupal\acquia_contenthub\Settings\ConnectionDetailsInterface
   */
  protected ConnectionDetailsInterface $chConnection;

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

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

    $this->authorizedUser = $this->drupalCreateUser([
      'administer acquia content hub',
    ]);

    $this->unauthorizedUser = $this->drupalCreateUser();
    $this->drupalLogin($this->authorizedUser);

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

  /**
   * Tests permissions of different users.
   *
   * @throws \Behat\Mink\Exception\ExpectationException
   * @throws \Behat\Mink\Exception\ResponseTextException
   */
  public function testContentHubSettingsPagePermissions() {
    $session = $this->assertSession();

    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $session->pageTextContains('Acquia Content Hub Settings');
    $session->statusCodeEquals(200);

    $this->drupalLogout();
    $this->drupalLogin($this->unauthorizedUser);

    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $session->pageTextContains('Access denied');
    $session->statusCodeEquals(403);
  }

  /**
   * Tests whether fields rendered properly.
   *
   * @throws \Behat\Mink\Exception\ElementNotFoundException
   * @throws \Behat\Mink\Exception\ExpectationException
   * @throws \Behat\Mink\Exception\ResponseTextException
   */
  public function testContentHubSettingsFormRenderedProperly() {
    $session = $this->assertSession();

    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $session->fieldExists('Acquia Content Hub Hostname');
    $session->fieldExists('API Key');
    $session->fieldExists('Secret Key');
    $session->fieldExists('Client Name');
    $session->pageTextContains('Send updates to Content Hub Service.');
    $session->pageTextContains('Send Client metrics updates to Content Hub Service.');
    $session->fieldExists('Publicly Accessible URL');
    $session->pageTextContains('Site\'s Origin UUID');
    $session->pageTextContains('Only include one entity per webhook');
    $session->pageTextContains('Syndication Mode');
    $session->pageTextContains('For push syndication, webhooks are a strict requirement. In contrast, for pull syndication, having a valid webhook is not mandatory, as cloud queues will be utilized instead.');
    $session->fieldExists('Push');
    $session->fieldExists('Pull');
    $session->fieldExists('Limit');
    $session->fieldExists('Visibility Timeout');
    $session->buttonExists('Register Site');
    $session->buttonNotExists('Update Client Name');
    $session->buttonNotExists('Update Webhook Settings');
    $session->buttonNotExists('Update Public URL');
    $session->buttonNotExists('Unregister Site');
    $session->buttonNotExists('Update Syndication Settings');
    // Test prefilled webhook field.
    $request = Request::createFromGlobals();
    $session->fieldValueEquals('webhook', $request->getSchemeAndHttpHost());

    $page = $this->getSession()->getPage();
    $value = $page->find('css', '#edit-settings > summary')->getText();
    $this->assertEquals('Connection Settings', $value, 'Account ID is only visible if the site is properly connected to Content Hub.');

    $value = $page->find('css', '.send-contenthub-updates')->getText();
    $this->assertEquals('ENABLED', $value, 'Send_contenthub_updates flag has to be enabled by default');

    $value = $page->find('css', '.send-clientcdf-updates')->getText();
    $this->assertEquals('ENABLED', $value, 'Send_clientcdf_updates flag has to be enabled by default');

    $value = $page->find('css', '#edit-client-details-table tbody tr:nth-child(6)');
    $this->assertNull($value, 'Account ID is only visible if the site is properly connected to Content Hub.');
  }

  /**
   * Tests empty form.
   *
   * @throws \Behat\Mink\Exception\ResponseTextException
   */
  public function testContentHubSettingsPageNoData() {
    $session = $this->assertSession();

    $settings = [
      'webhook' => 'httpp://invalid-url.com',
    ];

    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Register Site');
    $session->pageTextContains('Acquia Content Hub Hostname field is required.');
    $session->pageTextContains('API Key field is required.');
    $session->pageTextContains('Secret Key field is required.');
    $session->pageTextContains('Client Name field is required.');
    $session->pageTextContains('Please type a publicly accessible url.');
  }

  /**
   * Tests the successful registration.
   *
   * @throws \Behat\Mink\Exception\ElementNotFoundException
   * @throws \Behat\Mink\Exception\ExpectationException
   * @throws \Behat\Mink\Exception\ResponseTextException
   */
  public function testContentHubSettingsPageWithValidData() {
    $session = $this->assertSession();

    $settings = [
      'hostname' => MockDataProvider::VALID_HOSTNAME,
      'api_key' => MockDataProvider::VALID_API_KEY,
      'secret_key' => MockDataProvider::VALID_SECRET,
      'client_name' => MockDataProvider::VALID_CLIENT_NAME,
      'webhook' => MockDataProvider::VALID_WEBHOOK_URL,
    ];

    // Successful attempt to register client.
    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Register Site');
    $session->pageTextContains('Site successfully connected to Content Hub. To change connection settings, unregister the site first.');
    $session->statusCodeEquals(200);
    $session->buttonNotExists('Register Site');
    $session->buttonExists('Update Client Name');
    $session->buttonExists('Update Webhook Settings');
    $session->buttonExists('Update Syndication Settings');
    $session->linkExists('Unregister Site');

    // Checks if webhook data is preserved on successful webhook registration.
    $updated_settings = $this->container->get('acquia_contenthub.client.factory')->getClient()->getSettings();
    $this->assertSame($settings['webhook'], $updated_settings->getWebhook());

    $page = $this->getSession()->getPage();
    $value = $page->find('css', '#edit-settings > summary')->getText();
    $this->assertEquals('Connection Settings (CONTENTHUB_DEV)', $value);

    $value = $page->find('css', '#edit-client-details-table tbody tr:nth-child(6)')->getText();
    $this->assertStringContainsString('Account ID', $value);
    $this->assertStringContainsString('CONTENTHUB_DEV', $value);
  }

  /**
   * Tests values of syndication_mode in different scenarios.
   *
   * @throws \Behat\Mink\Exception\ElementNotFoundException
   * @throws \Behat\Mink\Exception\ExpectationException
   * @throws \Behat\Mink\Exception\ResponseTextException
   */
  public function testContentHubSettingsPageForSyndicationSettings() {
    $session = $this->assertSession();

    $settings = [
      'hostname' => MockDataProvider::VALID_HOSTNAME,
      'api_key' => MockDataProvider::VALID_API_KEY,
      'secret_key' => MockDataProvider::VALID_SECRET,
      'client_name' => MockDataProvider::VALID_CLIENT_NAME,
      'webhook' => MockDataProvider::VALID_WEBHOOK_URL,
    ];

    // Successful attempt to register client.
    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Register Site');
    $session->pageTextContains('Site successfully connected to Content Hub. To change connection settings, unregister the site first.');

    $config = $this->config('acquia_contenthub.admin_settings');
    $this->assertEquals('push', $config->get('syndication_mode'), 'Syndication mode should be push after initial registration.');

    $page = $this->getSession()->getPage();
    $page->selectFieldOption('syndication_mode', 'pull');
    $page->pressButton('Update Syndication Settings');
    $session->fieldValueEquals('syndication_mode', 'pull');
    $session->fieldValueEquals('limit', 50);
    $session->fieldValueEquals('visibility_timeout', '24h');

    $page->fillField('limit', 'invalid type');
    $page->pressButton('Update Syndication Settings');
    $session->pageTextContains('Limit must be a number.');

    $page->fillField('visibility_timeout', '337h');
    $page->pressButton('Update Syndication Settings');
    $session->pageTextContains('Visibility timeout cannot exceed 336h.');

    $page->fillField('visibility_timeout', '15');
    $page->pressButton('Update Syndication Settings');
    $session->pageTextContains('Invalid visibility timeout format. Must be suffixed with duration unit. E.g. 3600s, 60m, 1h. Valid units : ns, us, µs, ms, s, m, h');
  }

  /**
   * Tests when an exception occurs during the check.
   *
   * @throws \Behat\Mink\Exception\ElementNotFoundException
   * @throws \Behat\Mink\Exception\ExpectationException
   * @throws \Behat\Mink\Exception\ResponseTextException
   */
  public function testContentHubSettingsPageWithValidDataWhenExceptionOccurs() {
    $session = $this->assertSession();
    $this->createAcquiaContentHubAdminSettings();

    $this->drupalGet(self::CH_SETTINGS_FORM_PATH, [], [
      ContentHubClientMock::X_ACH_TEST_EXECUTION_RESULT => 'exception',
      ContentHubClientMock::X_ACH_TEST_EXECUTION_MSG => 'The request is failed due to some error',
    ]);
    $session->pageTextContains('Your client is not registered to Content Hub');
    $session->statusCodeEquals(200);
    $session->buttonExists('Register Site');
    $session->buttonNotExists('Update Client Name');
    $session->buttonNotExists('Update Webhook Settings');
    $session->buttonNotExists('Update Public URL');
    $session->buttonNotExists('Update Syndication Settings');
    $session->linkNotExists('Unregister Site');
  }

  /**
   * Tests different scenarios for Content Hub Settings Form.
   *
   * @throws \Behat\Mink\Exception\ElementNotFoundException
   * @throws \Behat\Mink\Exception\ExpectationException
   * @throws \Behat\Mink\Exception\ResponseTextException
   */
  public function testContentHubSettingsPageWithPartiallyValidData() {
    $session = $this->assertSession();

    $settings = [
      'hostname' => MockDataProvider::VALID_HOSTNAME,
      'api_key' => MockDataProvider::VALID_API_KEY,
      'secret_key' => MockDataProvider::VALID_SECRET,
      'client_name' => MockDataProvider::VALID_CLIENT_NAME,
      'webhook' => 'http://invalid-url.com',
    ];

    // Successful attempt to register client, but webhook url is unreachable.
    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Register Site');
    $session->pageTextContains('Site successfully connected to Content Hub. To change connection settings, unregister the site first.');
    $session->statusCodeEquals(200);

    $session->buttonNotExists('Register Site');
    $session->buttonExists('Update Client Name');
    $session->buttonExists('Update Webhook Settings');
    $session->buttonExists('Update Syndication Settings');
    $session->linkExists('Unregister Site');

    // Failed attempt to update url.
    $settings = ['webhook' => MockDataProvider::ALREADY_REGISTERED_WEBHOOK];
    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Update Webhook Settings');
    $session->pageTextContains('This webhook is already being used.');
    $session->pageTextContains('Please insert another one, or unregister the existing one first.');

    // Successful attempt to update url.
    $settings = ['webhook' => MockDataProvider::VALID_WEBHOOK_URL];
    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Update Webhook Settings');
    $session->pageTextContains('Site successfully connected to Content Hub. To change connection settings, unregister the site first.');
    $session->pageTextContains('Successfully updated Webhook Settings.');

    // Successful attempt to update webhook version.
    $settings = ['use_webhook_v1' => TRUE];
    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Update Webhook Settings');
    $session->pageTextContains('Site successfully connected to Content Hub. To change connection settings, unregister the site first.');
    $session->pageTextContains('Successfully updated Webhook Settings.');
    $session->pageTextContains('Client metadata updated successfully.');

    // Failed attempt to update client name, without making any changes.
    $settings = ['client_name' => $this->chConnection->getClientName()];
    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Update Client Name');
    $session->pageTextContains('Client name unchanged.');
  }

  /**
   * Tests different cases of invalid data provided through the form.
   *
   * @throws \Behat\Mink\Exception\ResponseTextException
   */
  public function testContentHubSettingsPageWithInvalidData() {
    $session = $this->assertSession();

    $settings = [
      'hostname' => 'https://invalid-url.com',
      'api_key' => 'invalid',
      'secret_key' => 'invalid',
      'client_name' => 'test',
      'webhook' => MockDataProvider::VALID_WEBHOOK_URL,
    ];

    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Register Site');
    $session->pageTextContains(sprintf('Could not get authorization from Content Hub to register client %s. Are your credentials inserted correctly?', $settings['client_name']));
    $session->pageTextContains('There is a problem connecting to Acquia Content Hub. Please ensure that your hostname and credentials are correct.');

    $settings['hostname'] = MockDataProvider::VALID_HOSTNAME;
    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Register Site');
    $session->pageTextContains(sprintf('[4001] Not Found: Customer Key %s could not be found.', $settings['api_key']));

    $settings['api_key'] = MockDataProvider::VALID_API_KEY;
    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Register Site');
    $session->pageTextContains('[4001] Signature for the message does not match expected signature for API key.');
  }

  /**
   * Tests CH Form when CH service is not available.
   *
   * @throws \Exception
   */
  public function testContentHubServiceNotAvailable(): void {
    $this->mockContentHubClientAndClientFactory($this->container);

    $session = $this->assertSession();

    $settings = [
      'hostname' => MockDataProvider::VALID_HOSTNAME,
      'api_key' => MockDataProvider::VALID_API_KEY,
      'secret_key' => MockDataProvider::VALID_SECRET,
      'client_name' => 'serviceUnderMaintenance',
      'webhook' => MockDataProvider::VALID_WEBHOOK_URL,
    ];

    $this->drupalGet(self::CH_SETTINGS_FORM_PATH);
    $this->submitForm($settings, 'Register Site');

    $session->pageTextContains('Service is under maintenance.');
  }

}
