<?php

namespace Drupal\acquia_dam\Form;

use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use GuzzleHttp\Exception\TransferException;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Acquia DAM module configuration form.
 */
class AcquiaDamConfigurationForm extends ConfigFormBase {

  /**
   * Client interface.
   *
   * @var \GuzzleHttp\ClientInterface
   */
  protected $httpClient;

  /**
   * DAM auth service.
   *
   * @var \Drupal\acquia_dam\AcquiadamAuthService
   */
  protected $authService;

  /**
   * The field plugin manager service.
   *
   * @var \Drupal\Core\Extension\ModuleHandler
   */
  protected $moduleHandler;

  /**
   * Acquia DAM logger channel.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $damLoggerChannel;

  /**
   * Key repository service.
   *
   * @var \Drupal\key\KeyRepositoryInterface|null
   */
  protected $keyRepository;

  /**
   * State service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * Acquia DAM client factory.
   *
   * @var \Drupal\acquia_dam\Client\AcquiaDamClientFactory
   */
  protected $clientFactory;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    $instance = parent::create($container);

    $instance->httpClient = $container->get('http_client');
    $instance->authService = $container->get('acquia_dam.authentication_service');
    $instance->moduleHandler = $container->get('module_handler');
    $instance->damLoggerChannel = $container->get('logger.channel.acquia_dam');
    // Only inject key repository if the key module is installed.
    if ($container->has('key.repository')) {
      $instance->keyRepository = $container->get('key.repository');
    }
    $instance->state = $container->get('state');
    $instance->clientFactory = $container->get('acquia_dam.client.factory');

    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'acquia_dam_config';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return [
      'acquia_dam.settings',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $config = $this->config('acquia_dam.settings');
    $is_key_module_installed = $this->moduleHandler->moduleExists('key');
    $is_authenticated = $this->authService->isSiteAuthenticated();
    // Get current domain.
    $domain = $this->authService->getDomain();
    $auth_type = $config->get('auth_type', 'api_token');
    $dam_user = $this->clientFactory->getSiteClient()->getUser();

    if ($domain) {
      $form['site_auth'] = [
        '#type' => 'fieldset',
        '#title' => $this->t('Site authentication'),
      ];

      $form['site_auth']['actions']['#type'] = 'actions';

      // If the site is not authenticated, show the authentication link.
      if (!$is_authenticated) {
        $form['site_auth']['desc'] = [
          '#markup' => '<p>' . $this->t('Authenticate your site with an Acquia DAM user account. It is recommended to use an Acquia DAM user account that has only view and download permissions. Follow this <a href=":document" target="_blank">document</a> to generate API access token.', [
            ':document' => 'https://docs.acquia.com/acquia-dam/acquia-dam-integration-drupal#generate-access-token',
          ]) . '</p>',
        ];
      }
      // If the site is authenticated, show the disconnect link.
      else {
        $message = $dam_user != NULL ? $this->t('Site is authenticated with Acquia DAM using user: @username.', ['@username' => $dam_user]) : $this->t('Site authentication failed with Acquia DAM, please update access token.');
        $form['site_auth']['desc'] = [
          '#markup' => '<p>' . $message . '</p>',
        ];
        if ($dam_user != NULL) {
          $form['site_auth']['actions']['authenticate_site'] = [
            '#type' => 'link',
            '#title' => 'Disconnect site',
            '#url' => Url::fromRoute('acquia_dam.disconnect_site')->setAbsolute(),
            '#cache' => [
              'max-age' => 0,
            ],
            '#attributes' => [
              'class' => ['button', 'button--danger'],
            ],
          ];
        }
      }
    }

    $form['configuration'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Acquia DAM configuration details'),
    ];

    if ($dam_user === NULL) {
      $form['configuration']['actions']['#type'] = 'actions';
      $form['configuration']['actions']['submit'] = [
        '#type' => 'submit',
        '#value' => $this->t('Authenticate Site'),
        '#button_type' => 'primary',
      ];
    }

    $is_media_acquiadam_installed = $this->moduleHandler->moduleExists('media_acquiadam');

    // Hide the auth type selection if Media Acquia DAM module is installed.
    $form['configuration']['auth_type'] = [
      '#type' => 'hidden',
      '#value' => $auth_type,
    ];

    // API access token configuration.
    $form['configuration']['access_token'] = [
      '#type' => 'textfield',
      '#title' => $this->t('API access token'),
      '#default_value' => $this->state->get('acquia_dam_token', ''),
      '#description' => $this->t('Enter the Acquia DAM API access token.'),
      '#required' => TRUE,
      '#disabled' => $dam_user === NULL ? FALSE : TRUE,
    ];

    // API Key configuration using Key module
    if ($is_key_module_installed && $this->keyRepository) {
      $key_collection_url = Url::fromRoute('entity.key.collection')->toString();
      $form['configuration']['keymodule'] = [
        '#type' => 'fieldset',
        '#title' => $this->t('Keys settings'),
        '#description' => $this->t('Use keys managed by the key module. <a href=":keys">Manage keys</a>', [
          ':keys' => $key_collection_url,
        ]),
        '#tree' => FALSE,
        '#states' => [
          'visible' => [
            ':input[name="auth_type"]' => ['value' => 'key_based'],
          ],
          'required' => [
            ':input[name="auth_type"]' => ['value' => 'key_based'],
          ],
        ],
        '#disabled' => $is_authenticated,
      ];
      $form['configuration']['keymodule']['key_id'] = [
        '#type' => 'key_select',
        '#title' => $this->t('Acquia DAM credentials key'),
        '#empty_option' => $this->t('- Select -'),
        '#default_value' => $config->get('key_id') ?? '',
        '#description' => $this->t('Select the key that contains your Acquia DAM domain.'),
        '#states' => [
          'visible' => [
            ':input[name="auth_type"]' => ['value' => 'key_based'],
          ],
          'required' => [
            ':input[name="auth_type"]' => ['value' => 'key_based'],
          ],
        ],
        '#disabled' => $is_authenticated,
      ];
    }

    $form['configuration']['domain'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Acquia DAM domain'),
      '#default_value' => $config->get('domain'),
      '#description' => $this->t('Enter your Acquia DAM domain. Permitted domains include *.acquiadam.com or *.widencollective.com (examples: demo.acquiadam.com, yourcompany.widencollective.com)'),
      '#states' => [
        'visible' => [
          ':input[name="auth_type"]' => ['!value' => 'key_based'],
        ],
        'required' => [
          ':input[name="auth_type"]' => ['!value' => 'key_based'],
        ],
      ],
      '#disabled' => $is_authenticated,
    ];

    return $form;
  }

  /**
   * Validate that the provided values are valid or nor.
   *
   * @param array $form
   *   Form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state instance.
   */
  public function validateForm(array &$form, FormStateInterface $form_state): void {
    $auth_type = $form_state->getValue('auth_type') ?? 'api_token';
    $old_token = $this->state->get('acquia_dam_token', '');
    $domain = $token = '';

    // Handle validation for key-based authentication.
    if ($auth_type === 'key_based') {
      $key_id = $form_state->getValue('key_id');

      // Ensure a key is selected.
      if (!$key_id) {
        $form_state->setErrorByName('key_id', $this->t('Please select a valid API key.'));
        return;
      }

      // Retrieve the key and validate its existence.
      $key = $this->keyRepository->getKey($key_id);
      if (!$key) {
        $form_state->setErrorByName('key_id', $this->t('Selected key not found.'));
        return;
      }

      // Extract credentials from the key.
      $credentials = $this->getKeyCredentials($key_id, $auth_type);
      // Get the token from the credentials.
      $token = !empty($credentials['token']) ? $credentials['token'] : '';
      // An assumption is made that domains via Key do not contain a protocol.
      $domain = Xss::filter($credentials['domain'] ?? '');
      // Validate the presence of required credentials.
      if (empty($domain) || empty($token)) {
        $form_state->setErrorByName('key_id', $this->t('Selected key does not contain valid credentials.'));
        return;
      }
    }
    else {
      // Handle validation for non-key-based authentication.
      // Domains should not contain a protocol or trailing slashes.
      $domain = Xss::filter(rtrim(preg_replace('#^https?://#', '', $form_state->getValue('domain')), '/'));
      if (empty($domain)) {
        $form_state->setErrorByName('domain', $this->t('Provided domain is not valid.'));
        return;
      }
      $form_state->setValue('domain', $domain);
    }

    $token = !empty($token) ? $token : $form_state->getValue('access_token');
    // Temporarily store the token in state for validation.
    // The client factory currently retrieves tokens from state or
    // key storage, but neither is available during form validation.
    // This token will be removed after validation completes.
    // @todo Fix when the client factory supports passing tokens directly.
    $this->state->set('acquia_dam_token', $token);
    // Validate the token by attempting to retrieve the username.
    $dam_user = $this->clientFactory->getSiteClient()->getUser();
    if (empty($dam_user)) {
      $form_state->setErrorByName($auth_type == 'key_based' ? 'key_id' : 'access_token', $this->t('The provided API token is not valid. Please verify and try again.'));
      return;
    }
    $this->messenger()->addStatus('API access token validation: OK!');
    // Clean up temporary token from state.
    // Token will be persisted during form submission.
    $this->state->delete('acquia_dam_token');

    // Define endpoints for domain validation.
    $endpoints = [
      'http' => 'http://' . $domain . '/collective.ping',
      'https' => 'https://' . $domain . '/collective.ping',
    ];
    // Validate the domain by pinging the endpoints.
    foreach ($endpoints as $protocol => $endpoint) {
      try {
        $response = $this->httpClient->get($endpoint);
        // Check for a successful response.
        $response->getStatusCode() === 200 ?
          $this->messenger()->addStatus($this->t('Validating domain (@protocol): OK!', ['@protocol' => $protocol]))
          : $form_state->setErrorByName($auth_type == 'key_based' ? 'key_id' : 'domain', $this->t('Validating domain (@protocol): @status', [
          '@protocol' => $protocol,
          '@status' => $response->getStatusCode(),
        ]));
      } catch (TransferException $e) {
        $form_state->setErrorByName($auth_type == 'key_based' ? 'key_id' : 'domain', $this->t('Unable to connect to the domain. Please verify the domain is entered correctly.'));
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $config_to_save = $this->config('acquia_dam.settings');
    $auth_type = $form_state->getValue('auth_type') ?? 'api_token';
    $current_domain = $this->authService->getDomain();
    // If auth type is key_based, we will not use the domain from the config.
    if ($auth_type === 'key_based' && $form_state->getValue('key_id')) {
      $credentials = $this->getKeyCredentials($form_state->getValue('key_id'), $auth_type);
      $new_domain = $credentials['domain'] ?? '';
    }
    else {
      $new_domain = Xss::filter($form_state->getValue('domain'));
    }

    // Handles domain swap when the site is already authenticated.
    if ($new_domain !== $current_domain && $this->authService->isSiteAuthenticated()) {
      try {
        $this->authService->disconnectSiteAndUsers();
      }
      catch (\Exception $exception) {
        $this->messenger()->addMessage('Something went wrong during domain change. Please contact the site administrator for more information.');
        $this->damLoggerChannel->error('Error during domain change: ' . $exception->getMessage());
        return;
      }

      $this->messenger()->addMessage('Previous domain has been disconnected successfully. All user registration has been cancelled.');
    }

    // Update domain in media_acquiadam settings if the module is enabled.
    if ($this->moduleHandler->moduleExists('media_acquiadam')) {
      $this->configFactory->getEditable('media_acquiadam.settings')
        ->set('domain', $new_domain)
        ->save();
    }

    // Save the token to state.
    $this->state->set('acquia_dam_token', $form_state->getValue('access_token'));

    $config_to_save->set('auth_type', $auth_type);

    // Save API key ID if using API key authentication
    if ($this->moduleHandler->moduleExists('key') && $auth_type === 'key_based') {
      $config_to_save->set('key_id', $form_state->getValue('key_id'))
        // Domain is not needed for key-based auth.
        ->clear('domain');
    }
    else {
      // Clear key_id if not using key-based auth.
      $config_to_save->set('domain', $new_domain)
        ->clear('key_id');
    }

    // Save the configuration.
    $config_to_save->save();

    parent::submitForm($form, $form_state);
  }

  /**
   * Get the credentials for the key selected.
   *
   * @param string $key_id
   *   The key ID.
   * @param string $auth_type
   *  The authentication type.
   *
   * @return array
   *   The key credentials as an associative array.
   */
  private function getKeyCredentials(string $key_id, string $auth_type): array {
    if ($this->moduleHandler->moduleExists('key') && $auth_type === 'key_based') {
      $key = $this->keyRepository->getKey($key_id);
      if ($key) {
        $credentials = $key->getKeyValue();
        // If the key value is already an array, return as-is.
        if (is_array($credentials)) {
          return $credentials;
        }
        // Attempt to decode JSON string, return array or empty array on failure.
        if (is_string($credentials) && !empty($credentials)) {
          $decoded_credentials = json_decode($credentials, TRUE);
          $decoded_credentials['domain'] = !empty($decoded_credentials['domain']) ? Xss::filter(rtrim(preg_replace('#^https?://#', '', $decoded_credentials['domain']))) : '';

          return $decoded_credentials;
        }
      }
    }

    return [];
  }

}
