<?php

/**
 * Базовий клас ядра системи Flowaxy CMS
 * Реалізує спільну логіку для HttpKernel та CliKernel
 *
 * @package Flowaxy\Core\System\Kernel
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Core\System\Kernel;

use Flowaxy\Core\Contracts\ContainerInterface;
use Flowaxy\Core\Contracts\KernelInterface;
use Flowaxy\Core\Contracts\ServiceProviderInterface;
use Flowaxy\Core\System\Autoloader\ClassAutoloader;
use Flowaxy\Core\System\Container\Container as ContainerInstance;
use Flowaxy\Core\System\Environment\EnvironmentLoader;
use Flowaxy\Core\System\ModuleManager;
use Flowaxy\Core\System\ServiceConfig;
use Flowaxy\Support\Facades\Log;
use RuntimeException;

use function class_exists;
use function method_exists;
use function rtrim;

abstract class Kernel implements KernelInterface
{
    protected ContainerInterface $container;
    protected ClassAutoloader $autoloader;
    protected bool $booted = false;
    protected string $rootDir;
    protected array $serviceProviders = [];
    protected ?EnvironmentLoader $environmentLoader = null;

    /**
     * @param string $rootDir Корінь директорії engine/
     */
    public function __construct(string $rootDir)
    {
        $this->rootDir = rtrim($rootDir, '/\\');
    }

    /**
     * Ініціалізація ядра
     */
    public function boot(): void
    {
        if ($this->booted) {
            Log::Debug('Kernel::boot: Kernel already booted');
            return;
        }

        Log::Debug('Kernel::boot: Starting kernel boot', ['root_dir' => $this->rootDir]);

        $this->createAutoloader();
        $this->createContainer();

        $this->booted = true;

        Log::Info('Kernel::boot: Kernel booted successfully', ['root_dir' => $this->rootDir]);
    }

    /**
     * Налаштування ядра
     */
    public function configure(): void
    {
        if (! $this->isBooted()) {
            $this->boot();
        }

        Log::Debug('Kernel::configure: Configuring kernel');

        $this->loadEnvironmentConfig();
        $this->loadServiceConfig();
        $this->loadGlobalFunctions();

        Log::Info('Kernel::configure: Kernel configured successfully');
    }

    /**
     * Реєстрація сервіс-провайдерів
     * Оптимізовано: lazy loading провайдерів
     */
    public function registerProviders(): void
    {
        if (! $this->isBooted()) {
            $this->boot();
        }

        Log::Debug('Kernel::registerProviders: Starting provider registration');

        $this->serviceProviders = $this->getServiceProviders();

        $registeredCount = 0;
        foreach ($this->serviceProviders as $providerClass) {
            if (! class_exists($providerClass)) {
                Log::Warning('Kernel::registerProviders: Provider class not found', ['provider' => $providerClass]);
                continue;
            }

            try {
                // Lazy loading: створюємо провайдер тільки коли потрібно
                /** @var ServiceProviderInterface $provider */
                $provider = new $providerClass();
                $provider->register($this->container);
                $registeredCount++;

                Log::Debug('Kernel::registerProviders: Provider registered', ['provider' => $providerClass]);
            } catch (\Exception $e) {
                Log::Error('Kernel::registerProviders: Failed to register provider', [
                    'provider' => $providerClass,
                    'error' => $e->getMessage(),
                    'exception' => $e,
                ]);
            }
        }

        Log::Info('Kernel::registerProviders: Providers registered', [
            'total' => count($this->serviceProviders),
            'registered' => $registeredCount,
        ]);
    }

    /**
     * Запуск сервіс-провайдерів
     * Оптимізовано: кешування екземплярів провайдерів
     */
    public function bootProviders(): void
    {
        $providerInstances = [];

        foreach ($this->serviceProviders as $providerClass) {
            if (! class_exists($providerClass)) {
                continue;
            }

            // Кешуємо екземпляри провайдерів для уникнення повторного створення
            if (!isset($providerInstances[$providerClass])) {
                /** @var ServiceProviderInterface $provider */
                $providerInstances[$providerClass] = new $providerClass();
            }

            $providerInstances[$providerClass]->boot($this->container);
        }

        // Запускаємо ModuleManager після провайдерів
        if ($this->container->has('ModuleManager')) {
            /** @var ModuleManager|null $moduleManager */
            $moduleManager = $this->container->make('ModuleManager');
            if ($moduleManager !== null && method_exists($moduleManager, 'boot')) {
                $moduleManager->boot();
            }
        } elseif ($this->container->has(ModuleManager::class)) {
            /** @var ModuleManager|null $moduleManager */
            $moduleManager = $this->container->make(ModuleManager::class);
            if ($moduleManager !== null && method_exists($moduleManager, 'boot')) {
                $moduleManager->boot();
            }
        }
    }

    /**
     * Отримання контейнера
     */
    public function getContainer(): ContainerInterface
    {
        return $this->container;
    }

    /**
     * Перевірка, чи ядро ініціалізовано
     */
    public function isBooted(): bool
    {
        return $this->booted;
    }

    /**
     * Статичний метод для створення автозавантажувача (для тестів)
     *
     * @return ClassAutoloader
     */
    public static function createAutoloaderForTests(): ClassAutoloader
    {
        if (isset($GLOBALS['engineAutoloader']) && $GLOBALS['engineAutoloader'] instanceof ClassAutoloader) {
            return $GLOBALS['engineAutoloader'];
        }

        $rootDir = defined('ROOT_DIR') ? ROOT_DIR : dirname(__DIR__, 4);

        // КРИТИЧНО: Загружаем интерфейсы ПЕРЕД созданием ClassAutoloader
        $flowaxyDir = defined('FLOWAXY_DIR') ? FLOWAXY_DIR : ($rootDir . DS . 'flowaxy');
        $autoloaderInterfaceFile = $flowaxyDir . DS . 'Core' . DS . 'Contracts' . DS . 'AutoloaderInterface.php';
        if (file_exists($autoloaderInterfaceFile) && is_readable($autoloaderInterfaceFile) && !interface_exists('Flowaxy\Core\Contracts\AutoloaderInterface')) {
            require_once $autoloaderInterfaceFile;
        }

        $loggerInterfaceFile = $flowaxyDir . DS . 'Core' . DS . 'Contracts' . DS . 'LoggerInterface.php';
        if (file_exists($loggerInterfaceFile) && is_readable($loggerInterfaceFile) && !interface_exists('Flowaxy\Core\Contracts\LoggerInterface')) {
            require_once $loggerInterfaceFile;
        }

        // Загружаем ClassAutoloader, если еще не загружен
        $autoloaderClassFile = $flowaxyDir . DS . 'Core' . DS . 'System' . DS . 'Autoloader' . DS . 'ClassAutoloader.php';
        if (file_exists($autoloaderClassFile) && is_readable($autoloaderClassFile) && !class_exists('Flowaxy\Core\System\Autoloader\ClassAutoloader')) {
            require_once $autoloaderClassFile;
        }

        $autoloader = new ClassAutoloader($rootDir);
        $autoloader->enableMissingClassLogging(true);
        $autoloader->register();
        $GLOBALS['engineAutoloader'] = $autoloader;

        return $autoloader;
    }

    /**
     * Статичний метод для створення контейнера (для тестів)
     *
     * @return ContainerInterface
     */
    public static function createContainerForTests(): ContainerInterface
    {
        if (isset($GLOBALS['engineContainer']) && $GLOBALS['engineContainer'] instanceof ContainerInterface) {
            return $GLOBALS['engineContainer'];
        }

        $containerClass = ContainerInstance::class;
        if (class_exists($containerClass)) {
            $container = new $containerClass();
        } else {
            throw new \RuntimeException('Container class not found');
        }

        $GLOBALS['engineContainer'] = $container;

        return $container;
    }

    /**
     * Статичний метод для завантаження конфігурації сервісів (для тестів)
     *
     * @param ContainerInterface $container
     * @return void
     */
    public static function loadServicesConfig(ContainerInterface $container): void
    {
        $rootDir = defined('ROOT_DIR') ? ROOT_DIR : dirname(__DIR__, 4);

        // Загружаем из storage/config/flowaxy/services.json
        $servicesConfigFile = $rootDir . '/storage/config/flowaxy/services.json';

        // Fallback для обратной совместимости
        if (!file_exists($servicesConfigFile)) {
            $servicesConfigFile = $rootDir . '/config/flowaxy/services.php';
        }

        if (file_exists($servicesConfigFile)) {
            $config = ServiceConfig::load($servicesConfigFile, null);
            if (is_array($config) && !empty($config)) {
                ServiceConfig::register($container, $config);
            }
        }
    }

    /**
     * Створення автозавантажувача
     */
    protected function createAutoloader(): void
    {
        // Якщо автозавантажувач вже створено (наприклад, в app.php), використовуємо його
        if (isset($GLOBALS['engineAutoloader']) && $GLOBALS['engineAutoloader'] instanceof ClassAutoloader) {
            $this->autoloader = $GLOBALS['engineAutoloader'];

            return;
        }

        // КРИТИЧНО: Загружаем интерфейсы ПЕРЕД созданием ClassAutoloader
        $flowaxyDir = $this->rootDir . DS . 'flowaxy';
        $autoloaderInterfaceFile = $flowaxyDir . DS . 'Core' . DS . 'Contracts' . DS . 'AutoloaderInterface.php';
        if (file_exists($autoloaderInterfaceFile) && is_readable($autoloaderInterfaceFile) && !interface_exists('Flowaxy\Core\Contracts\AutoloaderInterface')) {
            require_once $autoloaderInterfaceFile;
        }

        $loggerInterfaceFile = $flowaxyDir . DS . 'Core' . DS . 'Contracts' . DS . 'LoggerInterface.php';
        if (file_exists($loggerInterfaceFile) && is_readable($loggerInterfaceFile) && !interface_exists('Flowaxy\Core\Contracts\LoggerInterface')) {
            require_once $loggerInterfaceFile;
        }

        // Загружаем ClassAutoloader, если еще не загружен
        $autoloaderClassFile = $flowaxyDir . DS . 'Core' . DS . 'System' . DS . 'Autoloader' . DS . 'ClassAutoloader.php';
        if (file_exists($autoloaderClassFile) && is_readable($autoloaderClassFile) && !class_exists('Flowaxy\Core\System\Autoloader\ClassAutoloader')) {
            require_once $autoloaderClassFile;
        }

        $this->autoloader = new ClassAutoloader($this->rootDir);
        $this->autoloader->enableMissingClassLogging(true);

        $this->registerClassMap();
        $this->registerDirectories();

        $this->autoloader->register();
        $GLOBALS['engineAutoloader'] = $this->autoloader;
    }

    /**
     * Створення контейнера залежностей
     * Оптимізовано: використання правильного namespace
     */
    protected function createContainer(): void
    {
        // Якщо контейнер вже створено (наприклад, в app.php), використовуємо його
        if (isset($GLOBALS['engineContainer']) && $GLOBALS['engineContainer'] instanceof ContainerInterface) {
            $this->container = $GLOBALS['engineContainer'];

            return;
        }

        // Використовуємо повний namespace для Container
        $containerClass = ContainerInstance::class;
        if (class_exists($containerClass)) {
            $this->container = new $containerClass();
        } else {
            throw new RuntimeException('Container class not found');
        }

        $GLOBALS['engineContainer'] = $this->container;
    }

    /**
     * Завантаження environment конфігурації
     */
    protected function loadEnvironmentConfig(): void
    {
        $projectRoot = dirname($this->rootDir);
        $this->environmentLoader = new EnvironmentLoader($projectRoot);

        // Завантажуємо конфігурацію
        $envConfig = $this->environmentLoader->load();

        // Зареєструємо EnvironmentLoader в контейнері для доступу
        $this->container->singleton(EnvironmentLoader::class, fn() => $this->environmentLoader);

        // Зберігаємо environment в контейнері (використовуємо singleton для примітивних типів)
        /** @var EnvironmentLoader $envLoader */
        $envLoader = $this->environmentLoader;
        /** @phpstan-ignore-next-line */
        $environment = $envLoader->getEnvironment();
        $this->container->singleton('environment', fn() => $environment);
        $this->container->singleton('env.config', fn() => $envConfig);
    }

    /**
     * Завантаження конфігурації сервісів
     */
    protected function loadServiceConfig(): void
    {
        // Загружаем из storage/config/flowaxy/services.json
        $servicesConfigFile = $this->rootDir . '/storage/config/flowaxy/services.json';

        // Fallback для обратной совместимости
        if (!file_exists($servicesConfigFile)) {
            $servicesConfigFile = $this->rootDir . '/config/flowaxy/services.php';
        }

        if (file_exists($servicesConfigFile)) {
            $servicesConfig = ServiceConfig::load($servicesConfigFile, null);
            if (is_array($servicesConfig) && !empty($servicesConfig)) {
                ServiceConfig::register($this->container, $servicesConfig);
            }
        }
    }

    /**
     * Завантаження глобальних функцій
     */
    protected function loadGlobalFunctions(): void
    {
        // Сначала пробуем новую структуру flowaxy/Support/
        $functionsFile = dirname($this->rootDir) . '/flowaxy/Support/functions.php';
        if (!file_exists($functionsFile)) {
            // Fallback для зворотної сумісності
            $functionsFile = $this->rootDir . '/Support/functions.php';
        }

        if (file_exists($functionsFile)) {
            require_once $functionsFile;
        }

        // RoleHelper.php завантажується всередині HelperFunctionsLoader

        // @phpstan-ignore-next-line
        if (\function_exists('loadDatabaseConfig')) {
            /** @var callable $loadDbConfig */
            $loadDbConfig = 'loadDatabaseConfig';
            $loadDbConfig();
        }
    }

    /**
     * Отримання списку сервіс-провайдерів для реєстрації
     * Потрібно перевизначити в нащадках
     *
     * @return array<string> Масив класів провайдерів
     */
    abstract protected function getServiceProviders(): array;

    /**
     * Реєстрація class map в автозавантажувачі
     * Оптимізовано: спроба завантажити згенерований class map
     */
    protected function registerClassMap(): void
    {
        // Спочатку намагаємося завантажити згенерований class map
        $classMapFile = dirname($this->rootDir) . '/storage/cache/classmap.php';
        if (file_exists($classMapFile)) {
            $generatedMap = require $classMapFile;
            if (is_array($generatedMap)) {
                $this->autoloader->addClassMap($generatedMap);
            }
        }

        // Додаємо додатковий class map з getClassMap() (для класів, які не в згенерованому map)
        $additionalClassMap = $this->getClassMap();
        if (!empty($additionalClassMap)) {
            $this->autoloader->addClassMap($additionalClassMap);
        }
    }

    /**
     * Реєстрація директорій в автозавантажувачі
     */
    protected function registerDirectories(): void
    {
        $directories = $this->getDirectories();
        $this->autoloader->addDirectories($directories);
    }

    /**
     * Отримання class map для автозавантажувача
     * Можна перевизначити в нащадках
     *
     * @return array<string, string>
     */
    protected function getClassMap(): array
    {
        return [];
    }

    /**
     * Отримання списку директорій для автозавантажувача
     * Можна перевизначити в нащадках
     *
     * @return array<string>
     */
    protected function getDirectories(): array
    {
        $flowaxyDir = $this->rootDir . DS . 'flowaxy';
        $directories = [];

        // Нова структура flowaxy/
        $newStructureDirs = [
            'Core',
            'Core/System',
            'Core/System/Autoloader',
            'Core/System/Container',
            'Core/System/Kernel',
            'Core/System/Environment',
            'Core/Hooks',
            'Core/Routing',
            'Core/Contracts',
            'Infrastructure',
            'Infrastructure/Persistence',
            'Infrastructure/Persistence/Database',
            'Infrastructure/Cache',
            'Infrastructure/Cache/Drivers',
            'Infrastructure/Filesystem',
            'Infrastructure/Logging',
            'Infrastructure/Security',
            'Infrastructure/Config',
            'Infrastructure/Mail',
            'Domain',
            'Domain/User',
            'Domain/User/Entities',
            'Domain/User/Services',
            'Domain/Plugin',
            'Domain/Plugin/Entities',
            'Domain/Plugin/Services',
            'Domain/Theme',
            'Domain/Theme/Entities',
            'Domain/Theme/Services',
            'Application',
            // Application/Services removed - moved to Domain/Services
            'Interface',
            'Interface/Http',
            'Interface/Http/Middleware',
            'Interface/CLI',
            'Interface/UI',
            'Contracts',
            'Contracts/Filesystem',
            'Contracts/Http',
            'Support',
            'Support/Base',
        ];

        foreach ($newStructureDirs as $dir) {
            $fullPath = $flowaxyDir . DS . str_replace('/', DS, $dir);
            if (is_dir($fullPath) && is_readable($fullPath)) {
                $directories[] = $fullPath . DS;
            }
        }

        // Fallback для зворотної сумісності - додаємо старі директорії engine/
        $engineDir = $this->rootDir . DS . 'engine';
        if (is_dir($engineDir)) {
            $oldStructureDirs = [
                'core',
                'core/system',
                'core/bootstrap',
                'core/providers',
                'Contracts',
                'Support',
                'Support/Base',
                'Support/Helpers',
                'Support/Managers',
                'domain',
                'domain/content',
                'application',
                'application/content',
                'application/security',
                'infrastructure',
                'infrastructure/config',
                'infrastructure/logging',
                'infrastructure/filesystem',
                'infrastructure/security',
                'infrastructure/mail',
                'interface',
                'interface/http',
                'interface/http/controllers',
                'interface/ui',
            ];

            foreach ($oldStructureDirs as $dir) {
                $fullPath = $engineDir . DS . str_replace('/', DS, $dir);
                if (is_dir($fullPath) && is_readable($fullPath)) {
                    $directories[] = $fullPath . DS;
                }
            }
        }

        return $directories;
    }
}
