<?php

declare(strict_types=1);

namespace Drupal\netforum\xWeb;

use Drupal\netforum\xWeb\Exception\AuthFailedException;
use Drupal\netforum\xWeb\Generated\StructType\AuthorizationToken;

/**
 * Base functionality for authentication token handling.
 */
abstract class AuthTokenHandler implements AuthTokenHandlerInterface {

  /**
   * SOAP Functions that do not require an Authorization Token.
   *
   * @var string[]
   */
  protected array $noAuthTokenFunctions = [
    'TestConnection',
    'GetDateTime',
    'Authenticate',
    'AuthenticateLdap',
  ];

  /**
   * Callback to handle calling Authenticate on xWeb.
   *
   * @var callable():bool
   */
  private $authCallback;

  private ?AuthorizationToken $savedAuthorizationToken = NULL;

  /**
   * Creates a new Authorization Token handler with a callback to handle
   * calling Authenticate on xWeb.
   *
   * @param callable():bool $authCallback
   *   Must return true if successful authentication or false if not.
   */
  public function __construct(callable $authCallback) {
    $this->authCallback = $authCallback;
  }

  /**
   * Ensure there is no Authorization Token set for the SOAP calls that do not
   * need one. By default, the SOAP headers are sent on every call. If there
   * is an Authorization Token, save it and restore it after the call finishes
   * so the session is not lost.
   */
  public function preSoapCall(string $name, SoapClient $soapClient, ?array $requestHeaders): void {
    if (in_array($name, $this->noAuthTokenFunctions) && is_array($requestHeaders)) {
      foreach ($requestHeaders as $key => $requestHeader) {
        if ($requestHeader->name === 'AuthorizationToken') {
          $this->savedAuthorizationToken = $requestHeader->data;

          unset($requestHeaders[$key]);

          break;
        }
      }

      $soapClient->__setSoapheaders($requestHeaders);
    }
  }

  /**
   * Restore the Authorization Token if it was saved previously.
   */
  public function postSoapCall(string $name, SoapClient $soapClient, ?array $responseHeaders): void {
    if (in_array($name, $this->noAuthTokenFunctions) && $this->savedAuthorizationToken !== NULL) {
      SoapClientBase::setSoapHeaderOn($soapClient, NetForumXml::XML_NAMESPACE, 'AuthorizationToken', $this->savedAuthorizationToken);
    }
  }

  /**
   * Call Authenticate on xWeb and attempt to authenticate.
   *
   * @throws \Drupal\netforum\xWeb\Exception\AuthFailedException
   * @throws \Exception
   */
  public function authenticate(): void {
    if (!($this->authCallback)()) {
      throw new AuthFailedException('Authentication with xWeb failed.');
    }
  }

  /**
   * Check if an xWeb SOAP Function should have an authorization token.
   */
  protected function isNoAuthTokenFunction(string $functionName): bool {
    return in_array($functionName, $this->noAuthTokenFunctions);
  }

}
