<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\OAuth\Repositories;

use JTL\DB\DbInterface;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use Plugin\tnt_mcp\src\Mcp\OAuth\Entities\AccessTokenEntity;
use stdClass;

/**
 * DB-backed access-token store (table tnt_mcp_oauth_access_token). The token itself is a signed
 * JWT held by the client; this table records its identifier + scopes so it can be revoked and so
 * the resource server can reject a token whose id is missing/revoked (deny-by-default).
 */
final class AccessTokenRepository implements AccessTokenRepositoryInterface
{
    public function __construct(private readonly DbInterface $db)
    {
    }

    /**
     * @param list<ScopeEntityInterface> $scopes
     */
    public function getNewToken(
        ClientEntityInterface $clientEntity,
        array $scopes,
        ?string $userIdentifier = null,
    ): AccessTokenEntityInterface {
        $token = new AccessTokenEntity();
        $token->setClient($clientEntity);
        foreach ($scopes as $scope) {
            $token->addScope($scope);
        }
        // An empty user identifier is no identity at all — leaving it unset keeps the token
        // unbound rather than recording '' as the approving admin.
        if ($userIdentifier !== null && $userIdentifier !== '') {
            $token->setUserIdentifier($userIdentifier);
        }

        return $token;
    }

    public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity): void
    {
        $row               = new stdClass();
        $row->cToken       = $accessTokenEntity->getIdentifier();
        $row->cClientId    = $accessTokenEntity->getClient()->getIdentifier();
        $row->cUserId      = (string)$accessTokenEntity->getUserIdentifier();
        $row->cScopes      = ScopeJson::encode(\array_values($accessTokenEntity->getScopes()));
        $row->dExpires     = $accessTokenEntity->getExpiryDateTime()->format('Y-m-d H:i:s');
        $row->nRevoked     = 0;
        $row->dCreated     = (new \DateTimeImmutable())->format('Y-m-d H:i:s');

        $this->db->insert('tnt_mcp_oauth_access_token', $row);
    }

    public function revokeAccessToken(string $tokenId): void
    {
        $this->db->queryPrepared(
            'UPDATE tnt_mcp_oauth_access_token SET nRevoked = 1 WHERE cToken = :id',
            ['id' => $tokenId],
        );
    }

    public function isAccessTokenRevoked(string $tokenId): bool
    {
        $row = $this->db->getSingleObject(
            'SELECT nRevoked FROM tnt_mcp_oauth_access_token WHERE cToken = :id',
            ['id' => $tokenId],
        );

        // Unknown token id is treated as revoked — deny-by-default.
        return $row === null || (int)$row->nRevoked === 1;
    }
}
