<?php

/**
 * HTTP ядро системи Flowaxy CMS
 * Обробляє HTTP запити через роутер
 *
 * @package Flowaxy\Core\System\Kernel
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Core\System\Kernel;

use Flowaxy\Bootstrap\RolesInitializer;
use Flowaxy\Core\Routing\RouterManager;
use Flowaxy\Core\System\Container\CoreServiceProvider;
use Flowaxy\Core\System\Container\Providers\AuthServiceProvider;
use Flowaxy\Core\System\Container\Providers\ThemeServiceProvider;
use Flowaxy\Core\System\Kernel\Kernel;
use Flowaxy\Infrastructure\Security\Session;
use Flowaxy\Support\Facades\FeatureFlag;
use Flowaxy\Support\Facades\Hooks;
use Flowaxy\Support\Facades\Log;
use Flowaxy\Support\Facades\Settings;

use function class_exists;

final class HttpKernel extends Kernel
{
    /**
     * Отримання списку сервіс-провайдерів
     */
    protected function getServiceProviders(): array
    {
        return [
            CoreServiceProvider::class,
            ThemeServiceProvider::class,
            AuthServiceProvider::class,
        ];
    }

    /**
     * Обробка HTTP запиту
     */
    public function serve(): void
    {
        // Применение security headers
        $this->applySecurityHeaders();

        // Применение rate limiting для API endpoints
        $this->applyRateLimiting();

        $requestUri = $_SERVER['REQUEST_URI'] ?? '/';
        $requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'GET';

        Log::Debug('HttpKernel::serve: Processing HTTP request', [
            'method' => $requestMethod,
            'uri' => $requestUri,
        ]);

        if (! $this->isBooted()) {
            $this->boot();
            $this->configure();
            $this->registerProviders();
            $this->bootProviders();
        }

        // Завантажуємо конфігурацію БД (потрібно перед ініціалізацією)
        // @phpstan-ignore-next-line
        if (\function_exists('loadDatabaseConfig')) {
            /** @var callable $loadDbConfig */
            $loadDbConfig = 'loadDatabaseConfig';
            $loadDbConfig();
        }

        // Ініціалізація системи (timezone, error handlers, session, migrations, roles)
        $this->initializeApplication();

        // Перевіряємо підключення до БД
        // @phpstan-ignore-next-line
        if (\function_exists('initializeSystem')) {
            /** @var callable $initSystem */
            $initSystem = 'initializeSystem';
            $initSystem();
        }

        // Обробляємо запит через роутер
        $this->dispatchRequest();

        Log::Info('HttpKernel::serve: Request processed successfully', [
            'method' => $requestMethod,
            'uri' => $requestUri,
        ]);
    }

    /**
     * Ініціалізація додатка (timezone, error handlers, session, migrations)
     */
    private function initializeApplication(): void
    {
        $requestUri = $_SERVER['REQUEST_URI'] ?? '/';
        $databaseIniFile = dirname($this->rootDir) . '/storage/config/system/database.ini';
        $defaultTimezone = 'Europe/Kyiv';
        // Єдине правило доступності БД у системі:
        // якщо DB_* константи не визначені (наприклад status=false) — БД вважається вимкненою.
        $dbEnabled = defined('DB_HOST') && defined('DB_NAME') && is_string(DB_NAME) && DB_NAME !== '';

        // Встановлення часового поясу з БД через TimezoneManager
        $timezone = $defaultTimezone;
        if ($dbEnabled && \file_exists($databaseIniFile)) {
            // @phpstan-ignore-next-line
            if (\class_exists('ModuleLoader')) {
                /** @var class-string $moduleLoaderClass */
                $moduleLoaderClass = 'ModuleLoader';
                $moduleLoaderClass::init();
            }

            // Завантажуємо TimezoneManager та отримуємо timezone з БД
            // Підключаємо functions.php, який завантажує TimezoneManager
            $functionsFile = $this->rootDir . '/Support/functions.php';
            if (file_exists($functionsFile)) {
                require_once $functionsFile;
            }

            // Використовуємо TimezoneManager для отримання timezone з БД
            // @phpstan-ignore-next-line
            if (\function_exists('getTimezoneFromDatabase')) {
                try {
                    /** @var callable $getTz */
                    $getTz = 'getTimezoneFromDatabase';
                    $tz = $getTz();

                    // Автоматичне оновлення старого часового поясу на новий
                    if ($tz === 'Europe/Kiev') {
                        $tz = 'Europe/Kyiv';

                        // Оновлюємо в налаштуваннях
                        // @phpstan-ignore-next-line
                        if (\class_exists('SettingsManager')) {
                            try {
                                $settingsManager = Settings::manager();
                                if ($settingsManager !== null) {
                                    $settingsManager->set('timezone', 'Europe/Kyiv');
                                }
                            } catch (\Exception $e) {
                                Log::Warning('Не вдалося оновити налаштування часового поясу', ['error' => $e->getMessage()]);
                            }
                        }
                    }

                    $timezone = $tz;
                } catch (\Exception $e) {
                    Log::Warning('Помилка завантаження часового поясу', ['error' => $e->getMessage()]);
                }
            }
        }
        date_default_timezone_set($timezone);

        // Налаштування обробників помилок
        if ($dbEnabled && file_exists($databaseIniFile)) {
            set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool {
                $level = match($errno) {
                    E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE, E_RECOVERABLE_ERROR => 'error',
                    E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING => 'warning',
                    default => 'info'
                };
                match($level) {
                    'error' => Log::Error($errstr, ['file' => $errfile, 'line' => $errline, 'errno' => $errno]),
                    'warning' => Log::Warning($errstr, ['file' => $errfile, 'line' => $errline, 'errno' => $errno]),
                    'notice' => Log::notice($errstr, ['file' => $errfile, 'line' => $errline, 'errno' => $errno]),
                    'info' => Log::Info($errstr, ['file' => $errfile, 'line' => $errline, 'errno' => $errno]),
                    default => Log::Debug($errstr, ['file' => $errfile, 'line' => $errline, 'errno' => $errno]),
                };

                return false;
            });

            \set_exception_handler(function (\Throwable $e): void {
                Log::Error('Exception: ' . $e->getMessage(), ['exception' => $e]);

                // Використовуємо функцію для відображення сторінки помилки
                if (function_exists('showError500Page')) {
                    showError500Page($e);
                } else {
                    // Fallback якщо функція недоступна
                    if (! headers_sent()) {
                        http_response_code(500);
                    }
                    $debugMode = FeatureFlag::enabled('debug_mode', []);
                    if ($debugMode) {
                        echo '<pre>' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . "\n" . htmlspecialchars($e->getTraceAsString(), ENT_QUOTES, 'UTF-8') . '</pre>';
                    } else {
                        echo '<h1>Внутрішня помилка сервера</h1>';
                    }
                }
            });

            \register_shutdown_function(function (): void {
                $error = \error_get_last();
                if ($error && \in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE], true)) {
                    Log::Error('Критична помилка: ' . $error['message'], ['file' => $error['file'], 'line' => $error['line']]);

                    // Відображаємо сторінку помилки, якщо функція доступна
                    if (function_exists('showError500Page')) {
                        showError500Page(null, [
                            'message' => $error['message'],
                            'file' => $error['file'],
                            'line' => $error['line'],
                            'code' => $error['type'],
                        ]);
                    }
                }
            });
        }

        // Визначаємо secure для сесії
        $isSecure = false;
        // @phpstan-ignore-next-line
        if (\function_exists('detectProtocol')) {
            // @phpstan-ignore-next-line
            $protocol = detectProtocol();
            $isSecure = ($protocol === 'https://');
        }

        // Запускаємо сесію
        if (class_exists(Session::class)) {
            Session::start([
                'domain' => '',
                'path' => '/',
                'secure' => $isSecure,
                'httponly' => true,
                'samesite' => 'Lax',
            ]);
        }

        // Встановлення security headers
        // @phpstan-ignore-next-line
        if (! \headers_sent() && \class_exists('Response')) {
            /** @var class-string $responseClass */
            $responseClass = 'Response';
            $responseClass::setSecurityHeaders();
        }

        // Виконання міграцій через систему міграцій
        // @phpstan-ignore-next-line
        if ($dbEnabled && \file_exists($databaseIniFile) && \function_exists('DatabaseHelper') && \class_exists('DatabaseHelper')) {
            try {
                /** @var class-string $dbHelperClass */
                $dbHelperClass = 'DatabaseHelper';
                /** @phpstan-ignore-next-line */
                $db = $dbHelperClass::getConnection();
                // @phpstan-ignore-next-line
                if ($db && \class_exists('MigrationRunner')) {
                    $migrationsDir = "{$this->rootDir}/core/system/migrations";
                    // @phpstan-ignore-next-line
                    $runnerClass = 'MigrationRunner';
                    // @phpstan-ignore-next-line
                    $runner = new $runnerClass($migrationsDir, $db);
                    $runner->run();
                }
            } catch (\Exception $e) {
                Log::Error('Не вдалося виконати міграції', ['error' => $e->getMessage()]);
            }
        }

        // Ініціалізація системи ролей
        try {
            RolesInitializer::initialize();
        } catch (\Throwable $e) {
            // Продовжуємо роботу навіть якщо ініціалізація ролей не вдалася
        }
    }

    /**
     * Обробка запиту через роутер
     */
    private function dispatchRequest(): void
    {
        // Перевірка ранніх хуків
        if (class_exists(Hooks::class)) {
            $handled = Hooks::apply('handle_early_request', false);
            if ($handled === true) {
                exit;
            }
        }

        // Обробляємо запит через роутер
        if (class_exists(RouterManager::class)) {
            $routerManager = RouterManager::getInstance();
            $routerManager->dispatch();
        }
    }

    /**
     * Отримання class map для автозавантажувача
     *
     * @return array<string, string>
     */
    protected function getClassMap(): array
    {
        // Используем метод из родительского класса
        return parent::getClassMap();
    }

    /**
     * Отримання списку директорій для автозавантажувача
     *
     * @return array<string>
     */
    protected function getDirectories(): array
    {
        // Используем метод из родительского класса
        return parent::getDirectories();
    }

    /**
     * Применить security headers
     *
     * @return void
     */
    private function applySecurityHeaders(): void
    {
        try {
            $securityHeaders = new SecurityHeaders();
            $csp = CspHeaders::createDefault();
            $securityHeaders->setCSP($csp);
            $securityHeaders->send();
        } catch (\Throwable $e) {
            // Ignore errors
        }
    }

    /**
     * Применить rate limiting для API endpoints
     *
     * @return void
     */
    private function applyRateLimiting(): void
    {
        try {
            $requestUri = $_SERVER['REQUEST_URI'] ?? '/';

            // Применяем rate limiting только для API endpoints
            if (str_starts_with($requestUri, '/api/')) {
                $rateLimiter = new RateLimiter();
                $middleware = new RateLimitingMiddleware($rateLimiter);

                $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
                $key = 'api:' . $ip;

                $middleware->handle($key, 60, 1, function () {
                    // Request allowed
                });
            }
        } catch (\Throwable $e) {
            // Ignore errors, но логируем
            try {
                Log::Warning('Rate limiting error', ['exception' => $e]);
            } catch (\Throwable $logError) {
                // Ignore
            }
        }
    }
}
