<?php

namespace MintyPHP\Http;

use MintyPHP\Service\Security\RateLimiterService;
use MintyPHP\Service\Security\SecurityServicesFactory;
use MintyPHP\Service\Settings\SettingGateway;
use MintyPHP\Service\Settings\SettingServicesFactory;

class ApiBootstrap
{
    private const API_RATE_SCOPE_TOKEN = 'api.token_ip';
    private const API_RATE_SCOPE_IP = 'api.ip';
    private const API_RATE_LIMIT_TOKEN = 300;
    private const API_RATE_LIMIT_IP = 120;
    private const API_RATE_WINDOW_SECONDS = 60;
    private const API_RATE_BLOCK_SECONDS = 120;

    private static bool $initialized = false;
    private static bool $shutdownRegistered = false;
    private static ?SecurityServicesFactory $securityServicesFactory = null;
    private static ?RateLimiterService $rateLimiterService = null;
    private static ?SettingServicesFactory $settingServicesFactory = null;
    private static ?SettingGateway $settingGateway = null;

    /**
     * Initialize the API request context.
     *
     * Call this at the top of every API page action.
     * Sets MINTY_ALLOW_OUTPUT, CORS headers, authenticates via Bearer token.
     */
    public static function init(): void
    {
        if (self::$initialized) {
            return;
        }
        self::$initialized = true;

        if (!defined('MINTY_ALLOW_OUTPUT')) {
            define('MINTY_ALLOW_OUTPUT', true);
        }

        self::registerShutdownHandler();
        \auditServicesFactory()->createApiAuditService()->startRequestContext();
        self::setCorsHeaders();

        if (strtoupper($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') {
            http_response_code(204);
            die();
        }

        self::enforceRateLimit();

        $authenticated = ApiAuth::authenticate();
        if (!$authenticated) {
            ApiResponse::unauthorized();
        }
    }

    private static function setCorsHeaders(): void
    {
        $rawOrigin = trim((string) ($_SERVER['HTTP_ORIGIN'] ?? ''));
        if ($rawOrigin === '') {
            return;
        }
        $origin = self::normalizeOrigin($rawOrigin);
        if ($origin === null) {
            return;
        }

        $allowedOrigins = array_fill_keys(self::settings()->getApiCorsAllowedOrigins(), true);
        if (!isset($allowedOrigins[$origin])) {
            return;
        }

        header('Vary: Origin');
        header('Access-Control-Allow-Origin: ' . $origin);
        header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
        header('Access-Control-Allow-Headers: Authorization, Content-Type, Accept');
        header('Access-Control-Max-Age: 86400');
    }

    private static function registerShutdownHandler(): void
    {
        if (self::$shutdownRegistered) {
            return;
        }
        self::$shutdownRegistered = true;

        register_shutdown_function(static function (): void {
            $error = error_get_last();
            if (!is_array($error)) {
                $statusCode = (int) http_response_code();
                if ($statusCode <= 0) {
                    $statusCode = 200;
                }
                \auditServicesFactory()->createApiAuditService()->finish($statusCode);
                return;
            }

            $type = (int) $error['type'];
            $fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
            if (!in_array($type, $fatalTypes, true)) {
                $statusCode = (int) http_response_code();
                if ($statusCode <= 0) {
                    $statusCode = 200;
                }
                \auditServicesFactory()->createApiAuditService()->finish($statusCode);
                return;
            }

            $statusCode = (int) http_response_code();
            if ($statusCode < 400) {
                $statusCode = 500;
            }
            \auditServicesFactory()->createApiAuditService()->finish($statusCode, 'fatal_error');
        });
    }

    private static function normalizeOrigin(string $origin): ?string
    {
        $origin = rtrim(trim($origin), '/');
        if ($origin === '') {
            return null;
        }

        $parsed = parse_url($origin);
        if (!is_array($parsed)) {
            return null;
        }

        $scheme = strtolower((string) ($parsed['scheme'] ?? ''));
        $host = strtolower((string) ($parsed['host'] ?? ''));
        $port = isset($parsed['port']) ? (int) $parsed['port'] : null;

        if (!in_array($scheme, ['http', 'https'], true) || $host === '') {
            return null;
        }

        if (
            isset($parsed['path'])
            || isset($parsed['query'])
            || isset($parsed['fragment'])
            || isset($parsed['user'])
            || isset($parsed['pass'])
        ) {
            return null;
        }

        $defaultPort = ($scheme === 'https') ? 443 : 80;
        $portSuffix = ($port !== null && $port !== $defaultPort) ? ':' . $port : '';
        return $scheme . '://' . $host . $portSuffix;
    }

    private static function enforceRateLimit(): void
    {
        $ip = trim((string) ($_SERVER['REMOTE_ADDR'] ?? ''));
        if ($ip === '') {
            $ip = 'unknown';
        }

        $ipResult = self::rateLimiter()->hit(
            self::API_RATE_SCOPE_IP,
            $ip,
            self::API_RATE_LIMIT_IP,
            self::API_RATE_WINDOW_SECONDS,
            self::API_RATE_BLOCK_SECONDS
        );
        if (!($ipResult['allowed'] ?? true)) {
            ApiResponse::tooManyRequests(max(1, (int) ($ipResult['retry_after'] ?? self::API_RATE_BLOCK_SECONDS)));
        }

        $selector = self::extractBearerSelector();
        if ($selector === '') {
            return;
        }

        $tokenResult = self::rateLimiter()->hit(
            self::API_RATE_SCOPE_TOKEN,
            strtolower($selector) . '|' . $ip,
            self::API_RATE_LIMIT_TOKEN,
            self::API_RATE_WINDOW_SECONDS,
            self::API_RATE_BLOCK_SECONDS
        );

        if (!($tokenResult['allowed'] ?? true)) {
            ApiResponse::tooManyRequests(max(1, (int) ($tokenResult['retry_after'] ?? self::API_RATE_BLOCK_SECONDS)));
        }
    }

    private static function extractBearerSelector(): string
    {
        $token = ApiAuth::extractBearerToken();
        if ($token === '') {
            return '';
        }

        $parts = explode(':', $token, 2);
        $selector = trim((string) ($parts[0] ?? ''));
        if ($selector === '' || !preg_match('/^[a-f0-9]{24}$/i', $selector)) {
            return '';
        }

        return $selector;
    }

    private static function settings(): SettingGateway
    {
        if (self::$settingGateway instanceof SettingGateway) {
            return self::$settingGateway;
        }

        if (!(self::$settingServicesFactory instanceof SettingServicesFactory)) {
            self::$settingServicesFactory = new SettingServicesFactory();
        }

        self::$settingGateway = self::$settingServicesFactory->createSettingGateway();
        return self::$settingGateway;
    }

    private static function rateLimiter(): RateLimiterService
    {
        if (self::$rateLimiterService instanceof RateLimiterService) {
            return self::$rateLimiterService;
        }

        if (!(self::$securityServicesFactory instanceof SecurityServicesFactory)) {
            self::$securityServicesFactory = new SecurityServicesFactory();
        }

        self::$rateLimiterService = self::$securityServicesFactory->createRateLimiterService();
        return self::$rateLimiterService;
    }
}
