<?php

declare(strict_types=1);

namespace Butschster\ContextGenerator\McpServer;

use Butschster\ContextGenerator\McpServer\Middleware\AuthMiddleware;
use Butschster\ContextGenerator\McpServer\Middleware\ExceptionHandlerMiddleware;
use Butschster\ContextGenerator\McpServer\Middleware\LoggerMiddleware;
use GuzzleHttp\Client;
use Laminas\Diactoros\RequestFactory;
use Laminas\Diactoros\ResponseFactory;
use Laminas\Diactoros\StreamFactory;
use Mcp\Server\Authentication\Contract\OAuthRegisteredClientsStoreInterface;
use Mcp\Server\Authentication\Contract\OAuthServerProviderInterface;
use Mcp\Server\Authentication\Contract\OAuthTokenVerifierInterface;
use Mcp\Server\Authentication\Dto\OAuthClientInformation;
use Mcp\Server\Authentication\Dto\OAuthMetadata;
use Mcp\Server\Authentication\Dto\OAuthProtectedResourceMetadata;
use Mcp\Server\Authentication\Handler\AuthorizeHandler;
use Mcp\Server\Authentication\Handler\MetadataHandler;
use Mcp\Server\Authentication\Handler\RegisterHandler;
use Mcp\Server\Authentication\Handler\RevokeHandler;
use Mcp\Server\Authentication\Handler\TokenHandler;
use Mcp\Server\Authentication\Provider\GenericTokenVerifier;
use Mcp\Server\Authentication\Provider\ProxyEndpoints;
use Mcp\Server\Authentication\Provider\ProxyProvider;
use Mcp\Server\Authentication\Provider\TokenIntrospectionClient;
use Mcp\Server\Authentication\Provider\TokenIntrospectionConfig;
use Mcp\Server\Authentication\Router\AuthRouterOptions;
use Mcp\Server\Authentication\Router\McpAuthRouter;
use Mcp\Server\Authentication\Storage\InMemoryClientRepository;
use Mcp\Server\Transports\Middleware\CorsMiddleware;
use Mcp\Server\Transports\Middleware\ProxyAwareMiddleware;
use Psr\Container\ContainerInterface;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Boot\EnvironmentInterface;
use Spiral\Config\ConfiguratorInterface;
use Spiral\McpServer\MiddlewareRegistryInterface;

final class HttpTransportBootloader extends Bootloader
{
    public function __construct(
        private readonly ConfiguratorInterface $configurator,
    ) {}

    #[\Override]
    public function defineSingletons(): array
    {
        $requestFactory = new RequestFactory();
        $streamFactory = new StreamFactory();
        $responseFactory = new ResponseFactory();
        $httpClient = new Client();

        return [
            // OAuth Token Validator
            OAuthTokenVerifierInterface::class => static fn() => new GenericTokenVerifier(
                config: TokenIntrospectionConfig::forGitHub(),
                client: new TokenIntrospectionClient(
                    httpClient: $httpClient,
                    requestFactory: $requestFactory,
                    streamFactory: $streamFactory,
                ),
            ),

            OAuthRegisteredClientsStoreInterface::class => static function (
                EnvironmentInterface $env,
            ) {
                $store = new InMemoryClientRepository();
                $store->registerClient(
                    new OAuthClientInformation(
                        clientId: $env->get('OAUTH_CLIENT_ID'),
                        clientSecret: $env->get('OAUTH_CLIENT_SECRET'),
                        clientIdIssuedAt: \time(),
                        clientSecretExpiresAt: null, // Never expires
                    ),
                );

                return $store;
            },

            // OAuth Server Provider - using proxy to external OAuth server
            OAuthServerProviderInterface::class => static fn(
                EnvironmentInterface $env,
                OAuthTokenVerifierInterface $tokenVerifier,
                OAuthRegisteredClientsStoreInterface $clientStore,
                OAuthMetadata $authMetadata,
            ) => new ProxyProvider(
                endpoints: new ProxyEndpoints(
                    authorizationUrl: $authMetadata->authorizationEndpoint,
                    tokenUrl: $authMetadata->tokenEndpoint,
                    revocationUrl: $authMetadata->revocationEndpoint,
                    registrationUrl: $authMetadata->registrationEndpoint,
                ),
                verifyAccessToken: static fn(string $token) => $tokenVerifier->verifyAccessToken($token),
                getClient: static fn(string $clientId) => $clientStore->getClient($clientId),
                httpClient: $httpClient,
                requestFactory: $requestFactory,
                streamFactory: $streamFactory,
            ),

            AuthRouterOptions::class => static fn(
                EnvironmentInterface $env,
            ) => new AuthRouterOptions(
                issuerUrl: $env->get('OAUTH_ISSUER_URL', 'http://127.0.0.1:8090'),
                baseUrl: $env->get('OAUTH_SERVER_URL', 'http://127.0.0.1:8090'),
                resourceName: 'CTX MCP Server',
            ),

            // OAuth Metadata
            OAuthMetadata::class => static fn(
                AuthRouterOptions $options,
            ) => OAuthMetadata::forGithub(
                issuer: $options->issuerUrl,
            ),

            AuthorizeHandler::class => static fn(
                OAuthServerProviderInterface $provider,
            ) => new AuthorizeHandler(
                provider: $provider,
                responseFactory: $responseFactory,
                streamFactory: $streamFactory,
            ),
            RegisterHandler::class => static fn(
                OAuthRegisteredClientsStoreInterface $clientStore,
            ) => new RegisterHandler(
                clientsStore: $clientStore,
                responseFactory: $responseFactory,
                streamFactory: $streamFactory,
            ),
            TokenHandler::class => static fn(
                OAuthServerProviderInterface $provider,
                OAuthRegisteredClientsStoreInterface $clientStore,
            ) => new TokenHandler(
                provider: $provider,
                responseFactory: $responseFactory,
                streamFactory: $streamFactory,
            ),
            RevokeHandler::class => static fn(
                OAuthServerProviderInterface $provider,
            ) => new RevokeHandler(
                provider: $provider,
                responseFactory: $responseFactory,
                streamFactory: $streamFactory,
            ),
            MetadataHandler::class => static fn(
                AuthRouterOptions $options,
                OAuthMetadata $oauthMetadata,
            ) => new MetadataHandler(
                oauthMetadata: $oauthMetadata,
                protectedResourceMetadata: new OAuthProtectedResourceMetadata(
                    resource: $options->baseUrl ?? $oauthMetadata->issuer,
                    authorizationServers: [$oauthMetadata->issuer],
                    jwksUri: null,
                    scopesSupported: empty($options->scopesSupported) ? null : $options->scopesSupported,
                    bearerMethodsSupported: null,
                    resourceSigningAlgValuesSupported: null,
                    resourceName: $options->resourceName,
                    resourceDocumentation: $options->serviceDocumentationUrl,
                ),
                responseFactory: $responseFactory,
                streamFactory: $streamFactory,
            ),

            // OAuth Router
            McpAuthRouter::class => static fn(
                AuthorizeHandler $authorizeHandler,
                RegisterHandler $registerHandler,
                TokenHandler $tokenHandler,
                RevokeHandler $revokeHandler,
                MetadataHandler $metadataHandler,
                OAuthTokenVerifierInterface $tokenVerifier,
            ) => new McpAuthRouter(
                authorizeHandler: $authorizeHandler,
                registerHandler: $registerHandler,
                tokenHandler: $tokenHandler,
                revokeHandler: $revokeHandler,
                metadataHandler: $metadataHandler,
                responseFactory: $responseFactory,
                streamFactory: $streamFactory,
                tokenVerifier: $tokenVerifier,
            ),
        ];
    }

    public function init(EnvironmentInterface $env): void
    {
        $this->configurator->setDefaults(OauthConfig::CONFIG, [
            'enabled' => (bool) $env->get('OAUTH_ENABLED', false),
            'client_id' => $env->get('OAUTH_CLIENT_ID'),
            'client_secret' => $env->get('OAUTH_CLIENT_SECRET'),
        ]);
    }

    public function boot(
        MiddlewareRegistryInterface $registry,
        EnvironmentInterface $env,
        ExceptionHandlerMiddleware $exceptionHandler,
        LoggerMiddleware $logger,
        AuthMiddleware $authMiddleware,
        OauthConfig $oauthConfig,
        ContainerInterface $container,
    ): void {
        $convertValues = static fn(
            string|null|bool $values,
        ) => \is_string($values) ? \array_map(\trim(...), \explode(',', $values)) : [];

        // Get allowed origins from env or use wildcard
        $allowedOrigins = $convertValues($env->get('MCP_CORS_ALLOWED_ORIGINS', '*'));
        $allowedMethods = $convertValues($env->get('MCP_CORS_ALLOWED_METHODS', 'GET,POST,PUT,DELETE,OPTIONS'));
        $allowedHeaders = $convertValues($env->get('MCP_CORS_ALLOWED_HEADERS', 'Content-Type,Authorization'));

        $registry->register($logger);
        $registry->register($exceptionHandler);

        $registry->register(
            new CorsMiddleware(
                allowedOrigins: $allowedOrigins,
                allowedMethods: $allowedMethods,
                allowedHeaders: $allowedHeaders,
            ),
        );

        $registry->register(
            new ProxyAwareMiddleware(
                trustProxy: (bool) $env->get('MCP_TRUST_PROXY', true),
            ),
        );

        // Register OAuth router middleware if enabled
        if ($oauthConfig->isEnabled() && $oauthConfig->hasCredentials()) {
            $registry->register($container->get(McpAuthRouter::class));
        }

        $registry->register($authMiddleware);
    }
}
