<?php

declare(strict_types=1);

namespace Drupal\conductor\Form;

use Drupal\conductor\ConductorHttpApiClient;
use Drupal\conductor\Exception\ConductorCredentialsNotFoundException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\Markup;
use Drupal\key\KeyRepositoryInterface;
use GuzzleHttp\Exception\GuzzleException;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Module settings form.
 */
final class ConductorConfigForm extends ConfigFormBase {

  /**
   * @var \Drupal\conductor\ConductorHttpApiClient
   */
  private ConductorHttpApiClient $conductorHttpApiClient;

  /**
   * @var \Drupal\key\KeyRepositoryInterface
   */
  private KeyRepositoryInterface $keyRepository;

  /**
   * Constructs class variables.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The factory for configuration objects.
   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
   *   Conductor Helper service.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   Drupal messenger.
   * @param \Drupal\conductor\ConductorHttpApiClient $conductorHttpApiClient
   *   Conductor HTTP API client.
   * @param \Drupal\key\KeyRepositoryInterface $keyRepository
   *   The key repository service.
   */
  public function __construct(
    ConfigFactoryInterface $config_factory,
    TypedConfigManagerInterface $typedConfigManager,
    MessengerInterface $messenger,
    ConductorHttpApiClient $conductorHttpApiClient,
    KeyRepositoryInterface $keyRepository,
  ) {
    parent::__construct($config_factory, $typedConfigManager);
    $this->messenger = $messenger;
    $this->conductorHttpApiClient = $conductorHttpApiClient;
    $this->keyRepository = $keyRepository;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): ConductorConfigForm {
    return new static(
      $container->get('config.factory'),
      $container->get('config.typed'),
      $container->get('messenger'),
      $container->get(ConductorHttpApiClient::class),
      $container->get('key.repository')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'conductor_config_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $form = parent::buildForm($form, $form_state);
    $config = $this->configFactory->get('conductor.settings');

    // API Credentials configuration section.
    $form['credentials_section'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('API Credentials'),
      '#tree' => FALSE,
    ];

    $form['credentials_section']['key_id'] = [
      '#type' => 'key_select',
      '#title' => $this->t('API Credentials Key'),
      '#empty_option' => $this->t('- Select -'),
      '#default_value' => $config->get('key_id') ?? '_none',
      '#description' => $this->t('Select the key that contains your Conductor API credentials (<em>api_key</em> and <em>shared_secret</em> as JSON).'),
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state): void {
    parent::validateForm($form, $form_state);
    $this->validateCredentials($form_state);
  }

  /**
   * Validates credentials when using Key module.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   */
  private function validateCredentials(FormStateInterface $form_state): void {
    $keyId = $form_state->getValue('key_id');
    if (empty($keyId) || $keyId === '_none') {
      $form_state->setErrorByName('key_id', $this->t('Please select a key with the Conductor credentials.'));
      return;
    }

    // Verify the key exists and can be loaded.
    $key = $this->keyRepository->getKey($keyId);
    if (!$key) {
      $form_state->setErrorByName('key_id', $this->t('The selected key could not be found.'));
      return;
    }

    $keyValue = $key->getKeyValue();
    if (empty($keyValue)) {
      $form_state->setErrorByName('key_id', $this->t('The selected key is empty or could not be retrieved.'));
      return;
    }

    // Parse and validate the JSON credentials.
    $credentials = json_decode($keyValue, TRUE);
    if (json_last_error() !== JSON_ERROR_NONE) {
      $form_state->setErrorByName('key_id', $this->t('The selected key does not contain valid JSON credentials.'));
      return;
    }

    // Validate required credentials are present.
    if (empty($credentials['api_key'])) {
      $form_state->setErrorByName('key_id', $this->t('The selected key does not contain a valid API key.'));
      return;
    }

    if (empty($credentials['shared_secret'])) {
      $form_state->setErrorByName('key_id', $this->t('The selected key does not contain a valid shared secret.'));
      return;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $config = $this->config('conductor.settings');
    $config->set('key_id', $form_state->getValue('key_id'));
    $config->save();

    parent::submitForm($form, $form_state);
    $this->connectionIsSuccessful();
  }

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

  /**
   * Tests the connection to Conductor API.
   *
   * @return bool
   *   TRUE if the connection is successful, FALSE otherwise.
   */
  private function connectionIsSuccessful(): bool {
    try {
      $account_id = $this->conductorHttpApiClient->getAccountId();

      // Check account id if received.
      if (!empty($account_id)) {
        $this->messenger->addMessage(Markup::create('Successfully established connection to Conductor API.'));
        return TRUE;
      }

      $this->messenger
        ->addError(
          Markup::create(
            'Unable to connect to Conductor API. Please check credentials or <a href="https://app.conductor.com/u/password/forgot" target="_blank">reset your password</a>'
          )
        );
    }
    catch (GuzzleException | ConductorCredentialsNotFoundException) {
      $this->messenger->addError(
        Markup::create('Unable to connect to Conductor API. Please, review your api key and secret or contact site administrator.')
      );
    }
    catch (\throwable) {
      $this->messenger->addError(Markup::create('Something went wrong. Please contact site administrator.'));
    }
    return FALSE;
  }

}
