<?php

namespace Docuform\Api\Authentication;

use DI\DependencyException;
use DI\NotFoundException;
use Docuform\Api\Authentication\OAuth2Server\AuthorizationServerMps;
use Docuform\Api\Authentication\Repositories\UserRepository;
use Docuform\Api\Exception\ApiHttpException;
use Docuform\Api\HttpRequestHandler;
use Docuform\Database\DbPDO;
use Exception;
use JsonException;
use Nyholm\Psr7\Stream;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class Wellknown extends HttpRequestHandler
{
  /**
   * @param   ServerRequestInterface  $request
   *
   * @return  array
   */
  public function getAllowedUserTypes($request): array
  {
    return [];
  }

  /**
   * @param   ServerRequestInterface  $request
   *
   * @return  array
   */
  public function getRequiredScopes($request): array
  {
    return [
      'allOf' => [],
      'anyOf' => []
    ];
  }

  /**
   * @param   ServerRequestInterface  $request
   * @param   ResponseInterface       $response
   * @param   array                   $arguments
   *
   * @return int HTTP code
   *
   * @throws DependencyException
   * @throws NotFoundException
   */
  public function handleRequest($request, &$response, $arguments): int
  {
    $code = 500;

    /** @var AuthorizationServerMps $server */
    $server  = $this->getContainer()->get('authserver');

    if ($server !== null)
    {
      try
      {
        $errDescription = '';

        $path = $request->getUri()->getPath();

        if (str_ends_with(strtolower($path), '/.well-known/oauth-authorization-server'))
        {
          return $this->handleWellKnownOauthAuthorizationServer($request, $response);
        }

        $json = json_encode('Not Found', JSON_UNESCAPED_SLASHES);
        $response->getBody()->write($json);
        return 404;
      }
      catch (ApiHttpException $exception)
      {
        $code = $exception->getCode();

        $this->getLogger()->error($exception->getMessage() . ': ' . $exception->getDescription());

        $errorMessage = $exception->getMessage();
        $errDescription = $exception->getDescription();
      }
      catch (Exception $exception)
      {
        $this->getLogger()->error($exception->getMessage());

        $errorMessage = 'Server error';
      }

      if (!empty($errorMessage))
      {
        $jsPayload = ['error' => $errorMessage, 'error_description' => $errDescription];

        $bodyStream = Stream::create();

        $bodyStream->write(json_encode($jsPayload, JSON_UNESCAPED_SLASHES));

        $response = $response->withStatus($code)->withHeader('Content-Type', 'application/json')->withBody($bodyStream);
      }
    }

    return $code;
  }


  /**
   * @param ServerRequestInterface $request
   * @param ResponseInterface $response
   *
   * @return int
   * @throws DependencyException
   * @throws NotFoundException
   * @throws JsonException
   */
  private function handleWellKnownOauthAuthorizationServer(ServerRequestInterface $request, ResponseInterface & $response): int
  {
    $jsonData = [];

    try
    {
      $url = $request->getUri();
      $position = strpos($url, '/.well-known/oauth-authorization-server');
      if ($position !== false)
      {
        $urlPrefix = substr($url, 0, $position).'/v'.(int)RESTAPI_VERSION;
      }

      if (!empty($urlPrefix))
      {
        $jsonData = [
          'issuer' => $urlPrefix,
          'authorization_endpoint' => $urlPrefix . '/authorize',
          'token_endpoint' => $urlPrefix . '/token',
          'jwks_uri' => $urlPrefix . '/certs',
          'response_types_supported' => ['code', 'token'],
          'scopes_supported' => ['admin', 'server_write', 'server_read', 'eshop', 'client_write', 'client_read', 'bulk', 'accounting'],
          'grant_types_supported' => ['authorization_code', 'refresh_token'],
          'code_challenge_methods_supported' => ['S256']
        ];
      }

      $response = $this->handleAuthorizationHeaderCheck($request, $response);

      $response = $this->handleAccountingHeaderCheck($request, $response);

      $code = 200;
    }
    catch (Exception $e)
    {
      $jsonData = [];
      $code = 500;
      $this->getLogger()->error('Exception while generating .well-known/oauth-authorization-server: ' . $e->getMessage());
    }

    $json = json_encode($jsonData, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);

    $response->getBody()->write($json);

    return $code;
  }

  /**
   * @param ServerRequestInterface $request
   * @param ResponseInterface $response
   *
   * @return ResponseInterface
   * @throws DependencyException
   * @throws NotFoundException
   */
  private function handleAccountingHeaderCheck(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
  {
    $accountingHeader = strtolower($request->getHeaderLine('X-MCS-Accounting'));

    if ($accountingHeader !== 'true')
    {
      return $response;
    }

    $accountingResult = 'Invalid card reader password';
    $cardReaderPassword = $request->getHeaderLine('X-MCS-CReader-Password');

    if (empty($cardReaderPassword))
    {
      return $response->withHeader('X-MCS-Accounting', 'Missing card reader password');
    }

    /** @var DbPDO $database */
    $database = $this->getContainer()->get('database');

    $dbPassword = $database->getNonUtf8Charset() ? mb_convert_encoding($cardReaderPassword, 'ISO-8859-1', 'UTF-8') : $cardReaderPassword;
    $dbPasswordMd5 = md5($dbPassword);
    $cardReaderPasswordDb = $database->getDbValue('ACCSYSTEM', 'sValue', "sSection='CardReader' AND sKey='Password'", [], '');

    if ($cardReaderPasswordDb !== md5('') && $dbPasswordMd5 === $cardReaderPasswordDb)
    {
      $accountingResult = 'true';
    }
    else
    {
      sleep(1);
    }

    return $response->withHeader('X-MCS-Accounting', $accountingResult);
  }

  /**
   * @param ServerRequestInterface $request
   * @param ResponseInterface $response
   *
   * @return ResponseInterface
   * @throws DependencyException
   * @throws NotFoundException
   */
  private function handleAuthorizationHeaderCheck(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
  {
    $authHeader = $request->getHeaderLine('Authorization');

    if (empty($authHeader))
    {
      return $response;
    }

    $authToken = substr($authHeader, 7);

    if (empty($authToken))
    {
      return $response;
    }

    $userRepository = new UserRepository($this->getContainer());
    $tokenValid = $userRepository->checkAuthToken($authToken);

    return $response->withHeader('X-MCS-Authorization', $tokenValid ? 'true' : 'false');
  }
}