<?php

declare(strict_types=1);

namespace Drupal\netforum;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\netforum\xWeb\Generated\ClassMap;
use Drupal\netforum\xWeb\Generated\GeneratedInfo;
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\InstanceAuthTokenHandler;
use Drupal\netforum\xWeb\NetForumXml;
use Psr\Log\LoggerInterface;

/**
 * Service to be used by other Drupal modules to interact with
 * xWeb SOAP Web Service.
 *
 * @phpcs:disable Drupal.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
 */
class NetForumXmlService implements NetForumXmlServiceInterface {

  private const HTTP_USER_AGENT = 'DrupalNetForum/1';

  private const WSDL_FILENAME = 'netForumXML.wsdl';

  protected ?NetForumXmlServiceOptions $serviceOptions = NULL;

  protected ?NetForumXml $netForumXML = NULL;

  private readonly ImmutableConfig $config;

  private readonly LoggerInterface $logger;

  private readonly string $modulePath;

  public function __construct(ConfigFactoryInterface $configFactory, ModuleExtensionList $moduleExtensionList, LoggerInterface $logger) {
    $this->config = $configFactory->get('netforum.settings');

    $this->modulePath = $moduleExtensionList->getPath('netforum');

    $this->logger = $logger;
  }

  /**
   * Gets a reusable instance of NetForumXml that is configured based on
   * configuration.
   */
  public function getClient(): NetForumXml {
    if (is_null($this->netForumXML)) {
      $clientOptions = $this->getClientOptions();
      $this->netForumXML = $this->newClient($clientOptions);
    }

    return $this->netForumXML;
  }

  /**
   * Get the options for a NetForumXml instance based on settings set for the
   * NetForum module.
   */
  public function getClientOptions(): NetForumXmlServiceOptions {
    if (is_null($this->serviceOptions)) {
      $this->serviceOptions = new NetForumXmlServiceOptions();
      $this->serviceOptions->setClassMap(ClassMap::get())
        ->setWsdl($this->modulePath . DIRECTORY_SEPARATOR . self::WSDL_FILENAME)
        ->setAuthTokenHandler(new InstanceAuthTokenHandler($this->xWebAuthenticate(...)))
        ->setEndpoint($this->config->get('xweb_endpoint'))
        ->setUserAgent(self::HTTP_USER_AGENT)
        ->setRequestTimeout($this->config->get('request_timeout'))
        ->setWsdlCache(SoapWsdlCacheType::from($this->config->get('wsdl_cache')))
        ->setHttpKeepalive($this->config->get('http_keepalive'))
        ->setHttpCompression($this->config->get('http_compression'))
        ->setVerifyTls($this->config->get('verify_tls'))
        ->setHttpAuth($this->config->get('http_auth_enabled'), SoapHttpAuthenticationType::from($this->config->get('http_auth_type')), $this->config->get('http_auth_username'), $this->config->get('http_auth_password'))
        ->setLogHandler($this->logger)
        ->setDebugLogAll($this->config->get('debug_log_all'))
        ->setProxyServer($this->config->get('proxy_enabled'), $this->config->get('proxy_hostname'), $this->config->get('proxy_port'), $this->config->get('proxy_username'), $this->config->get('proxy_password'));
    }

    return $this->serviceOptions;
  }

  /**
   * Creates a new client with custom options. Each returned client is a new
   * instance.
   */
  public function newClient(NetForumXmlServiceOptions $options): NetForumXml {
    return new NetForumXml($options->getOptions());
  }

  public function getGeneratedDate(): string {
    return GeneratedInfo::DATE;
  }

  public function getGeneratedNFVersion(): string {
    return GeneratedInfo::NF_VERSION;
  }

  public function getGeneratedRelease(): string {
    return GeneratedInfo::RELEASE;
  }

  /**
   * Attempt to call Authenticate and receive an Authorization token to make
   * subsequent xWeb calls.
   *
   * @throws \Exception
   */
  private function xWebAuthenticate(): bool {
    if (!($this->netForumXML instanceof NetForumXml)) {
      throw new \RuntimeException('Attempting to use a NetForumXml client that has not been instantiated!');
    }

    $authenticateResponse = $this->netForumXML->Authenticate(new Authenticate($this->config->get('xweb_username'), $this->config->get('xweb_password')));
    if ($authenticateResponse instanceof AuthenticateResponse) {
      $soapHeaders = $this->netForumXML->getOutputHeaders();
      foreach ($soapHeaders as $soapHeader) {
        if ($soapHeader instanceof AuthorizationToken) {
          return TRUE;
        }
      }
    }

    return FALSE;
  }

}
