<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\OAuth;

use DateInterval;
use JTL\DB\DbInterface;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\CryptKey;
use Plugin\tnt_mcp\src\Mcp\OAuth\CompressedAuthCodeGrant as AuthCodeGrant;
use League\OAuth2\Server\Grant\RefreshTokenGrant;
use League\OAuth2\Server\ResourceServer;
use Plugin\tnt_mcp\src\Mcp\OAuth\Repositories\AccessTokenRepository;
use Plugin\tnt_mcp\src\Mcp\OAuth\Repositories\AuthCodeRepository;
use Plugin\tnt_mcp\src\Mcp\OAuth\Repositories\ClientRepository;
use Plugin\tnt_mcp\src\Mcp\OAuth\Repositories\RefreshTokenRepository;
use Plugin\tnt_mcp\src\Mcp\OAuth\Repositories\ScopeRepository;

/**
 * Wires league/oauth2-server into an OAuth 2.1 authorization-code-with-PKCE + refresh-token server.
 * PKCE (S256) is mandatory for public clients — league enforces it by default and we do not relax
 * it — so the connector flow is a genuine OAuth 2.1 flow, never an implicit/token-in-URL shortcut.
 * TTLs are conservative: 10-minute auth codes, 1-hour access tokens, 1-month refresh tokens.
 */
final class OAuthServerFactory
{
    private const AUTH_CODE_TTL     = 'PT10M';
    private const ACCESS_TOKEN_TTL  = 'PT1H';
    private const REFRESH_TOKEN_TTL = 'P1M';

    public function __construct(
        private readonly DbInterface $db,
        private readonly KeyManager $keys,
    ) {
    }

    public function authorizationServer(): AuthorizationServer
    {
        $scopeRepository = new ScopeRepository();

        $server = new AuthorizationServer(
            new ClientRepository($this->db),
            new AccessTokenRepository($this->db),
            $scopeRepository,
            // The key is handed over as PEM CONTENTS, never as a path: it lives encrypted in the
            // database ({@see KeyStore}), so there is no file whose permissions league could check
            // — it skips the assertion by itself for a contents-constructed key.
            new CryptKey($this->keys->privateKey(), null, false),
            $this->keys->encryptionKey(),
        );
        $server->setDefaultScope(ScopeCatalog::defaultScope());

        $authCodeGrant = new AuthCodeGrant(
            new AuthCodeRepository($this->db),
            new RefreshTokenRepository($this->db),
            new DateInterval(self::AUTH_CODE_TTL),
        );
        $authCodeGrant->setRefreshTokenTTL(new DateInterval(self::REFRESH_TOKEN_TTL));
        $server->enableGrantType($authCodeGrant, new DateInterval(self::ACCESS_TOKEN_TTL));

        $refreshGrant = new RefreshTokenGrant(new RefreshTokenRepository($this->db));
        $refreshGrant->setRefreshTokenTTL(new DateInterval(self::REFRESH_TOKEN_TTL));
        $server->enableGrantType($refreshGrant, new DateInterval(self::ACCESS_TOKEN_TTL));

        return $server;
    }

    public function resourceServer(): ResourceServer
    {
        return new ResourceServer(
            new AccessTokenRepository($this->db),
            new CryptKey($this->keys->publicKey(), null, false),
        );
    }
}
