<?php

namespace Drupal\acquia_dam\Form;

use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
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;

  /**
   * {@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');
    }

    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();

    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.') . '</p>',
        ];
      }
      // If the site is authenticated, show the disconnect link.
      else {
        $form['site_auth']['desc'] = [
          '#markup' => '<p>' . $this->t('Site is authenticated with Acquia DAM.') . '</p>',
        ];

        $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'),
    ];

    $form['configuration']['actions']['#type'] = 'actions';
    $form['configuration']['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Authenticate Site'),
      '#button_type' => 'primary',
      '#disabled' => $is_authenticated,
    ];

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

    // Build auth type options
    $auth_options = [
      'authorization_code' => $this->t('Authorization code (standard)'),
      'refresh_token' => $this->t('Refresh token (more secure)'),
    ];

    if ($is_key_module_installed) {
      $auth_options['key_based'] = $this->t('Key Based (using Key module)');
    }

    $form['configuration']['auth_type'] = [
      '#type' => 'radios',
      '#title' => $this->t('Authentication method'),
      '#access' => !$is_media_acquiadam_installed,
      '#default_value' => $config->get('auth_type', 'authorization_code'),
      '#options' => $auth_options,
      'authorization_code' => [
        '#description' => $this->t('Standard connection using long-lived access token.'),
        '#disabled' => $is_authenticated,
      ],
      'refresh_token' => [
        '#description' => $this->t('Requires support assistance to enable an integration with refresh token support. Refresh tokens improve security by using short-lived access tokens. Disabled if Media Acquia DAM module is installed.'),
        '#disabled' => $is_authenticated,
      ],
    ];

    if ($is_key_module_installed) {
      $form['configuration']['auth_type']['key_based'] = [
        '#description' => $this->t('Use the Key module to securely store your API token. This allows for centralized key management and enhanced security.'),
        '#disabled' => $is_authenticated,
      ];
    }

    // Refresh token configuration.
    $form['configuration']['refresh_token'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('OAuth settings'),
      '#tree' => FALSE,
      '#states' => [
        'visible' => [
          ':input[name="auth_type"]' => ['value' => 'refresh_token'],
        ],
        'required' => [
          ':input[name="auth_type"]' => ['value' => 'refresh_token'],
        ],
      ],
    ];
    $form['configuration']['refresh_token']['client_id'] = [
      '#type' => 'textfield',
      '#access' => !$is_media_acquiadam_installed,
      '#title' => $this->t('OAuth client ID'),
      '#default_value' => $config->get('client_id') ?? '',
      '#description' => $this->t('The client ID for your OAuth integration.'),
      '#states' => [
        'visible' => [
          ':input[name="auth_type"]' => ['value' => 'refresh_token'],
        ],
      ],
      '#disabled' => $is_authenticated,
    ];

    $form['configuration']['refresh_token']['client_secret'] = [
      '#type' => 'textfield',
      '#access' => !$is_media_acquiadam_installed,
      '#title' => $this->t('OAuth client secret'),
      '#default_value' => $config->get('client_secret') ?? '',
      '#description' => $this->t('The client secret for your OAuth integration.'),
      '#states' => [
        'visible' => [
          ':input[name="auth_type"]' => ['value' => 'refresh_token'],
        ],
      ],
      '#disabled' => $is_authenticated,
    ];

    // 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('example: demo.acquiadam.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');
    $domain = '';

    // 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);
      // 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($credentials['domain']) || empty($credentials['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 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);
    }

    // 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') ?? 'authorization_code';
    $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();
    }

    $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)
        ->set('client_id', $auth_type === 'refresh_token' ? $form_state->getValue('client_id') : '')
        ->set('client_secret', $auth_type === 'refresh_token' ? $form_state->getValue('client_secret') : '')
        ->clear('key_id');
    }

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

    // Authenticate only if the site isn't authenticated yet.
    // On domain change it will be disconnected first.
    if (!$this->authService->isSiteAuthenticated()) {
      $auth_link = Url::fromRoute('acquia_dam.site_auth')->setAbsolute()->toString();
      $url = Url::fromUri($this->authService->generateAuthUrl($auth_link));
      $form_state->setResponse(new TrustedRedirectResponse($url->toString()));
    }

    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 [];
  }

}
