<?php

namespace Drupal\acquia_contenthub\Form;

use Acquia\ContentHubClient\ObjectFactory;
use Acquia\ContentHubClient\Settings;
use Acquia\ContentHubClient\Webhook;
use Acquia\ContentHubClient\Webhook\WebhookStatusState;
use Drupal\acquia_contenthub\Client\CdfMetricsManager;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub\Client\ClientMetaDataManagerInterface;
use Drupal\acquia_contenthub\ContentHubConnectionManager;
use Drupal\acquia_contenthub\Exception\QueueItemInputViolationException;
use Drupal\acquia_contenthub\Libs\PullSyndication\QueueItemInputValidator;
use Drupal\acquia_contenthub\Libs\Traits\ResponseCheckerTrait;
use Drupal\acquia_contenthub\Settings\ConfigSettingsInterface;
use Drupal\acquia_contenthub\Settings\ConnectionDetailsInterface;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Defines the form to configure the Content Hub connection settings.
 */
class ContentHubSettingsForm extends ConfigFormBase {

  use ResponseCheckerTrait;

  public const ROUTE = 'acquia_contenthub.admin_settings';

  /**
   * Default cloud filter prefix.
   *
   * @var string
   */
  const DEFAULT_FILTER = 'default_filter_';

  /**
   * The Content Hub endpoint for receiving webhooks.
   *
   * @var \Drupal\Core\GeneratedUrl|string
   */
  protected $achPath;

  /**
   * The client factory.
   *
   * @var \Drupal\acquia_contenthub\Client\ClientFactory
   */
  protected $clientFactory;

  /**
   * Contenthub Settings.
   *
   * @var \Acquia\ContentHubClient\Settings
   */
  protected $settings;

  /**
   * Contenthub Client.
   *
   * @var \Acquia\ContentHubClient\ContentHubClient|bool
   */
  protected $client;

  /**
   * The Content Hub connection manager.
   *
   * @var \Drupal\acquia_contenthub\ContentHubConnectionManager
   */
  protected $chConnectionManager;

  /**
   * Client Metadata Manager.
   *
   * @var \Drupal\acquia_contenthub\Client\ClientMetaDataManagerInterface
   */
  protected ClientMetaDataManagerInterface $metaDataManager;

  /**
   * Content Hub metrics manager.
   *
   * @var \Drupal\acquia_contenthub\Client\CdfMetricsManager
   */
  protected $chMetrics;

  /**
   * Client name before updating the new one.
   *
   * @var string
   */
  protected string $oldClientName = '';

  /**
   * Content Hub connection details.
   *
   * @var \Drupal\acquia_contenthub\Settings\ConnectionDetailsInterface
   */
  protected ConnectionDetailsInterface $achConnection;

  /**
   * CH configurations.
   *
   * @var \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface
   */
  protected ContentHubConfigurationInterface $achConfigurations;

  /**
   * Content Hub configuration.
   *
   * @var \Drupal\acquia_contenthub\Settings\ConfigSettingsInterface
   */
  protected ConfigSettingsInterface $achConfigSettings;

  /**
   * The acquia_contenthub cache bin.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected CacheBackendInterface $cache;

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

  /**
   * Returns bootstrapped client.
   *
   * Modules might want to alter the settings form. In some cases
   * a bootstrapped client would come in handy before the end of
   * submission. Get it from the form object.
   *
   * @return \Acquia\ContentHubClient\ContentHubClient|bool
   *   The content hub client.
   */
  public function getClient() {
    return $this->client;
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return ['acquia_contenthub.admin_settings'];
  }

  /**
   * ContentHubSettingsForm constructor.
   *
   * @param \Drupal\acquia_contenthub\ContentHubConnectionManager $ch_connection_manager
   *   The Content Hub connection manager service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The Configuration Factory.
   * @param \Drupal\acquia_contenthub\Client\ClientFactory $client_factory
   *   The Client Factory.
   * @param \Drupal\acquia_contenthub\Client\CdfMetricsManager $cdf_metrics_manager
   *   The Content Hub metrics manager.
   * @param \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ach_configurations
   *   The Content Hub configs.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
   *   The acquia_contenthub cache bin.
   * @param \Drupal\acquia_contenthub\Client\ClientMetaDataManagerInterface $metadata_manager
   *   Client metadata manager.
   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
   *   Typed config manager.
   */
  public function __construct(ContentHubConnectionManager $ch_connection_manager, ConfigFactoryInterface $config_factory, ClientFactory $client_factory, CdfMetricsManager $cdf_metrics_manager, ContentHubConfigurationInterface $ach_configurations, CacheBackendInterface $cache, ClientMetaDataManagerInterface $metadata_manager, TypedConfigManagerInterface $typedConfigManager) {
    if (version_compare(\Drupal::VERSION, '10.2.0', '>=')) {
      parent::__construct($config_factory, $typedConfigManager);
    }
    else {
      parent::__construct($config_factory);
    }
    $this->chConnectionManager = $ch_connection_manager;
    $this->clientFactory = $client_factory;
    $this->chMetrics = $cdf_metrics_manager;
    $this->achPath = Url::fromRoute('acquia_contenthub.webhook')->toString();
    $this->achConnection = $ach_configurations->getConnectionDetails();
    $this->achConfigSettings = $ach_configurations->getContentHubConfig();
    $this->achConfigurations = $ach_configurations;
    $this->cache = $cache;
    $this->metaDataManager = $metadata_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('acquia_contenthub.connection_manager'),
      $container->get('config.factory'),
      $container->get('acquia_contenthub.client.factory'),
      $container->get('acquia_contenthub.cdf_metrics_manager'),
      $container->get('acquia_contenthub.configuration'),
      $container->get('cache.acquia_contenthub'),
      $container->get('acquia_contenthub.client_metadata_manager'),
      $container->get('config.typed')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $provider = $this->achConfigurations->getProvider();
    $this->settings = $this->achConfigurations->getSettings();
    $this->initializeClient();

    $readonly = $provider !== 'core_config';
    $connected = FALSE;
    $is_maintenance = FALSE;
    if ($maintenance_mode = $this->cache->get('ch_maintenance_mode')) {
      $is_maintenance = $maintenance_mode->data;
    }
    if (!empty($this->client)) {
      try {
        $response = $this->client->ping();
        $connected = $this->isSuccessful($response);
      }
      catch (\Exception $e) {
        if (method_exists($e, 'getResponse') && $this->isMaintenanceError($e->getResponse())) {
          $this->messenger()
            ->addError($this->t('Service is under maintenance. Please try to connect Content Hub later!'));
          $this->client = FALSE;
          $is_maintenance = TRUE;
        }
        else {
          $msg = 'Your client is not registered to Content Hub';
          $this->messenger()->addWarning($this->t('@msg', ['@msg' => $msg]));
          $this->logger('acquia_contenthub')
            ->warning(sprintf('%s: %s', $msg, $e->getMessage()));
        }
      }
    }
    $client_name = '';

    $is_suppressed = $this
      ->achConnection
      ->isWebhookSuppressed();

    $form['actions']['env_var'] = [
      '#type' => 'details',
      '#title' => $this->t('Register with environment variable'),
      '#description' => $this->t('Credentials are set in environment variables.'),
      '#open' => TRUE,
      '#access' => $provider === 'environment_variable',
    ];

    $form['actions']['env_var']['remove_suppression'] = [
      '#type' => 'submit',
      '#value' => $this->t('Register site'),
      '#button_type' => 'primary',
      '#weight' => 100,
      '#limit_validation_errors' => [],
      '#access' => $is_suppressed,
      '#submit' => [[$this, 'removeWebhookSuppression']],
      '#disabled' => $is_maintenance,
    ];

    $form['actions']['env_var']['suppress_webhook'] = [
      '#type' => 'submit',
      '#value' => $this->t('Unregister site'),
      '#button_type' => 'primary',
      '#weight' => 100,
      '#limit_validation_errors' => [],
      '#access' => !$is_suppressed,
      '#submit' => [[$this, 'suppressWebhook']],
      '#disabled' => $is_maintenance,
    ];

    if ($connected) {
      $client_name = $this->ensureClientNameIsSynced();
    }

    if ($readonly && $connected && $provider !== 'environment_variable') {
      $this->messenger()
        ->addMessage($this->t('Settings are currently read-only, provided by %provider. Values shown for informational purposes only.', ['%provider' => $provider]));
    }

    if ($readonly && !$connected) {
      $this->messenger()
        ->addMessage($this->t('Warning, you are not connected to Content Hub, but your settings provider (%provider) does not allow settings changes. Values shown for informational purposes only.', ['%provider' => $provider]), 'warning');
      $connected = TRUE;
    }

    $title = $connected ? $this->t('Connection Settings (%account)',
      ['%account' => $this->getAccountId()]) : $this->t('Connection Settings');
    $form['settings'] = [
      '#type' => 'details',
      '#title' => $title,
      '#collapsible' => TRUE,
      '#description' => $this->t('Settings for connection to Acquia Content Hub'),
      '#disabled' => $connected,
      '#open' => !$connected,
      '#access' => $provider !== 'environment_variable',
    ];

    $form['settings']['client_details_table'] = [
      '#type' => 'table',
      '#header' => [
        'name' => $this->t('Name'),
        'value' => $this->t('Value'),
        'desc' => $this->t('Description'),
      ],
    ];

    $val = $this->achConfigSettings->shouldSendContentHubUpdates() ? 'ENABLED' : 'DISABLED';
    $form['settings']['client_details_table']['send_contenthub_updates'] = [
      'name' => [
        '#markup' => $this->t('Send updates to Content Hub Service.'),
      ],
      'value' => [
        '#wrapper_attributes' => [
          'class' => ['send-contenthub-updates'],
        ],
        '#markup' => $this->t('@val', ['@val' => $val]),
      ],
      'desc' => [
        '#markup' => $this->t('Set this flag with drush to False in case of service degradation to disable sending Content Hub Service related updates temporarily. Turning it off will prevent any performance hits for the site for the duration of the event.'),
      ],
    ];

    $val = $this->achConfigSettings->shouldSendClientCdfUpdates() ? 'ENABLED' : 'DISABLED';
    $form['settings']['client_details_table']['send_clientcdf_updates'] = [
      'name' => [
        '#markup' => $this->t('Send Client metrics updates to Content Hub Service.'),
      ],
      'value' => [
        '#wrapper_attributes' => [
          'class' => ['send-clientcdf-updates'],
        ],
        '#markup' => $this->t('@val', ['@val' => $val]),
      ],
      'desc' => [
        '#markup' => $this->t('Set this flag with drush to false in case of service degradation to disable sending Metrics updates to Content Hub Service.'),
      ],
    ];

    if (empty($client_name)) {
      $form['settings'][] = $this->getFormElements($client_name);
    }
    else {
      $form['settings']['client_details_table'] = array_merge($form['settings']['client_details_table'], $this->getFormElements($client_name));
    }

    $webhook_is_disabled = FALSE;
    if ($this->client) {
      $webhooks = $this->client->getWebHooks();
      $webhook_uuid = $this->settings->getWebhook('uuid');

      /** @var \Acquia\ContentHubClient\Webhook $webhook */
      $webhook = current(array_filter($webhooks, function (Webhook $webhook) use ($webhook_uuid) {
        return $webhook->getUuid() === $webhook_uuid;
      }));
      $webhook_is_disabled = $webhook ? !$webhook->isEnabled() : FALSE;
    }
    $webhook_description = $this->t('This should be the domain (and drupal subpath if applicable). Do not include trailing slash.');
    if ($webhook_is_disabled) {
      $webhook_description .= ' <b>' . $this->t('To re-enable the webhook please click "Update Public URL" button.') . '</b>';
    }
    $request = Request::createFromGlobals();
    $form['settings']['webhook'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Publicly Accessible URL'),
      '#required' => TRUE,
      '#default_value' => $this->settings->getWebhook() ?: $request->getSchemeAndHttpHost(),
      '#description' => $webhook_description,
      '#disabled' => $webhook_is_disabled,
    ];

    if (empty($client_name)) {
      $form['settings']['origin'] = [
        '#type' => 'item',
        '#title' => $this->t("Site's Origin UUID"),
      ];
    }

    if ($readonly) {
      return $form;
    }

    $form = parent::buildForm($form, $form_state);

    if (!$connected) {
      $form['actions']['submit']['#value'] = $this->t('Register Site');
      $form['actions']['submit']['#name'] = 'register_site';
      $form['actions']['submit']['#disabled'] = $is_maintenance;
      return $form;
    }

    $this->messenger()
      ->addMessage($this->t('Site successfully connected to Content Hub. To change connection settings, unregister the site first.'));

    $form['client_details'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Client Details'),
      '#collapsible' => FALSE,
      '#open' => TRUE,
    ];

    $ach_client_name = $this->achConnection->getClientName() ?: '';
    $form['client_details']['client_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Client Name'),
      '#default_value' => $ach_client_name,
      '#disabled' => $this->achConnection->isOverridden('client_name'),
      '#required' => TRUE,
      '#description' => $this->getClientNameDescription(),
    ];

    $form['client_details']['updateClient'] = [
      '#type' => 'submit',
      '#value' => $this->t('Update Client Name'),
      '#button_type' => 'primary',
      '#weight' => 1000,
      '#validate' => [[$this, 'validateClientName']],
      '#submit' => [
        [$this, 'updateClientName'],
        [$this, 'updateClientCdf'],
        [$this, 'updateDefaultFilter'],
      ],
      '#name' => 'update_clientName',
      '#disabled' => $is_maintenance,
    ];

    $form['webhook_details'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Webhook Details'),
      '#collapsible' => FALSE,
      '#open' => TRUE,
    ];

    $form['webhook_details']['use_webhook_v1'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Only include one entity per webhook'),
      '#description' => $this->t('By default, Content Hub will attempt to batch related entities into a single webhook to reduce the number of webhooks received by this site. However, this can be disabled if it interferes with custom code.'),
      '#default_value' => $this->achConnection->shouldUseSingleEntityPayload(),
    ];

    $form['webhook_details']['webhook'] = $form['settings']['webhook'];
    unset($form['settings']['webhook']);

    $form['webhook_details']['updatewebhook'] = [
      '#type' => 'submit',
      '#value' => $this->t('Update Webhook Settings'),
      '#button_type' => 'primary',
      '#weight' => 100,
      '#submit' => [
        [$this, 'updateWebhook'],
        [$this, 'updateClientCdf'],
        [$this, 'updateClientMetadata'],
      ],
      '#validate' => [[$this, 'validateWebhook']],
      '#name' => 'update_webhook',
      '#suffix' => '<p>' . $this->t('By clicking on this button it will update the webhook.') . '</p>',
      '#disabled' => $is_maintenance,
    ];

    $this->addSyndicationStrategyFormElements($form, $is_maintenance);

    $form['actions']['unregister'] = [
      '#type' => 'link',
      '#title' => $this->t('Unregister Site'),
      '#url' => Url::fromRoute('acquia_contenthub.delete_client_confirm'),
      '#weight' => 999,
      '#limit_validation_errors' => [],
      '#attributes' => [
        'class' => [
          'use-ajax',
          'button',
        ],
        'data-dialog-type' => 'modal',
        'data-dialog-options' => json_encode([
          'width' => '50%',
        ]),
      ],
      // No validation at all is required in the equivocate case, so
      // we include this here to make it skip the form-level validator.
      '#validate' => [],
      '#name' => 'unregister_site',
    ];

    unset($form['actions']['submit']);

    return $form;
  }

  /**
   * Returns required form elements as per registration.
   *
   * @param string $client_name
   *   Registered Client Name.
   *
   * @return array
   *   Content Hub Form with required elements.
   */
  protected function getFormElements(string $client_name): array {
    $form_elements = [];
    if (empty($client_name)) {
      $form_elements['settings']['hostname'] = [
        '#type' => 'textfield',
        '#title' => $this->t('Acquia Content Hub Hostname'),
        '#description' => $this->t('The hostname of the Acquia Content Hub API without trailing slash at end of URL, e.g. http://localhost:5000'),
        '#default_value' => $this->settings->getUrl(),
        '#required' => TRUE,
      ];

      $form_elements['settings']['api_key'] = [
        '#type' => 'textfield',
        '#title' => $this->t('API Key'),
        '#default_value' => $this->settings->getApiKey(),
        '#required' => TRUE,
      ];

      $form_elements['settings']['secret_key'] = [
        '#type' => 'password',
        '#title' => $this->t('Secret Key'),
        '#default_value' => $this->settings->getSecretKey(),
        '#required' => TRUE,
      ];

      $form_elements['settings']['client_name'] = [
        '#type' => 'textfield',
        '#title' => $this->t('Client Name'),
        '#default_value' => $this->settings->getName(),
        '#disabled' => $this->achConnection->isOverridden('client_name'),
        '#required' => TRUE,
        '#description' => $this->getClientNameDescription(),
      ];

      $form_elements['settings']['use_webhook_v1'] = [
        '#type' => 'checkbox',
        '#title' => $this->t('Only include one entity per webhook'),
        '#description' => $this->t('By default, Content Hub will attempt to batch related entities into a single webhook to reduce the number of webhooks received by this site. However, this can be disabled if it interferes with custom code.'),
        '#default_value' => $this->achConnection->shouldUseSingleEntityPayload(),
      ];

      $this->addCommonSyndicationStrategyElements($form_elements, ConnectionDetailsInterface::SYNDICATION_MODE_PUSH);
    }
    else {
      $form_elements['hostname'] = [
        'name' => [
          '#markup' => $this->t('Acquia Content Hub Hostname'),
        ],
        'value' => [
          '#markup' => $this->settings->getUrl(),
        ],
        'desc' => [
          '#markup' => $this->t('The hostname of the Acquia Content Hub API without trailing slash at end of URL, e.g. http://localhost:5000'),
        ],
      ];

      $form_elements['api_key'] = [
        'name' => [
          '#markup' => $this->t('API Key'),
        ],
        'value' => [
          '#markup' => $this->settings->getApiKey(),
        ],
      ];

      $form_elements['origin'] = [
        'name' => [
          '#markup' => $this->t("Site's Origin UUID"),
        ],
        'value' => [
          '#markup' => $this->settings->getUuid(),
        ],
      ];

      $form_elements['account_name'] = [
        'name' => [
          '#markup' => $this->t('Account ID'),
        ],
        'value' => [
          '#markup' => $this->getAccountId(),
        ],
      ];
    }

    return $form_elements;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state) {
    $action_name = $form_state->getTriggeringElement()['#name'];
    if (!in_array($action_name,
      ['register_site', 'update_webhook', 'unregister_site'],
      TRUE)) {
      return;
    }

    if (!UrlHelper::isValid($form_state->getValue('hostname'), TRUE)) {
      $form_state->setErrorByName('hostname', $this->t('This is not a valid URL. Please insert another one.'));
    }

    $webhook = $this->getFormattedWebhookUrl($form_state);
    if (!UrlHelper::isValid($webhook, TRUE)) {
      $form_state->setErrorByName('webhook', $this->t('Please type a publicly accessible url.'));
    }

    // Important. This should never validate if it is an UUID. Lift 3 does not
    // use UUIDs for the api_key but they are valid for Content Hub.
    $fields = [
      'api_key' => $this->t('Please insert an API Key.'),
      'secret_key' => $this->t('Please insert a Secret Key.'),
      'client_name' => $this->t('Please insert a Client Name.'),
    ];
    foreach ($fields as $field => $error_message) {
      if (!$form_state->hasValue($field)) {
        $form_state->setErrorByName($field, $error_message);
      }
    }

    $this->validateVisibilityTimeoutInput($form_state);

    if (!empty($form_state->getErrors())) {
      return;
    }

    if ($action_name === 'register_site') {
      $this->register($form_state);
      $this->updateSyndicationMode($form, $form_state);
    }
  }

  /**
   * Validates webhook update.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @throws \Exception
   */
  public function validateWebhook(array &$form, FormStateInterface $form_state): void {
    if (!empty($form_state->getErrors())) {
      return;
    }

    $webhook_url = $this->getFormattedWebhookUrl($form_state);
    if (!UrlHelper::isValid($webhook_url, TRUE)) {
      $form_state->setErrorByName('webhook', $this->t('Please type a publicly accessible url.'));
    }

    $saved_webhook_url = $this->settings->getWebhook('url');
    if ($saved_webhook_url === $webhook_url && $this->chConnectionManager->webhookIsRegistered($webhook_url)) {
      return;
    }

    if ($this->chConnectionManager->webhookIsRegistered($webhook_url)) {
      $form_state->setErrorByName('webhook', $this->t('This webhook is already being used. (%name) Please insert another one, or unregister the existing one first.', [
        '%name' => $webhook_url,
      ]));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    parent::submitForm($form, $form_state);

    // We assume here all inserted values have passed validation.
    $this->settings = $this->client->getSettings();
    $hostname = rtrim($this->settings->getUrl(), '/');
    $remote = $this->client->getRemoteSettings();
    $config_data = [
      'hostname' => $hostname,
      'api_key' => $this->settings->getApiKey(),
      'secret_key' => $this->settings->getSecretKey(),
      'origin' => $this->settings->getUuid(),
      'client_name' => $this->settings->getName(),
      'use_webhook_v1' => (bool) $form_state->getValue('use_webhook_v1'),
      'syndication_mode' => $form_state->getValue('syndication_mode'),
    ];
    $this->achConnection->setMultiple($config_data);

    if (isset($remote['shared_secret'])) {
      $this->achConnection->setSharedSecret($remote['shared_secret']);
    }

    $this->updateClientMetadata();

    // If a webhook was sent during registration, lets get that added as
    // well, return the webhook UUID if successful.
    $webhook = $form_state->getValue('webhook');
    $response = $this->chConnectionManager->registerWebhook($webhook);
    if (empty($response)) {
      $this->messenger()
        ->addWarning($this->t('Registration to Content Hub was successful, but Content Hub could not reach your site to verify connectivity. Please update your publicly accessible URL and try again.'));
    }
    else {
      $webhook_details = [
        'uuid' => $response['uuid'],
        'url' => $response['url'],
        'settings_url' => $webhook,
      ];
      $this->resetClient($webhook_details);
    }
    try {
      $this->chMetrics->sendClientCdfUpdates($this->client);
    }
    catch (\Exception $e) {
      $this->messenger()
        ->addWarning($this->t('Warning: incomplete client registration. Please check the logs for more information.'));
      $this->logger('acquia_contenthub')
        ->warning(sprintf('Error during client CDF creation. Could not send it to Content Hub. Error: %s', $e->getMessage()));
    }
  }

  /**
   * Validates Client name during update.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @throws \Exception
   */
  public function validateClientName(array &$form, FormStateInterface $form_state): void {
    if ($this->achConnection->getClientName() === $form_state->getValue('client_name')) {
      $form_state->setErrorByName('client_name', $this->t('Client name unchanged.'));
    }
  }

  /**
   * Updates client name.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  public function updateClientName(array &$form, FormStateInterface $form_state): void {
    $response = $this->client->updateClient($this->achConnection->getClientUuid(), $form_state->getValue('client_name'));
    if ($this->isSuccessful($response)) {
      $this->oldClientName = $this->achConnection->getClientName();
      $this->achConnection->setClientName($form_state->getValue('client_name'));
      $this->messenger()
        ->addMessage($this->t('Client name updated successfully.'));
      return;
    }
    $response_body = json_decode($response->getBody(), TRUE);
    $error_msg = $response_body['error']['message'] ?? 'Unexpected error, please check the logs';
    $this->messenger()
      ->addError($this->t('Client name update failed: @val', ['@val' => $error_msg]));
    $this->logger('acquia_contenthub')
      ->error('Client name update failed: {val}', ['val' => (string) $response->getBody()]);
  }

  /**
   * Triggers a client metadata update.
   */
  public function updateClientMetadata(): void {
    $result = $this->metaDataManager->updateClientMetadata();
    if ($result) {
      $this->messenger()
        ->addMessage($this->t('Client metadata updated successfully.'));
    }
  }

  /**
   * Updates default filter if old default filter exist.
   *
   * Otherwise, creates a new default filter and adds to the webhook.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  public function updateDefaultFilter(array &$form, FormStateInterface $form_state): void {
    $old_default_filter_name = self::DEFAULT_FILTER . $this->oldClientName;
    $new_default_filter_name = self::DEFAULT_FILTER . $form_state->getValue('client_name');
    $response = $this->chConnectionManager->updateFilterName($old_default_filter_name, $new_default_filter_name);
    if (!$response) {
      return;
    }
    if ($response['success']) {
      $this->messenger()
        ->addMessage($this->t('Default filter updated successfully.'));
      return;
    }
    $this->messenger()->addError($this->t('Default filter update failed.'));
    if (!$response['success'] && !empty($response['error']['message'])) {
      $this->logger('acquia_contenthub')
        ->error('Default filter update failed. Error: {error}', ['error' => $response['error']['message']]);
    }
  }

  /**
   * Triggers a client CDF update.
   *
   * @param array $form
   *   The current form.
   * @param \Drupal\Core\Form\FormStateInterface $formState
   *   The state of the current form.
   */
  public function updateClientCdf(array &$form, FormStateInterface $formState): void {
    $this->resetClient([
      'url' => $this->achConnection->getWebhook('url'),
      'settings_url' => $this->achConnection->getWebhook(),
      'uuid' => $this->achConnection->getWebhook('uuid'),
    ], $this->achConnection->getClientName());
    try {
      $this->chMetrics->sendClientCdfUpdates($this->client);
    }
    catch (\Exception $e) {
      $this->messenger()
        ->addWarning($this->t("The operation was successful, but there was an error updating the client's CDF. Please check the logs!"));
      $this->logger('acquia_contenthub')
        ->error('Failed to update client CDF: {error}. ', ['error' => $e->getMessage()]);
    }
  }

  /**
   * Updates webhook.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @throws \Exception
   */
  public function updateWebhook(array &$form, FormStateInterface $form_state): void {
    try {
      $this->achConnection->toggleWebhookV1Payload((bool) $form_state->getValue('use_webhook_v1'));
    }
    catch (\Exception $e) {
      $this->logger('acquia_contenthub')->warning($e);
    }

    $webhook_url = $this->getFormattedWebhookUrl($form_state);
    $response = [];
    try {
      $response = $this->chConnectionManager
        ->checkClient()
        ->updateWebhook($webhook_url);
    }
    catch (\Exception $e) {
      $this->logger('acquia_contenthub')->warning($e);
    }

    if (!$response) {
      $this->messenger()
        ->addError($this->t("Something went wrong during webhook (%webhook) update. Check logs for more information.", [
          '%webhook' => $webhook_url,
        ]));
      return;
    }
    $this->messenger()
      ->addMessage($this->t('Successfully updated Webhook Settings.'));
  }

  /**
   * Provides syndication strategy related form elements.
   *
   * @param array $form
   *   The form to extend.
   * @param bool $is_maintenance
   *   Flag whether Content Hub Service is in maintenance mode.
   */
  protected function addSyndicationStrategyFormElements(array &$form, bool $is_maintenance): void {
    $form['syndication_strategy'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Syndication Strategy'),
      '#collapsible' => FALSE,
      '#open' => TRUE,
    ];

    $this->addCommonSyndicationStrategyElements($form, $this->achConnection->getSyndicationMode());

    $form['syndication_strategy']['update_mode'] = [
      '#type' => 'submit',
      '#value' => $this->t('Update Syndication Settings'),
      '#button_type' => 'primary',
      '#weight' => 100,
      '#submit' => [
        [$this, 'updateSyndicationMode'],
        [$this, 'updateClientMetadata'],
      ],
      '#validate' => [[$this, 'validateSyndicationMode']],
      '#name' => 'update_mode',
      '#disabled' => $is_maintenance,
    ];
  }

  /**
   * Performs necessary validations based on the syndication mode.
   *
   * It uses webhook status API to check the webhook.
   *
   * @throws \Exception
   */
  public function validateSyndicationMode($form, FormStateInterface $form_state): void {
    $mode = $form_state->getValue('syndication_mode');
    if ($mode === ConnectionDetailsInterface::SYNDICATION_MODE_PUSH) {
      $this->validateWebhook($form, $form_state);
      // If we have errors let's not make an external call if not necessary.
      if (!empty($form_state->getErrors())) {
        return;
      }

      if (!$this->isWebhookValidForPushSyndication()) {
        $this->messenger()->addError(
          $this->t('Webhook url was rejected by Content Hub Service. Please make sure you register a valid, reachable webhook! Check logs for more information!')
        );
        $form_state->setErrorByName('webhook');
      }
      return;
    }

    $this->validateVisibilityTimeoutInput($form_state);

  }

  /**
   * Validate the value of visibility_timeout field.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @throws \Exception
   */
  private function validateVisibilityTimeoutInput(FormStateInterface $form_state): void {
    if ($form_state->getValue('syndication_mode') === ConnectionDetailsInterface::SYNDICATION_MODE_PULL) {
      $visibility_timeout = $form_state->getValue('visibility_timeout');
      if (isset($visibility_timeout)) {
        try {
          QueueItemInputValidator::validateVisibilityTimeout($visibility_timeout);
        }
        catch (QueueItemInputViolationException $exception) {
          $this->messenger()->addError($this->t('@error', ['@error' => $exception->getMessage()]));
          $form_state->setErrorByName('visibility_timeout');
        }
      }
    }
  }

  /**
   * Updates Syndication Mode.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @throws \Exception
   */
  public function updateSyndicationMode(array &$form, FormStateInterface $form_state): void {
    try {
      $syndication_mode = $form_state->getValue('syndication_mode');
      $this->achConnection->setSyndicationMode($syndication_mode);
      if ($syndication_mode === ConnectionDetailsInterface::SYNDICATION_MODE_PULL) {
        $limit = $form_state->getValue('limit') ?: $this->achConnection->getSyndicationLimit();
        $this->achConnection->setSyndicationLimit($limit);
        $visibility_timeout = $form_state->getValue('visibility_timeout') ?? $this->achConnection->getVisibilityTimeout();
        $this->achConnection->setVisibilityTimeout($visibility_timeout);
      }
    }
    catch (\Exception $e) {
      $this->logger('acquia_contenthub')->warning($e);
    }
  }

  /**
   * Checks if the webhook is valid for push syndication.
   *
   * @throws \Exception
   */
  protected function isWebhookValidForPushSyndication(): bool {
    $uuid = $this->achConnection->getWebhook('uuid');

    if (!$uuid) {
      $this->logger('acquia_contenthub')
        ->error('Could not find webhook uuid, please confirm if webhook url is registered!');
      return FALSE;
    }

    $status = $this->client->getWebhookStatusFor($uuid);
    if (!$status->isValid()) {
      $this->logger('acquia_contenthub')->error('Could not get webhook status for uuid: @uuid', [
        '@uuid' => $uuid,
      ]);
      return FALSE;
    }

    if ($status->isOfState(WebhookStatusState::UNREGISTERED)) {
      $this->logger('acquia_contenthub')->error('Webhook is not registered! UUID: @uuid', [
        '@uuid' => $uuid,
      ]);
      return FALSE;
    }

    $invalid_states = [
      WebhookStatusState::DISABLED,
      WebhookStatusState::UNKNOWN,
      WebhookStatusState::UNREACHABLE,
    ];
    if (in_array(strtolower($status->getCurrentState()), $invalid_states, TRUE)) {
      $this->logger('acquia_contenthub')
        ->error('Webhook url is not valid! Reason: @reason. UUID: @uuid. State: @state. URL: @url.', [
          '@uuid' => $uuid,
          '@reason' => $status->getReason(),
          '@state' => $status->getCurrentState(),
          '@url' => $status->getUrl(),
        ]);
      return FALSE;
    }

    return TRUE;
  }

  /**
   * Returns formatted webhook.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return string
   *   The formatted webhook url.
   */
  protected function getFormattedWebhookUrl(FormStateInterface $form_state): string {
    $webhook = rtrim($form_state->getValue('webhook'), '/');

    if (strpos($webhook, $this->achPath) === FALSE) {
      $webhook .= $this->achPath;
    }

    $form_state->setValue('webhook', $webhook);

    return $webhook;
  }

  /**
   * Registers client.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   */
  protected function register(FormStateInterface $form_state): void {
    try {
      $values = $form_state->getValues();
      if ($client = $this->clientFactory->registerClient(
        $values['client_name'],
        $values['hostname'],
        $values['api_key'],
        $values['secret_key']
      )) {
        // If the client passes back, then we're good.
        $this->client = $client;
        $this->chConnectionManager->setClient($client);
      }
    }
    catch (RequestException $ch_error) {
      // Were we able to connect to the service?
      if ($ch_error->getResponse()) {
        $service_error = json_decode($ch_error->getResponse()->getBody(), TRUE);
        $message = $service_error['error']['message'];
      }
      // We failed during the response, grab the parent request exception.
      else {
        $service_error['error']['code'] = 1;
        $message = $ch_error->getMessage();
      }

      if (isset($service_error['error']['code'])) {
        switch ($service_error['error']['code']) {
          case 4006:
          case 4001:
            $form_state->setErrorByName('client_name', $message);
            break;

          case 401:
            $form_state->setErrorByName('secret_key', $this->t('Access Denied, Reason: %message', ['%message' => $message]));
            break;

          case 1:
            $form_state->setErrorByName('hostname', $message);

          default:
            $form_state->setErrorByName('settings',
              $this->t('There is a problem connecting to Acquia Content Hub. Please ensure that your hostname and credentials are correct.'));
        }
      }
    }
    catch (\Exception $ch_error) {
      $form_state->setErrorByName('settings', $this->t('Unexpected error occurred: @message', ['@message' => $ch_error->getMessage()]));
    }
  }

  /**
   * Ensures client is in sync with the registered one on Content Hub.
   *
   * @return string
   *   The ensured and synced client name.
   *
   * @throws \Exception
   */
  protected function ensureClientNameIsSynced(): string {
    $client_name = $this->settings->getName();
    $uuid = $this->settings->getUuid();
    foreach ($this->client->getClients() as $client) {
      // If its not in-sync secretly change the values and resave the config.
      if ($client['uuid'] === $uuid && $client['name'] !== $client_name) {
        $client_name = $client['name'];
        if ($this->achConfigurations->getProvider() !== 'core_config') {
          $this->messenger()
            ->addMessage($this->t('Warning: client name is out of sync it should be %name. Please manually update your settings file.', ['%name' => $client_name]));
          break;
        }
        $this->achConnection->setClientName($client_name);
      }
    }

    return $client_name;
  }

  /**
   * Remove webhook suppression.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @throws \Exception
   */
  public function removeWebhookSuppression(array &$form, FormStateInterface $form_state): void {
    $webhook_uuid = $this->settings->getWebhook('uuid');
    $success = $this->chConnectionManager->removeWebhookSuppression($webhook_uuid);

    if (!$success) {
      $this
        ->messenger()
        ->addError($this->t('Cannot register with env vars, please check your log messages.'));
    }

    $this
      ->achConnection
      ->unsuppressWebhook();

    $this
      ->messenger()
      ->addMessage($this->t('Registering ACH was successful.'));
  }

  /**
   * Suppress webhook.
   *
   * @param array $form
   *   The form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @throws \Exception
   */
  public function suppressWebhook(array &$form, FormStateInterface $form_state): void {
    $webhook_uuid = $this->settings->getWebhook('uuid');
    $success = $this->chConnectionManager->suppressWebhook($webhook_uuid);

    if (!$success) {
      $this
        ->messenger()
        ->addError($this->t('Cannot register with env vars, please check your log messages.'));
    }

    $this
      ->achConnection
      ->suppressWebhook();

    $this
      ->messenger()
      ->addMessage($this->t('Unregistering ACH was successful.'));
  }

  /**
   * Resets content hub client.
   *
   * @param array $webhook_details
   *   The webhook data.
   * @param string $name
   *   The client name to rest.
   */
  protected function resetClient(array $webhook_details, string $name = ''): void {
    $settings = ObjectFactory::instantiateSettings(
      $name ?: $this->settings->getName(),
      $this->settings->getUuid(),
      $this->settings->getApiKey(),
      $this->settings->getSecretKey(),
      $this->settings->getUrl(),
      $this->settings->getSharedSecret(),
      $webhook_details
    );
    $this->initializeClient($settings);
  }

  /**
   * Initializes content hub client.
   *
   * @param \Acquia\ContentHubClient\Settings|null $settings
   *   The Settings object or null.
   */
  public function initializeClient(?Settings $settings = NULL): void {
    $this->client = $this->clientFactory->getClient($settings);
  }

  /**
   * Returns the account id from remote settings.
   *
   * @return string
   *   The account id, aka. Content Hub subscription id.
   */
  protected function getAccountId(): string {
    try {
      $settings = $this->clientFactory->getClient()->getRemoteSettings();
      return $settings['uuid'] ?? '';
    }
    catch (\Exception $e) {
      $this->logger('acquia_contenthub')
        ->error('Content Hub settings form: error during account id retrieval: {err}', [
          'err' => $e->getMessage(),
        ]);
    }
    return '';
  }

  /**
   * Adds common syndication strategy form elements.
   *
   * @param array $form
   *   The form array to add elements to (passed by reference).
   * @param string $default_syndication_mode
   *   The default syndication mode value.
   */
  protected function addCommonSyndicationStrategyElements(array &$form, string $default_syndication_mode): void {
    $form['syndication_strategy']['syndication_mode'] = [
      '#type' => 'radios',
      '#name' => 'syndication_mode',
      '#title' => $this->t('Syndication Mode'),
      '#options' => [
        ConnectionDetailsInterface::SYNDICATION_MODE_PUSH => $this->t('Push'),
        ConnectionDetailsInterface::SYNDICATION_MODE_PULL => $this->t('Pull'),
      ],
      '#description' => $this->t('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.'),
      '#default_value' => $default_syndication_mode,
    ];

    $form['syndication_strategy']['limit'] = [
      '#type' => 'number',
      '#title' => $this->t('Limit'),
      '#description' => $this->t('Maximum number of items to be fetched from CH service. Maximum value can be 100.'),
      '#default_value' => $this->achConnection->getSyndicationLimit(),
      '#min' => QueueItemInputValidator::RECEIVE_QUEUE_ITEMS_MIN_LIMIT,
      '#max' => QueueItemInputValidator::RECEIVE_QUEUE_ITEMS_MAX_LIMIT,
      '#states' => [
        'visible' => [
          'input[name="syndication_mode"]' => ['value' => ConnectionDetailsInterface::SYNDICATION_MODE_PULL],
        ],
      ],
    ];

    $form['syndication_strategy']['visibility_timeout'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Visibility Timeout'),
      '#description' => $this->t('Visibility timeout for fetched items in service queue. Maximum value can be 336h (14 days).'),
      '#default_value' => $this->achConnection->getVisibilityTimeout(),
      '#states' => [
        'visible' => [
          'input[name="syndication_mode"]' => ['value' => ConnectionDetailsInterface::SYNDICATION_MODE_PULL],
        ],
      ],
    ];
  }

  /**
   * Returns the description for the client_name form element.
   *
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
   *   The description.
   */
  private function getClientNameDescription(): TranslatableMarkup {
    return $this->achConnection->isOverridden('client_name') ?
      $this->t('Client name is disabled as it is overridden in settings.php.') :
      $this->t('A unique client name by which Acquia Content Hub will identify this site.');
  }

}
