<?php

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Config;

use Flowaxy\Contracts\Config\ConfigInterface;
use Flowaxy\Core\System\PathResolver;
use Flowaxy\Infrastructure\Filesystem\Directory;
use Flowaxy\Infrastructure\Filesystem\File;
use Flowaxy\Support\Facades\Log;
use Flowaxy\Support\Helpers\IniHelper;
use Flowaxy\Support\Helpers\JsonHelper;
use Exception;

use function array_merge_recursive;
use function array_pop;
use function basename;
use function date;
use function dirname;
use function explode;
use function file_exists;
use function glob;
use function in_array;
use function is_array;
use function is_dir;
use function is_file;
use function mkdir;
use function preg_replace;
use function rtrim;
use function str_contains;
use const DS;

// Клас для роботи з конфігураціями сайту
// Керування конфігураційними файлами в директорії engine/data
final class Config implements ConfigInterface
{
    private static ?self $instance = null;
    private string $configDir;
    private array $loadedConfigs = [];
    private const DEFAULT_EXTENSION = '.json';

    // Конструктор (приватний для Singleton)
    private function __construct(?string $configDir = null)
    {
        if ($configDir === null) {
            $this->configDir = PathResolver::storageConfig() . DS;
        } else {
            $this->configDir = rtrim($configDir, '/\\') . DS;
        }
        $this->configDir = rtrim($this->configDir, '/\\') . DS;
        $this->ensureConfigDir();
    }

    // Отримання екземпляра класу (Singleton)
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    // Створення директорії конфігурацій та необхідних піддиректорій
    private function ensureConfigDir(): void
    {
        // Перевіряємо існування директорії і створюємо, якщо не існує
        if (! is_dir($this->configDir)) {
            // Пробуємо створити директорію
            if (! @mkdir($this->configDir, 0755, true) && ! is_dir($this->configDir)) {
                // Не використовуємо Log::error тут, щоб уникнути циклічної залежності
                error_log("Config: Failed to create directory: {$this->configDir}. Please check permissions.");
                return;
            }
        }

        // Создаем необходимые поддиректории
        $this->ensureConfigSubdirectories();
    }

    /**
     * Создание необходимых поддиректорий в storage/config
     *
     * @return void
     */
    private function ensureConfigSubdirectories(): void
    {
        $subdirectories = [
            'plugins',
            'system',
            'themes',
            'flowaxy',
            'settings',
        ];

        foreach ($subdirectories as $subdir) {
            $dirPath = rtrim($this->configDir, '/\\') . DS . $subdir;
            if (!is_dir($dirPath)) {
                if (!@mkdir($dirPath, 0755, true) && !is_dir($dirPath)) {
                    // Не використовуємо Log::error тут, щоб уникнути циклічної залежності
                    error_log("Config: Failed to create subdirectory: {$dirPath}. Please check permissions.");
                }
            }
        }
    }

    // Отримання значення конфігурації
    public function get(string $key, mixed $default = null): mixed
    {
        $extension = self::DEFAULT_EXTENSION;

        // Розбираємо ключ
        if (! str_contains($key, '.')) {
            // Якщо ключ без точки, значить це ім'я конфігу
            $configName = $key;
            $path = null;
        } else {
            $parts = explode('.', $key, 2);
            $configName = $parts[0];
            $path = $parts[1] ?? null;
        }

        // Завантажуємо конфігурацію, якщо її немає в пам'яті
        if (! isset($this->loadedConfigs[$configName])) {
            $this->loadConfig($configName, $extension);
        }

        $config = $this->loadedConfigs[$configName];

        if ($path === null) {
            // Повертаємо весь конфіг
            return $config;
        }

        // Отримуємо значення за шляхом
        return $this->getValueByPath($config, $path, $default);
    }

    // Встановлення значення конфігурації
    public function set(string $key, mixed $value): void
    {
        $extension = self::DEFAULT_EXTENSION;

        // Розбираємо ключ
        if (! str_contains($key, '.')) {
            throw new Exception("Для встановлення значення потрібно вказати шлях (наприклад, 'config.key')");
        }

        $parts = explode('.', $key, 2);
        $configName = $parts[0];
        $path = $parts[1];

        // Завантажуємо конфігурацію, якщо її немає в пам'яті
        if (! isset($this->loadedConfigs[$configName])) {
            $this->loadConfig($configName, $extension);
        }

        // Встановлюємо значення за шляхом
        $this->setValueByPath($this->loadedConfigs[$configName], $path, $value);

        // Зберігаємо конфігурацію
        $this->saveConfig($configName, $extension);
    }

    // Перевірка наявності ключа в конфігурації
    public function has(string $key, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;

        // Розбираємо ключ
        if (! str_contains($key, '.')) {
            $configName = $key;
            $path = null;
        } else {
            $parts = explode('.', $key, 2);
            $configName = $parts[0];
            $path = $parts[1] ?? null;
        }

        // Завантажуємо конфігурацію, якщо її немає в пам'яті
        if (! isset($this->loadedConfigs[$configName])) {
            $this->loadConfig($configName, $extension);
        }

        if ($path === null) {
            return isset($this->loadedConfigs[$configName]);
        }

        $config = $this->loadedConfigs[$configName];

        return $this->hasValueByPath($config, $path);
    }

    // Видалення значення з конфігурації
    public function remove(string $key, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;

        if (! str_contains($key, '.')) {
            throw new Exception("Для видалення значення потрібно вказати шлях (наприклад, 'config.key')");
        }

        $parts = explode('.', $key, 2);
        $configName = $parts[0];
        $path = $parts[1];

        // Завантажуємо конфігурацію, якщо її немає в пам'яті
        if (! isset($this->loadedConfigs[$configName])) {
            $this->loadConfig($configName, $extension);
        }

        // Видаляємо значення за шляхом
        $this->removeValueByPath($this->loadedConfigs[$configName], $path);

        // Зберігаємо конфігурацію
        $this->saveConfig($configName, $extension);
    }

    // Видалити ключ з конфігурації (реалізація інтерфейсу)
    public function forget(string $key): void
    {
        $this->remove($key);
    }

    // Завантаження конфігурації
    private function loadConfig(string $configName, ?string $extension = null): void
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $filePath = $this->getConfigPath($configName, $extension);

        if ($extension === '.json') {
            if (file_exists($filePath)) {
                try {
                    $config = JsonHelper::readFile($filePath, true);
                    $this->loadedConfigs[$configName] = is_array($config) ? $config : [];
                } catch (Exception $e) {
                    Log::Error("Config load error for '{$configName}': " . $e->getMessage(), ['exception' => $e]);
                    $this->loadedConfigs[$configName] = [];
                }
            } else {
                $this->loadedConfigs[$configName] = [];
            }
        } elseif ($extension === '.ini') {
            if (file_exists($filePath)) {
                try {
                    $config = IniHelper::readFile($filePath, true);
                    $this->loadedConfigs[$configName] = is_array($config) ? $config : [];
                } catch (Exception $e) {
                    Log::Error("Config load error for '{$configName}': " . $e->getMessage(), ['exception' => $e]);
                    $this->loadedConfigs[$configName] = [];
                }
            } else {
                // Файл не существует - возвращаем пустой массив
                // Создание конфигурационных файлов происходит только через ConfigurationInitializer
                $this->loadedConfigs[$configName] = [];
            }
        } else {
            throw new Exception("Непідтримуване розширення файлу: {$extension}");
        }
    }

    // Збереження конфігурації
    private function saveConfig(string $configName, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $filePath = $this->getConfigPath($configName, $extension);

        if (! isset($this->loadedConfigs[$configName])) {
            return false;
        }

        $data = $this->loadedConfigs[$configName];

        if ($extension === '.json') {
            try {
                return JsonHelper::writeFile($filePath, $data, true);
            } catch (Exception $e) {
                Log::Error("Config save error for '{$configName}': " . $e->getMessage(), ['exception' => $e]);

                return false;
            }
        } elseif ($extension === '.ini') {
            try {
                return IniHelper::writeFile($filePath, $data);
            } catch (Exception $e) {
                Log::Error("Config save error for '{$configName}': " . $e->getMessage(), ['exception' => $e]);

                return false;
            }
        } else {
            throw new Exception("Непідтримуване розширення файлу: {$extension}");
        }
    }

    // Отримання шляху до файлу конфігурації
    private function getConfigPath(string $configName, string $extension): string
    {
        // Безпека: прибираємо неприпустимі символи з імені конфігу (но сохраняем дефисы и подчеркивания)
        $configName = preg_replace('/[^a-zA-Z0-9_-]/', '', $configName);

        // Определяем категорию конфигурации (system, flowaxy, plugin, theme или root)
        $category = self::getConfigCategory($configName);

        if ($category === 'system') {
            // Системные конфигурации в storage/config/system/
            return $this->configDir . 'system' . DS . $configName . $extension;
        }

        if ($category === 'flowaxy') {
            // Конфигурации flowaxy в storage/config/flowaxy/
            // Если configName содержит '/', используем его как путь
            if (str_contains($configName, '/')) {
                $parts = explode('/', $configName, 2);
                return $this->configDir . 'flowaxy' . DS . $parts[1] . $extension;
            }
            return $this->configDir . 'flowaxy' . DS . $configName . $extension;
        }

        // Остальные конфигурации в корне storage/config/
        return $this->configDir . $configName . $extension;
    }

    /**
     * Определение категории конфигурационного файла
     *
     * @param string $configName Имя конфигурации
     * @return string Категория: 'system', 'flowaxy', 'plugin', 'theme' или 'root'
     */
    private static function getConfigCategory(string $configName): string
    {
        // Системные конфигурации (модули движка)
        $systemConfigs = [
            'app',
            'cache',
            'database',
            'feature-flags',
            'modules',
            'security',
            'logging',
            'session',
            'mail',
            'queue',
        ];

        if (in_array($configName, $systemConfigs, true)) {
            return 'system';
        }

        // Конфигурации flowaxy (например, services)
        if ($configName === 'flowaxy' || str_starts_with($configName, 'flowaxy/')) {
            return 'flowaxy';
        }

        // По умолчанию - корневая конфигурация
        return 'root';
    }

    /**
     * Публичный метод для явного создания конфигурационного файла с дефолтными значениями
     *
     * ВНИМАНИЕ: Для системных конфигураций (app, cache, database, feature-flags, modules, security, logging)
     * создание файлов должно происходить ТОЛЬКО через ConfigurationInitializer::ensureConfigFiles().
     * Этот метод используется только для несистемных конфигураций.
     *
     * @param string $configName Имя конфигурации
     * @param string $extension Расширение файла (.ini или .json)
     * @return bool Успешно ли создан файл
     */
    public function ensureConfig(string $configName, string $extension = '.ini'): bool
    {
        $filePath = $this->getConfigPath($configName, $extension);

        // Если файл уже существует, не создаем его
        if (file_exists($filePath)) {
            return true;
        }

        // Проверяем, является ли это системной конфигурацией
        $category = self::getConfigCategory($configName);
        if ($category === 'system') {
            // Системные конфигурации должны создаваться только через ConfigurationInitializer
            try {
                if (class_exists(Log::class)) {
                    Log::Warning("Config::ensureConfig: System config '{$configName}' should be created via ConfigurationInitializer", [
                        'config_name' => $configName,
                        'file_path' => $filePath
                    ]);
                } else {
                    error_log("Config::ensureConfig: System config '{$configName}' should be created via ConfigurationInitializer. File path: {$filePath}");
                }
            } catch (Exception $logError) {
                error_log("Config::ensureConfig: System config '{$configName}' should be created via ConfigurationInitializer");
            }
            return false;
        }

        // Вызываем приватный метод для создания файла (только для несистемных конфигураций)
        $this->ensureConfigFile($configName, $extension);

        return file_exists($filePath);
    }

    /**
     * Создание файла конфигурации с дефолтными значениями
     *
     * ВНИМАНИЕ: Используется ТОЛЬКО для несистемных конфигураций.
     * Системные конфигурации создаются через ConfigurationInitializer::ensureConfigFiles().
     *
     * @param string $configName Имя конфигурации
     * @param string $extension Расширение файла
     * @return void
     */
    private function ensureConfigFile(string $configName, string $extension): void
    {
        $filePath = $this->getConfigPath($configName, $extension);

        // Если файл уже существует, не создаем его
        if (file_exists($filePath)) {
            return;
        }

        // Защита от перезаписи installed.flag
        if ($configName === 'installed' && $extension === '.flag') {
            return;
        }

        // Создаем директорию, если её нет
        $configDir = dirname($filePath);
        if (!is_dir($configDir)) {
            if (!@mkdir($configDir, 0755, true) && !is_dir($configDir)) {
                try {
                    if (class_exists(Log::class)) {
                        Log::Error("Config::ensureConfigFile: Failed to create config directory", [
                            'config_dir' => $configDir,
                            'config_name' => $configName
                        ]);
                    } else {
                        error_log("Config::ensureConfigFile: Failed to create config directory: {$configDir}");
                    }
                } catch (Exception $logError) {
                    // Игнорируем ошибки логирования
                    error_log("Config::ensureConfigFile: Failed to create config directory: {$configDir}");
                }
                return;
            }
        }

        // Для несистемных конфигураций дефолтные данные не определены в Config
        // Если нужны дефолтные значения, они должны быть определены в вызывающем коде
        // или конфигурация должна быть создана вручную

        try {
            if (class_exists(Log::class)) {
                Log::Warning("Config::ensureConfigFile: No default data available for config '{$configName}'. File creation skipped.", [
                    'config_name' => $configName,
                    'extension' => $extension,
                    'file_path' => $filePath,
                    'note' => 'System configs should be created via ConfigurationInitializer. Non-system configs require explicit data.'
                ]);
            } else {
                error_log("Config::ensureConfigFile: No default data available for config '{$configName}'. File path: {$filePath}");
            }
        } catch (Exception $logError) {
            error_log("Config::ensureConfigFile: No default data available for config '{$configName}'. File path: {$filePath}");
        }
    }


    // Отримання значення за шляхом (точкова нотація)
    private function getValueByPath($data, string $path, $default = null)
    {
        $keys = explode('.', $path);
        $value = $data;

        foreach ($keys as $key) {
            if (is_array($value)) {
                if (isset($value[$key])) {
                    $value = $value[$key];
                } else {
                    return $default;
                }
            } elseif (is_object($value)) {
                if (isset($value->$key)) {
                    $value = $value->$key;
                } else {
                    return $default;
                }
            } else {
                return $default;
            }
        }

        return $value;
    }

    // Встановлення значення за шляхом (точкова нотація)
    private function setValueByPath(&$data, string $path, $value): void
    {
        $keys = explode('.', $path);
        $lastKey = array_pop($keys);
        $target = &$data;

        foreach ($keys as $key) {
            if (is_array($target)) {
                if (! isset($target[$key]) || ! is_array($target[$key])) {
                    $target[$key] = [];
                }
                $target = &$target[$key];
            } elseif (is_object($target)) {
                if (! isset($target->$key) || ! is_array($target->$key)) {
                    $target->$key = [];
                }
                $target = &$target->$key;
            } else {
                $target = (array)$target;
                if (! isset($target[$key])) {
                    $target[$key] = [];
                }
                $target = &$target[$key];
            }
        }

        if (is_array($target)) {
            $target[$lastKey] = $value;
        } elseif (is_object($target)) {
            $target->$lastKey = $value;
        } else {
            $target = [$lastKey => $value];
        }
    }

    // Перевірка наявності значення за шляхом
    private function hasValueByPath($data, string $path): bool
    {
        $keys = explode('.', $path);
        $value = $data;

        foreach ($keys as $key) {
            if (is_array($value)) {
                if (! isset($value[$key])) {
                    return false;
                }
                $value = $value[$key];
            } elseif (is_object($value)) {
                if (! isset($value->$key)) {
                    return false;
                }
                $value = $value->$key;
            } else {
                return false;
            }
        }

        return true;
    }

    // Видалення значення за шляхом
    private function removeValueByPath(&$data, string $path): void
    {
        $keys = explode('.', $path);
        $lastKey = array_pop($keys);
        $target = &$data;

        foreach ($keys as $key) {
            if (is_array($target)) {
                if (! isset($target[$key])) {
                    return;
                }
                $target = &$target[$key];
            } elseif (is_object($target)) {
                if (! isset($target->$key)) {
                    return;
                }
                $target = &$target->$key;
            } else {
                return;
            }
        }

        if (is_array($target)) {
            unset($target[$lastKey]);
        } elseif (is_object($target)) {
            unset($target->$lastKey);
        }
    }

    // Отримання всіх значень конфігурації (реалізація інтерфейсу)
    public function all(): array
    {
        return $this->loadedConfigs;
    }

    // Отримання всіх даних конкретної конфігурації
    public function getAll(string $configName, ?string $extension = null): array
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;

        if (! isset($this->loadedConfigs[$configName])) {
            $this->loadConfig($configName, $extension);
        }

        return $this->loadedConfigs[$configName] ?? [];
    }

    // Встановлення всіх даних конфігурації
    public function setAll(string $configName, array $data, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $this->loadedConfigs[$configName] = $data;

        return $this->saveConfig($configName, $extension);
    }

    // Очистка конфигурации из памяти
    public function clear(?string $configName = null): self
    {
        if ($configName === null) {
            $this->loadedConfigs = [];
        } else {
            unset($this->loadedConfigs[$configName]);
        }

        return $this;
    }

    // Перезагрузка конфигурации
    public function reload(string $configName, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        unset($this->loadedConfigs[$configName]);
        $this->loadConfig($configName, $extension);

        return isset($this->loadedConfigs[$configName]);
    }

    // Отримання шляху до директорії конфігурацій
    public function getConfigDir(): string
    {
        return $this->configDir;
    }

    // Отримання списку всіх конфігураційних файлів
    public function getConfigFiles(?string $extension = null): array
    {
        $files = [];

        if (! is_dir($this->configDir)) {
            return $files;
        }

        $pattern = $this->configDir . '*' . ($extension ?? '*');
        $foundFiles = glob($pattern);

        if ($foundFiles === false) {
            return $files;
        }

        foreach ($foundFiles as $file) {
            if (is_file($file)) {
                $filename = basename($file);
                // Пропускаємо службові файли
                if (in_array($filename, ['.htaccess', '.gitkeep'], true)) {
                    continue;
                }
                $files[] = $filename;
            }
        }

        return $files;
    }

    // Експорт конфігурації в JSON
    public function export(string $configName, ?string $extension = null): string
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $data = $this->getAll($configName, $extension);

        return JsonHelper::encodePretty($data) ?: '';
    }

    // Імпорт конфігурації з JSON
    public function import(string $configName, string $jsonData, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;

        try {
            $data = JsonHelper::decode($jsonData, true);

            return $this->setAll($configName, $data, $extension);
        } catch (Exception $e) {
            Log::Error("Config import error for '{$configName}': " . $e->getMessage());

            return false;
        }
    }

    // Копіювання конфігурації
    public function copy(string $sourceConfig, string $targetConfig, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $data = $this->getAll($sourceConfig, $extension);

        return $this->setAll($targetConfig, $data, $extension);
    }

    // Видалення конфігураційного файлу
    public function deleteFile(string $configName, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $filePath = $this->getConfigPath($configName, $extension);

        // Видаляємо з пам'яті
        unset($this->loadedConfigs[$configName]);

        // Видаляємо файл (використовуємо File клас)
        $file = new File($filePath);
        if ($file->exists()) {
            try {
                return $file->delete();
            } catch (Exception $e) {
                Log::Error('Config deleteFile error: ' . $e->getMessage());

                return false;
            }
        }

        return true;
    }

    // Перевірка існування конфігураційного файлу
    public function fileExists(string $configName, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $filePath = $this->getConfigPath($configName, $extension);
        $file = new File($filePath);

        return $file->exists();
    }

    // Отримання розміру конфігураційного файлу
    public function getFileSize(string $configName, ?string $extension = null): int
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $filePath = $this->getConfigPath($configName, $extension);
        $file = new File($filePath);

        if ($file->exists()) {
            return $file->getSize();
        }

        return -1;
    }

    // Отримання часу останньої зміни конфігураційного файлу
    public function getFileMTime(string $configName, ?string $extension = null)
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $filePath = $this->getConfigPath($configName, $extension);
        $file = new File($filePath);

        if ($file->exists()) {
            return $file->getMTime();
        }

        return false;
    }

    // Об'єднання двох конфігурацій
    public function merge(string $configName1, string $configName2, string $targetConfig, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $data1 = $this->getAll($configName1, $extension);
        $data2 = $this->getAll($configName2, $extension);
        $merged = array_merge_recursive($data1, $data2);

        return $this->setAll($targetConfig, $merged, $extension);
    }

    // Резервне копіювання конфігурації
    public function backup(string $configName, ?string $backupPath = null, ?string $extension = null)
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $filePath = $this->getConfigPath($configName, $extension);

        if (! file_exists($filePath)) {
            return false;
        }

        if ($backupPath === null) {
            $timestamp = date('Y-m-d_H-i-s');
            $backupName = $configName . '_backup_' . $timestamp . $extension;
            $backupPath = $this->configDir . $backupName;
        }

        // Створюємо директорію, якщо її немає
        $dir = dirname($backupPath);
        if (! is_dir($dir)) {
            if (! @mkdir($dir, 0755, true)) {
                Log::Error("Config backup: Failed to create directory: {$dir}");
                return false;
            }
        }

        // Копіюємо файл (використовуємо File клас)
        $sourceFile = new File($filePath);
        if ($sourceFile->exists()) {
            try {
                if ($sourceFile->copy($backupPath)) {
                    return $backupPath;
                }
            } catch (Exception $e) {
                Log::Error('Config backup error: ' . $e->getMessage());

                return false;
            }
        }

        return false;
    }

    // Відновлення конфігурації з резервної копії
    public function restore(string $backupPath, string $configName, ?string $extension = null): bool
    {
        $extension = $extension ?? self::DEFAULT_EXTENSION;
        $filePath = $this->getConfigPath($configName, $extension);

        if (! file_exists($backupPath)) {
            return false;
        }

        // Видаляємо з пам'яті
        unset($this->loadedConfigs[$configName]);

        // Копіюємо файл (використовуємо File клас)
        $backupFile = new File($backupPath);
        if ($backupFile->exists()) {
            try {
                if ($backupFile->copy($filePath)) {
                    return true;
                }
            } catch (Exception $e) {
                Log::Error('Config restore error: ' . $e->getMessage());

                return false;
            }
        }

        return false;
    }

    // Запобігання клонуванню та десеріалізації
    private function __clone()
    {
    }

    // Запобігання десеріалізації
    public function __wakeup(): void
    {
        throw new Exception('Cannot unserialize singleton');
    }

    // Статичні методи замість глобальних функцій
    // Отримання екземпляра конфігурації
    public static function instance(): self
    {
        return self::getInstance();
    }

    // Отримання значення конфігурації
    public static function getValue(string $key, mixed $default = null): mixed
    {
        return self::getInstance()->get($key, $default);
    }

    // Встановлення значення конфігурації
    public static function setValue(string $key, mixed $value): void
    {
        self::getInstance()->set($key, $value);
    }

    // Перевірка наявності ключа в конфігурації
    public static function hasValue(string $key): bool
    {
        return self::getInstance()->has($key);
    }
}
