<?php

declare(strict_types=1);

namespace Drupal\netforum\Form;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\netforum\NetForumXmlServiceInterface;
use Drupal\netforum\SoapHttpAuthenticationType;
use Drupal\netforum\SoapWsdlCacheType;
use Drupal\netforum\xWeb\Generated\StructType\Authenticate;
use Drupal\netforum\xWeb\Generated\StructType\AuthenticateResponse;
use Drupal\netforum\xWeb\Generated\StructType\AuthorizationToken;
use Drupal\netforum\xWeb\Generated\StructType\GetDateTime;
use Drupal\netforum\xWeb\Generated\StructType\GetDateTimeResponse;
use Drupal\netforum\xWeb\Generated\StructType\GetVersion;
use Drupal\netforum\xWeb\Generated\StructType\GetVersionResponse;
use Drupal\netforum\xWeb\Generated\StructType\TestConnection;
use Drupal\netforum\xWeb\Generated\StructType\TestConnectionResponse;
use Drupal\netforum\xWeb\NullAuthTokenHandler;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Form to configure settings for NetForum.
 */
class NetforumSettingsForm extends ConfigFormBase {

  protected NetForumXmlServiceInterface $netforumXmlService;

  /**
   * Stores messages from test results.
   *
   * @var \Drupal\Core\StringTranslation\TranslatableMarkup[]
   */
  private array $testConnectionResult = [];

  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, NetForumXmlServiceInterface $netforumXmlService) {
    parent::__construct($config_factory, $typedConfigManager);

    $this->netforumXmlService = $netforumXmlService;
  }

  public static function create(ContainerInterface $container): static {
    return new static($container->get('config.factory'), $container->get('config.typed'), $container->get('netforum.xweb.netforum_xml'));
  }

  public function getFormId(): string {
    return 'netforum_settings_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state): array {
    $config = $this->config('netforum.settings');

    $form['xweb_status'] = [
      '#type' => 'details',
      '#title' => $this->t("xWeb info"),
      '#open' => TRUE,
    ];

    $form['xweb_status']['generated_info'] = [
      '#type' => 'inline_template',
      '#template' => <<<'ENDL'
        <h3>Generated SDK Info</h3>
        <p><strong>NetForum Version:</strong> {{ nf_version }}</p>
        <p><strong>Date:</strong> {{ date }}</p>
        <p><strong>Release:</strong> {{ release }}</p>
      ENDL,
      '#context' => [
        'nf_version' => $this->netforumXmlService->getGeneratedNFVersion(),
        'release' => $this->netforumXmlService->getGeneratedRelease(),
        'date' => $this->netforumXmlService->getGeneratedDate(),
      ],
    ];

    $form['xweb_status']['test_connection'] = [
      '#type' => 'submit',
      '#value' => $this->t("Test connection"),
      '#button_type' => 'secondary',
      '#name' => 'xweb-status-test-connection',
      '#attributes' => [
        'title' => $this->t("Test connecting to xWeb. Make sure to save any changes before testing!"),
      ],
      '#limit_validation_errors' => [],
      '#submit' => ['::testConnectionSubmit'],
      '#ajax' => [
        'callback' => '::testConnectionAjax',
        'wrapper' => 'xweb-status-test-connection-result',
        'effect' => 'slide',
        'progress' => [
          'type' => 'throbber',
        ],
      ],
    ];

    $form['xweb_status']['test_connection_result'] = [
      '#type' => 'container',
      '#id' => 'xweb-status-test-connection-result',
    ];

    if (count($this->testConnectionResult) > 0) {
      $form['xweb_status']['test_connection_result']['tests'] = [
        '#theme' => 'item_list',
        '#title' => $this->t('Test connection result'),
        '#list_type' => 'ul',
        '#items' => $this->testConnectionResult,
      ];
    }

    $form['xweb_connection'] = [
      '#type' => 'details',
      '#title' => $this->t("xWeb connection"),
      '#open' => TRUE,
    ];

    $form['xweb_connection']['xweb_endpoint'] = [
      '#type' => 'url',
      '#title' => $this->t('Endpoint'),
      '#description' => $this->t('The xWeb endpoint, e.g https://www.example.com/xWeb/Secure/netForumXML.asmx.'),
      '#default_value' => $config->get('xweb_endpoint'),
      '#required' => TRUE,
    ];

    $form['xweb_connection']['xweb_username'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Username'),
      '#description' => $this->t('The username of the xWeb user.'),
      '#default_value' => $config->get('xweb_username'),
      '#required' => TRUE,
    ];

    $form['xweb_connection']['xweb_password'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Password'),
      '#description' => $this->t('The password for the xWeb user.'),
      '#default_value' => $config->get('xweb_password'),
      '#required' => TRUE,
    ];

    $form['xweb_connection']['request_timeout'] = [
      '#type' => 'number',
      '#title' => $this->t('Request timeout'),
      '#description' => $this->t('How long to wait for a response for an xWeb request.'),
      '#field_suffix' => $this->t('seconds'),
      '#default_value' => $config->get('request_timeout'),
      '#min' => 0,
      '#size' => 4,
      '#required' => TRUE,
    ];

    $form['xweb_connection']['wsdl_cache'] = [
      '#type' => 'select',
      '#title' => $this->t('WSDL cache'),
      '#description' => $this->t('Determines how the internal NetForum xWeb WSDL file is cached. PHP will parse the WSDL file, serialize the resulting object and store it for reuse on future requests. <em>DiskAndMemory</em> is recommended but can be changed for special situations. Refer to the <a href="https://www.php.net/manual/en/soapclient.construct.php#soapclient.construct.options.cache-wsdl" rel="noreferrer" target="_blank">PHP documentation</a> for an explanation of each option.'),
      '#options' => [
        SoapWsdlCacheType::None->value => $this->t('None'),
        SoapWsdlCacheType::Disk->value => $this->t('Disk'),
        SoapWsdlCacheType::Memory->value => $this->t('Memory'),
        SoapWsdlCacheType::DiskAndMemory->value => $this->t('DiskAndMemory'),
      ],
      '#default_value' => $config->get('wsdl_cache'),
    ];

    $form['xweb_connection']['http_keepalive'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable using HTTP keep-alive'),
      '#description' => $this->t('Enables using the same TCP connection for
      multiple HTTP SOAP requests. This reduces latency and is recommended to have enabled.'),
      '#default_value' => $config->get('http_keepalive'),
    ];

    $form['xweb_connection']['http_compression'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable using HTTP compression'),
      '#description' => $this->t('Enables using HTTP compression (Gzip) to increase
      transfer speed of requests and responses. This should be enabled, unless you
      are running this site on the same webserver as xWeb.'),
      '#default_value' => $config->get('http_compression'),
    ];

    $form['xweb_connection']['verify_tls'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Verify TLS certificate'),
      '#description' => $this->t('Check if the TLS certificate used for HTTPS is valid. All xWeb message exchanges should occur over HTTPS. This
      setting should not be disabled in production environments.'),
      '#default_value' => $config->get('verify_tls'),
    ];

    $form['xweb_connection']['http_auth'] = [
      '#type' => 'details',
      '#title' => $this->t("HTTP authentication"),
      '#description' => $this->t('If your xWeb instance is behind HTTP Basic or Digest Authentication, configure the settings below.'),
      '#open' => $config->get('http_auth_enabled'),
    ];

    $form['xweb_connection']['http_auth']['http_auth_enabled'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable HTTP authentication'),
      '#default_value' => $config->get('http_auth_enabled'),
    ];

    $form['xweb_connection']['http_auth']['http_auth_type'] = [
      '#type' => 'select',
      '#title' => $this->t('Type'),
      '#description' => $this->t('The type of the HTTP Authentication.'),
      '#options' => [
        SoapHttpAuthenticationType::Basic->value => $this->t('Basic'),
        SoapHttpAuthenticationType::Digest->value => $this->t('Digest'),
      ],
      '#default_value' => $config->get('http_auth_type'),
      '#states' => [
        'enabled' => [
          'input[data-drupal-selector="edit-http-auth-enabled"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['xweb_connection']['http_auth']['http_auth_username'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Username'),
      '#description' => $this->t('The username to use for HTTP authentication.'),
      '#default_value' => $config->get('http_auth_username'),
      '#states' => [
        'enabled' => [
          'input[data-drupal-selector="edit-http-auth-enabled"]' => ['checked' => TRUE],
        ],
        'required' => [
          'input[data-drupal-selector="edit-http-auth-enabled"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['xweb_connection']['http_auth']['http_auth_password'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Password'),
      '#description' => $this->t('The password to use for  HTTP authentication.'),
      '#default_value' => $config->get('http_auth_password'),
      '#states' => [
        'enabled' => [
          'input[data-drupal-selector="edit-http-auth-enabled"]' => ['checked' => TRUE],
        ],
        'required' => [
          'input[data-drupal-selector="edit-http-auth-enabled"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['xweb_debug'] = [
      '#type' => 'details',
      '#title' => $this->t('xWeb debugging'),
      '#open' => TRUE,
    ];

    $form['xweb_debug']['debug_log_all'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Log all requests and responses'),
      '#default_value' => $config->get('debug_log_all'),
      '#description' => $this->t('Enable logging of all requests sent and responses
      received to Drupal\'s log. This should only be used for debugging and not enabled on
      production environments, since it will save sensitive information into the log and
      generate lots of log messages.'),
    ];

    $form['xweb_proxy'] = [
      '#title' => "xWeb proxy server",
      '#type' => 'details',
      '#description' => $this->t('The following settings are for a HTTP proxy
      server to be used in front of the xWeb endpoint specified above. You can use this
      feature to aid development by monitoring all xWeb requests and responses.'),
      '#open' => $config->get('proxy_enabled'),
    ];

    $form['xweb_proxy']['proxy_enabled'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Enable HTTP proxy server'),
      '#description' => $this->t('Enable sending all xWeb requests through the proxy server specified below.'),
      '#default_value' => $config->get('proxy_enabled'),
    ];

    $form['xweb_proxy']['proxy_hostname'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Hostname'),
      '#description' => $this->t('The hostname of the proxy server to use.'),
      '#default_value' => $config->get('proxy_hostname'),
      '#required' => TRUE,
      '#states' => [
        'enabled' => [
          'input[data-drupal-selector="edit-proxy-enabled"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['xweb_proxy']['proxy_port'] = [
      '#type' => 'number',
      '#title' => $this->t('Port'),
      '#description' => $this->t('The port of the proxy server to use.'),
      '#default_value' => $config->get('proxy_port'),
      '#size' => 4,
      '#required' => TRUE,
      '#min' => 0,
      '#max' => 65536,
      '#states' => [
        'enabled' => [
          'input[data-drupal-selector="edit-proxy-enabled"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['xweb_proxy']['proxy_username'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Username'),
      '#description' => $this->t('Optional. Username if your proxy server requires authentication.'),
      '#default_value' => $config->get('proxy_username'),
      '#states' => [
        'enabled' => [
          'input[data-drupal-selector="edit-proxy-enabled"]' => ['checked' => TRUE],
        ],
      ],
    ];

    $form['xweb_proxy']['proxy_password'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Password'),
      '#description' => $this->t('Optional. Password if your proxy server requires authentication.'),
      '#default_value' => $config->get('proxy_password'),
      '#states' => [
        'enabled' => [
          'input[data-drupal-selector="edit-proxy-enabled"]' => ['checked' => TRUE],
        ],
      ],
    ];

    return parent::buildForm($form, $form_state);
  }

  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $this->config('netforum.settings')
      ->set('xweb_endpoint', $form_state->getValue('xweb_endpoint'))
      ->set('xweb_username', $form_state->getValue('xweb_username'))
      ->set('xweb_password', $form_state->getValue('xweb_password'))
      ->set('request_timeout', $form_state->getValue('request_timeout'))
      ->set('wsdl_cache', $form_state->getValue('wsdl_cache'))
      ->set('http_keepalive', $form_state->getValue('http_keepalive'))
      ->set('http_compression', $form_state->getValue('http_compression'))
      ->set('verify_tls', $form_state->getValue('verify_tls'))
      ->set('http_auth_enabled', $form_state->getValue('http_auth_enabled'))
      ->set('http_auth_type', $form_state->getValue('http_auth_type'))
      ->set('http_auth_username', $form_state->getValue('http_auth_username'))
      ->set('http_auth_password', $form_state->getValue('http_auth_password'))
      ->set('debug_log_all', $form_state->getValue('debug_log_all'))
      ->set('proxy_enabled', $form_state->getValue('proxy_enabled'))
      ->set('proxy_hostname', $form_state->getValue('proxy_hostname'))
      ->set('proxy_port', $form_state->getValue('proxy_port'))
      ->set('proxy_username', $form_state->getValue('proxy_username'))
      ->set('proxy_password', $form_state->getValue('proxy_password'))
      ->save();

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

  public function testConnectionSubmit(array &$form, FormStateInterface $form_state): void {
    try {
      $clientOptions = $this->netforumXmlService->getClientOptions();
      $clientOptions->setAuthTokenHandler(new NullAuthTokenHandler());

      $this->testConnectionResult[] = $this->t('Successfully created client using endpoint %endpoint.', ['%endpoint' => $clientOptions->getEndpoint()]);

      $client = $this->netforumXmlService->newClient($clientOptions);

      // Test calling xWeb TestConnection() which returns 'Success' if the
      // application can connect to the database server or 'Failure' if not.
      $testConnectionResponse = $client->TestConnection(new TestConnection());
      if ($testConnectionResponse instanceof TestConnectionResponse) {
        if ($testConnectionResponse->getTestConnectionResult() === 'Success') {
          $this->testConnectionResult[] = $this->t('Called TestConnection() and received <em>Success</em> response.');
        }
        elseif ($testConnectionResponse->getTestConnectionResult() === 'Failure') {
          $this->testConnectionResult[] = $this->t('Called TestConnection() and received <em>Failure</em> response. xWeb cannot connect to the database server?');
        }
        else {
          // This shouldn't happen...
          $this->testConnectionResult[] = $this->t('Called TestConnection() and received unexpected response.');
        }
      }
      else {
        $this->testConnectionResult[] = $this->t('Calling TestConnection() failed. Error message: %error.', [
          '%error' => $client->getLastErrorMessage() ?? 'No error message available',
        ]);
      }

      // Get the current datetime on the xWeb server.
      $getDateTimeResponse = $client->GetDateTime(new GetDateTime());
      if ($getDateTimeResponse instanceof GetDateTimeResponse) {
        $this->testConnectionResult[] = $this->t('Called GetDateTime() and datetime on server is %datetime.', ['%datetime' => $getDateTimeResponse->getGetDateTimeResult()]);
      }
      else {
        $this->testConnectionResult[] = $this->t('Calling GetDateTime() failed. Error message: %error.', [
          '%error' => $client->getLastErrorMessage() ?? 'No error message available',
        ]);
      }

      // Attempt to Authenticate() using the saved credentials.
      $authToken = NULL;
      $config = $this->config('netforum.settings');
      $authResponse = $client->Authenticate(new Authenticate($config->get('xweb_username'), $config->get('xweb_password')));
      if ($authResponse instanceof AuthenticateResponse) {
        // The Authorization Token is stored in a SoapHeader.
        $soapHeaders = $client->getOutputHeaders();
        foreach ($soapHeaders as $soapHeader) {
          if ($soapHeader instanceof AuthorizationToken) {
            $authToken = $soapHeader->getToken();

            // Since we are using NullAuthTokenHandler, need to set the token
            // manually.
            $client->setSoapHeaderAuthorizationToken($soapHeader);
          }
        }

        $this->testConnectionResult[] = $this->t('Called Authenticate() successfully, received token %token.', ['%token' => $authToken ?? 'Token missing']);
      }
      else {
        $this->testConnectionResult[] = $this->t('Calling Authenticate() failed. Error message: %error.', [
          '%error' => $client->getLastErrorMessage() ?? 'No error message available',
        ]);
      }

      // Get version information.
      // @phpcs:disable Drupal.Arrays.Array.ArrayIndentation
      $getVersionResponse = $client->GetVersion(new GetVersion());
      if ($getVersionResponse instanceof GetVersionResponse) {
        $this->testConnectionResult[] = $this->t('Called GetVersion() successfully. %build using database %database on server %server.', [
          '%build' => $getVersionResponse->getGetVersionResult()
              ?->getBuild() ?? 'missing',
          '%database' => $getVersionResponse->getGetVersionResult()
              ?->getDatabase() ?? 'missing',
          '%server' => $getVersionResponse->getGetVersionResult()
              ?->getServer() ?? 'missing',
        ]);
      }
      else {
        $this->testConnectionResult[] = $this->t('Calling GetVersion() failed. Error message: %error', [
          '%error' => $client->getLastErrorMessage() ?? 'No error message available.',
        ]);
      }

      // Check if sliding authentication is being used.
      $lastAuthToken = NULL;
      $soapHeaders = $client->getOutputHeaders();
      foreach ($soapHeaders as $soapHeader) {
        if ($soapHeader instanceof AuthorizationToken) {
          $lastAuthToken = $soapHeader->getToken();
        }
      }

      if ($lastAuthToken !== $authToken) {
        // A new Token was returned with the last SOAP call.
        $this->testConnectionResult[] = $this->t('<strong>Notice:</strong> Authentication token expiration policy is set to <em>Sliding</em>.');
      }
    }
    catch (\Exception $e) {
      $this->testConnectionResult[] = $this->t('An exception occurred while testing. Error message: %error', [
        '%error' => $e->getMessage(),
      ]);
    }

    $form_state->setRebuild();
  }

  public function testConnectionAjax(array &$form, FormStateInterface $form_state): array {
    return $form['xweb_status']['test_connection_result'];
  }

  protected function getEditableConfigNames(): array {
    return ['netforum.settings'];
  }

}
