<?php
/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

namespace Docuform\Api\Authentication\Repositories;

use DateInterval;
use DateTimeImmutable;
use DateTimeZone;
use DI\DependencyException;
use DI\NotFoundException;
use Docuform\Api\Exception\ApiHttpException;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use Docuform\Api\Authentication\Entities\AccessTokenEntity;
use DI\Container;
use Docuform\Api\Authentication\Entities\ScopeEntity;
use Docuform\Database\DbPDO;
use Docuform\Lib\ApiDateTime;
use Random\RandomException;

class AccessTokenRepository implements AccessTokenRepositoryInterface
{
  private Container $m_container;
  /**
   * AccessTokenRepository constructor.
   *
   * @param Container $container
   *
   * @throws ApiHttpException
   */
  public function __construct(Container $container)
  {
    $this->m_container = $container;
  }


  /**
   * @param AccessTokenEntityInterface $accessTokenEntity
   *
   * @throws DateInvalidTimeZoneException
   * @throws DateMalformedIntervalStringException
   * @throws RandomException
   * @throws DependencyException
   * @throws NotFoundException
   */
  public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity): void
  {
    // Some logic here to save the access token to a database
    /** @var DbPDO $database */
    $database = $this->m_container->get('database');

    $clientId = $accessTokenEntity->getClient()->getIdentifier();

    if (!empty($clientId))
    {
      $ttlMin = (int) $database->getDbValue('ACCAPICLIENTS', 'AccessTokenTTLMin', 'ApiKey=?', [$clientId], 60);

      if ($ttlMin > 0)
      {
        if ($clientId === 'Es4v45qpySQUhsEB')
        {
          $ttlMin = $this->adjustTtlJitter($ttlMin);
        }

        $dtInterval = new DateInterval('PT' . $ttlMin . 'M');

        $accessTokenEntity->setExpiryDateTime((new DateTimeImmutable())->add($dtInterval));
      }
    }
  }


  /**
   * @param int $ttlMin
   *
   * @return int
   *
   * @throws DateInvalidTimeZoneException
   * @throws DateMalformedIntervalStringException
   * @throws RandomException
   */
  private function adjustTtlJitter(int $ttlMin): int
  {
    global $apiSettings;

    /** @var array $apiSettings */
    $startHour = ($apiSettings['mcs_access_token_hour']??12)%24;
    $jitterMin = $apiSettings['mcs_access_token_jitter_min']??180;

    if ($startHour > 0 || $jitterMin > 0)
    {
      $dtNow = new ApiDateTime();
      $dtNow->setTimezone(new DateTimeZone(date_default_timezone_get()));

      $dtExpire = clone $dtNow;
      $dtExpire->add(new DateInterval('PT' . $ttlMin . 'M'));

      $dtExpireDayStartHour = clone $dtExpire;
      $dtExpireDayStartHour->setTime($startHour, 0, 0);

      $dtExpireDayEndJitter = clone $dtExpireDayStartHour;
      $dtExpireDayEndJitter->add(new DateInterval('PT' . $jitterMin . 'M'));

      if ($dtExpire < $dtExpireDayStartHour || $dtExpire > $dtExpireDayEndJitter)
      {
        $jitterRandomMin = random_int(0, $jitterMin);

        if ($dtExpire > $dtExpireDayEndJitter)
        {
          $dtExpire->add(new DateInterval('PT24H'));
        }

        $dtExpire->setTime($startHour, 0, 0);
        $dtExpire->add(new DateInterval('PT' . $jitterRandomMin . 'M'));
      }

      $unixExpire = $dtExpire->getTimestamp();
      $unixNow = $dtNow->getTimestamp();

      $ttlMin = (int) (($unixExpire - $unixNow) / 60);

      if ($ttlMin < 10)
      {
        $ttlMin = 10;
      }
    }

    return $ttlMin;
  }


  /**
   * @param string $tokenId
   */
  public function revokeAccessToken(string $tokenId): void
  {
      // Some logic here to revoke the access token
  }

  /**
   * @param string $tokenId
   *
   * @return bool
   */
  public function isAccessTokenRevoked(string $tokenId): bool
  {
      return false; // Access token hasn't been revoked
  }

  /**
   * {@inheritdoc}
   */
  public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, string|null $userIdentifier = null): AccessTokenEntity|AccessTokenEntityInterface
  {
      $accessToken = new AccessTokenEntity($this->m_container);
      $accessToken->setClient($clientEntity);
      /** @var ScopeEntity $scope */
      foreach ($scopes as $scope)
      {
        $accessToken->addScope($scope);
      }

      if ($userIdentifier !== null)
      {
        $accessToken->setUserIdentifier($userIdentifier);
      }

      return $accessToken;
  }
}
