<?php

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Filesystem;

use Flowaxy\Core\System\PathResolver;
use Flowaxy\Domain\Plugin\Services\PluginFilesystemInterface;
use Flowaxy\Infrastructure\Persistence\Database\Database;
use Flowaxy\Support\Facades\Log;
use Flowaxy\Support\Helpers\DatabaseHelper;
use Flowaxy\Support\Helpers\FileHelper;
use Flowaxy\Support\Helpers\IniHelper;
use Flowaxy\Support\Helpers\JsonHelper;
use Flowaxy\Support\Helpers\ResourceCleanupHelper;
use PDO;
use PDOException;
use Throwable;

use function class_exists;
use function explode;
use function glob;
use function is_array;
use function method_exists;
use function preg_replace;
use function trim;
use function str_contains;
use const DS;

final class PluginFilesystem implements PluginFilesystemInterface
{
    private string $pluginsDir;

    public function __construct(?string $pluginsDir = null)
    {
        $this->pluginsDir = $pluginsDir ?? (PathResolver::plugins() . DS);
    }

    public function exists(string $slug): bool
    {
        return FileHelper::isDirectory($this->pluginsDir . $slug);
    }

    // Читання конфігурації плагіна
    public function readConfig(string $slug): ?array
    {
        try {
            $config = \Flowaxy\Support\Helpers\PluginMetadataHelper::readMetadata($slug);
            return !empty($config) ? $config : null;
        } catch (Throwable $e) {
            try {
                Log::Error("Помилка читання конфігурації плагіна", ['exception' => $e, 'slug' => $slug]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return null;
        }
    }

    public function runMigrations(string $slug): void
    {
        // Перевіряємо, чи є міграції у плагіна
        $migrationsDir = $this->pluginsDir . $slug . '/migrations';
        $dbDir = $this->pluginsDir . $slug . '/db';
        $hasMigrations = FileHelper::isDirectory($migrationsDir) || FileHelper::isDirectory($dbDir);

        $connection = $this->connection();
        if (!$connection) {
            // Якщо плагін має міграції, але БД не підключена - логуємо попередження
            if ($hasMigrations) {
                try {
                    Log::Warning("Плагін {$slug} має міграції, але база даних не підключена. Міграції не виконано.", [
                        'slug' => $slug,
                        'migrations_dir' => $migrationsDir,
                        'db_dir' => $dbDir,
                    ]);
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
            }
            return;
        }

        // Спочатку перевіряємо новий формат міграцій (PHP файли в migrations/)
        $migrationsDir = $this->pluginsDir . $slug . '/migrations';
        if (FileHelper::isDirectory($migrationsDir)) {
            $this->runPhpMigrations($slug, $migrationsDir, $connection);
            return; // Якщо знайдено PHP міграції, не виконуємо SQL
        }

        // Fallback на старий формат (SQL файли в db/)
        $dbDir = $this->pluginsDir . $slug . '/db';
        if (! FileHelper::isDirectory($dbDir)) {
            return;
        }

        $files = [];
        if (FileHelper::exists($dbDir . '/install.sql')) {
            $files[] = $dbDir . '/install.sql';
        } else {
            $files = glob($dbDir . '/*.sql') ?: [];
        }

        foreach ($files as $file) {
            $this->executeSqlFile($file);
        }
    }

    /**
     * Виконання PHP міграцій у форматі з ТЗ
     */
    private function runPhpMigrations(string $slug, string $migrationsDir, PDO $connection): void
    {
        // Отримуємо список виконаних міграцій з файлу конфігурації
        $executedMigrations = $this->getExecutedMigrations($slug);

        // Знаходимо всі PHP файли міграцій
        $migrationFiles = glob($migrationsDir . '/*.php') ?: [];

        // Сортуємо за ім'ям файлу (версія в імені)
        usort($migrationFiles, function($a, $b) {
            return strcmp(basename($a), basename($b));
        });

        foreach ($migrationFiles as $migrationFile) {
            $migrationName = basename($migrationFile, '.php');

            // Перевіряємо, чи вже виконана міграція
            if (in_array($migrationName, $executedMigrations, true)) {
                continue;
            }

            try {
                // Завантажуємо міграцію
                $migration = require $migrationFile;

                if (!is_array($migration) || !isset($migration['up']) || !is_callable($migration['up'])) {
                    try {
                        Log::Warning("Невірний формат міграції: {$migrationName}", ['slug' => $slug]);
                    } catch (Throwable $e) {
                        // Ignore logging errors
                    }
                    continue;
                }

                // Виконуємо міграцію в транзакції
                $connection->beginTransaction();
                try {
                    // Виконуємо міграцію
                    $migration['up']($connection);

                    // Коммітимо транзакцію
                    $connection->commit();

                    // Позначаємо міграцію як виконану в файлі конфігурації (тільки після успішного виконання)
                    $this->markMigrationAsExecuted($slug, $migrationName);

                    try {
                        Log::Info("Міграція виконана: {$migrationName}", [
                            'slug' => $slug,
                            'version' => $migration['version'] ?? 'unknown',
                            'description' => $migration['description'] ?? ''
                        ]);
                    } catch (Throwable $e) {
                        // Ignore logging errors
                    }
                } catch (Throwable $e) {
                    // Відкатуємо транзакцію при помилці
                    if ($connection->inTransaction()) {
                        $connection->rollBack();
                    }
                    throw $e; // Перекидаємо помилку для логування
                }
            } catch (Throwable $e) {
                try {
                    Log::Error("Помилка виконання міграції: {$migrationName}", [
                        'exception' => $e,
                        'slug' => $slug,
                        'message' => $e->getMessage(),
                        'file' => $e->getFile(),
                        'line' => $e->getLine()
                    ]);
                } catch (Throwable $logError) {
                    // Ignore logging errors
                }
            }
        }
    }

    /**
     * Отримати шлях до файлу конфігурації міграцій плагіна
     */
    private function getMigrationsConfigPath(string $slug): string
    {
        $configDir = PathResolver::storageConfig() . DS . 'plugins';
        if (!is_dir($configDir)) {
            mkdir($configDir, 0755, true);
        }
        return $configDir . DS . $slug . '-migrations.ini';
    }

    /**
     * Отримати список виконаних міграцій для плагіна з файлу конфігурації
     *
     * @return array<string>
     */
    private function getExecutedMigrations(string $slug): array
    {
        $configPath = $this->getMigrationsConfigPath($slug);

        if (!FileHelper::exists($configPath)) {
            return [];
        }

        try {
            $config = IniHelper::readFile($configPath);
            if (isset($config['migrations']) && is_array($config['migrations'])) {
                return array_keys($config['migrations']);
            }
        } catch (Throwable $e) {
            try {
                Log::Warning("Помилка читання конфігурації міграцій плагіна", [
                    'exception' => $e,
                    'slug' => $slug,
                    'path' => $configPath
                ]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
        }

        return [];
    }

    /**
     * Позначити міграцію як виконану в файлі конфігурації
     */
    private function markMigrationAsExecuted(string $slug, string $migrationName): void
    {
        $configPath = $this->getMigrationsConfigPath($slug);

        try {
            // Читаємо існуючу конфігурацію
            $config = [];
            if (FileHelper::exists($configPath)) {
                $config = IniHelper::readFile($configPath) ?: [];
            }

            // Додаємо міграцію до списку виконаних
            if (!isset($config['migrations'])) {
                $config['migrations'] = [];
            }

            $config['migrations'][$migrationName] = date('Y-m-d H:i:s');

            // Зберігаємо конфігурацію
            IniHelper::writeFile($configPath, $config, true);
        } catch (Throwable $e) {
            try {
                Log::Error("Помилка збереження конфігурації міграцій плагіна", [
                    'exception' => $e,
                    'slug' => $slug,
                    'migration' => $migrationName,
                    'path' => $configPath
                ]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
        }
    }

    public function delete(string $slug, string $path = ''): bool
    {
        if ($path === '') {
            // Видалення всього плагіна
            $dir = $this->pluginsDir . $slug;
            if (! FileHelper::isDirectory($dir)) {
                try {
                    Log::Warning('PluginFilesystem::delete: Plugin directory does not exist', [
                        'slug' => $slug,
                        'dir' => $dir,
                    ]);
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return false;
            }

            try {
                Log::Debug('PluginFilesystem::delete: Deleting plugin directory', [
                    'slug' => $slug,
                    'dir' => $dir,
                ]);
            } catch (Throwable $e) {
                // Ignore logging errors
            }

            ResourceCleanupHelper::deleteDirectory($dir);

            $deleted = ! FileHelper::isDirectory($dir);

            try {
                if ($deleted) {
                    Log::Info('PluginFilesystem::delete: Plugin directory deleted successfully', [
                        'slug' => $slug,
                        'dir' => $dir,
                    ]);
                } else {
                    Log::Warning('PluginFilesystem::delete: Plugin directory still exists after deletion', [
                        'slug' => $slug,
                        'dir' => $dir,
                    ]);
                }
            } catch (Throwable $e) {
                // Ignore logging errors
            }

            return $deleted;
        }

        // Видалення конкретного файлу
        $filePath = $this->getPath($slug, $path);
        if (! FileHelper::exists($filePath)) {
            return false;
        }

        return @unlink($filePath);
    }

    public function read(string $pluginSlug, string $path): string|false
    {
        $filePath = $this->getPath($pluginSlug, $path);
        if (! FileHelper::isReadable($filePath)) {
            return false;
        }

        return FileHelper::get($filePath) ?: false;
    }

    public function write(string $pluginSlug, string $path, string $content): bool
    {
        $filePath = $this->getPath($pluginSlug, $path);
        return FileHelper::put($filePath, $content) !== false;
    }

    public function list(string $pluginSlug, string $path = ''): array
    {
        $dirPath = $this->getPath($pluginSlug, $path);
        if (! FileHelper::isDirectory($dirPath)) {
            return [];
        }

        $files = glob($dirPath . '/*') ?: [];
        return array_map('basename', $files);
    }

    public function getPath(string $pluginSlug, string $path = ''): string
    {
        $basePath = $this->pluginsDir . $pluginSlug;
        if ($path === '') {
            return $basePath;
        }

        return $basePath . DS . ltrim($path, DS);
    }

    private function executeSqlFile(string $sqlFile): void
    {
        $connection = $this->connection();
        if (! $connection || ! FileHelper::isReadable($sqlFile)) {
            return;
        }

        try {
            $sql = FileHelper::get($sqlFile);
            if ($sql === false) {
                return;
            }

            $sql = preg_replace('/--.*$/m', '', $sql);
            $sql = preg_replace('/\/\*.*?\*\//s', '', $sql);
            $queries = explode(';', (string)$sql);

            foreach ($queries as $query) {
                $query = trim($query);
                if ($query === '') {
                    continue;
                }

                try {
                    // @phpstan-ignore-next-line - это метод PDO, а не системная команда exec()
                    $connection->exec($query);
                } catch (PDOException $e) {
                    $message = $e->getMessage();
                    if (! str_contains($message, 'Duplicate column') &&
                        ! str_contains($message, 'Duplicate key') &&
                        ! str_contains($message, 'already exists')) {
                        throw $e;
                    }
                }
            }
        } catch (Throwable $e) {
            try {
                Log::Error("Помилка виконання SQL файлу", ['exception' => $e, 'sql_file' => $sqlFile]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
        }
    }

    private function connection(): ?PDO
    {
        try {
            return DatabaseHelper::getConnection();
        } catch (Throwable $e) {
            try {
                Log::Error('Помилка підключення до бази даних', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }
            return null;
        }
    }

    /**
     * Перевірити, чи плагін має міграції
     *
     * @param string $slug
     * @return bool
     */
    public function hasMigrations(string $slug): bool
    {
        $migrationsDir = $this->pluginsDir . $slug . '/migrations';
        $dbDir = $this->pluginsDir . $slug . '/db';

        // Перевіряємо новий формат (PHP файли в migrations/)
        if (FileHelper::isDirectory($migrationsDir)) {
            $phpFiles = glob($migrationsDir . '/*.php') ?: [];
            if (!empty($phpFiles)) {
                return true;
            }
        }

        // Перевіряємо старий формат (SQL файли в db/)
        if (FileHelper::isDirectory($dbDir)) {
            if (FileHelper::exists($dbDir . '/install.sql')) {
                return true;
            }
            $sqlFiles = glob($dbDir . '/*.sql') ?: [];
            if (!empty($sqlFiles)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Перевірити, чи підключена база даних
     *
     * @return bool
     */
    public function isDatabaseConnected(): bool
    {
        return $this->connection() !== null;
    }
}
