<?php

declare(strict_types=1);

namespace Flowaxy\Support\Helpers;

use Exception;
use Flowaxy\Core\System\PathResolver;
use Flowaxy\Infrastructure\Logging\Logger;
use Flowaxy\Infrastructure\Persistence\Database\Database;
use Flowaxy\Infrastructure\Persistence\Database\DatabaseInterface;
use Flowaxy\Support\Facades\Log;
use Flowaxy\Support\Helpers\ExceptionHandler;
use PDO;
use Throwable;

use function class_exists;
use function constant;
use function defined;
use function file_exists;
use function interface_exists;
use function is_readable;
use function method_exists;
use function php_sapi_name;
use const DS;

// Хелпер для роботи з базою даних
// Обгортка над Database класом
final class DatabaseHelper
{
    // Отримання підключення до БД
    public static function getConnection(bool $showError = true): ?PDO
    {
        // Используем константы из database.ini
        $dbHost = defined('DB_HOST') ? constant('DB_HOST') : '';
        $dbName = defined('DB_NAME') ? constant('DB_NAME') : '';
        $dbUser = defined('DB_USER') ? constant('DB_USER') : 'root';
        $dbPass = defined('DB_PASS') ? constant('DB_PASS') : '';
        $dbCharset = defined('DB_CHARSET') ? constant('DB_CHARSET') : 'utf8mb4';

        // Перевіряємо, що конфігурація БД доступна
        if (empty($dbHost) || empty($dbName)) {
            if ($showError && php_sapi_name() !== 'cli') {
                // Не показуємо помилку БД, якщо конфігурація не встановлена (це нормально для встановлювача)
                // Просто повертаємо null
            }

            return null;
        }


        try {
            // Спочатку завантажуємо інтерфейс, якщо він ще не завантажений
            if (! interface_exists(DatabaseInterface::class)) {
                $interfaceFile = PathResolver::flowaxy() . DS . 'Infrastructure' . DS . 'Persistence' . DS . 'Database' . DS . 'DatabaseInterface.php';
                if (file_exists($interfaceFile) && is_readable($interfaceFile)) {
                    require_once $interfaceFile;
                }
            }

            if (! class_exists(Database::class)) {
                // Якщо Database ще не завантажено, намагаємося завантажити з нового розташування
                $dbFile = PathResolver::flowaxy() . DS . 'Infrastructure' . DS . 'Persistence' . DS . 'Database' . DS . 'Database.php';
                if (file_exists($dbFile)) {
                    require_once $dbFile;
                }
            }

            return Database::getInstance()->getConnection();
        } catch (Throwable $e) {
            // Завантажуємо ExceptionHandler, якщо він ще не завантажений
            if (! class_exists(ExceptionHandler::class)) {
                $exceptionHandlerFile = PathResolver::flowaxy() . DS . 'Support' . DS . 'Helpers' . DS . 'ExceptionHandler.php';
                if (file_exists($exceptionHandlerFile) && is_readable($exceptionHandlerFile)) {
                    require_once $exceptionHandlerFile;
                }
            }

            // Логуємо помилку БД
            $context = [
                'host' => $dbHost,
                'database' => $dbName,
            ];

            if (class_exists(ExceptionHandler::class)) {
                ExceptionHandler::logError(
                    'Помилка підключення до бази даних',
                    $e,
                    $context
                );
            } else {
                // Fallback: використовуємо Log facade або error_log
                try {
                    if (class_exists(Log::class)) {
                        Log::Error('Помилка підключення до бази даних', array_merge($context, [
                            'exception' => $e->getMessage(),
                            'file' => $e->getFile(),
                            'line' => $e->getLine(),
                        ]));
                    } else {
                        error_log('Database connection error: ' . $e->getMessage());
                    }
                } catch (Throwable $logError) {
                    // Ignore logging errors
                }
            }

            // ВАЖНО: Все ошибки выводятся ТОЛЬКО в логи, не пользователю
            // Согласно prompts.md: "Все ошибки, диагностика и отладка выводятся ИСКЛЮЧИТЕЛЬНО в логи"
            // Root пользователь может работать БЕЗ подключенной базы данных
            // Поэтому не выводим ошибку пользователю, только логируем
            if ($showError) {
                // Дополнительное логирование, если требуется
                try {
                    if (class_exists(Log::class)) {
                        Log::Error('Database connection error (logged only, not shown to user)', [
                            'exception' => $e,
                            'context' => $context,
                            'note' => 'Root user can work without database. Error logged only.',
                        ]);
                    }
                } catch (Throwable $logError) {
                    // Ignore logging errors
                }
                // НЕ выводим ошибку пользователю и НЕ делаем exit
                // Просто возвращаем null
            }

            return null;
        }
    }

    // Перевірка доступності БД
    public static function isAvailable(bool $showError = false): bool
    {
        // Перевіряємо, що константи БД визначені та не порожні
        $dbHost = defined('DB_HOST') ? constant('DB_HOST') : '';
        $dbName = defined('DB_NAME') ? constant('DB_NAME') : '';
        if (empty($dbHost) || empty($dbName)) {
            return false;
        }

        try {
            if (! class_exists(Database::class)) {
                // Якщо Database ще не завантажено, намагаємося завантажити з нового розташування
                $dbFile = PathResolver::flowaxy() . DS . 'Infrastructure' . DS . 'Persistence' . DS . 'Database' . DS . 'Database.php';
                if (file_exists($dbFile)) {
                    require_once $dbFile;
                }
            }
            $isAvailable = Database::getInstance()->isAvailable();

            // ВАЖНО: Все ошибки выводятся ТОЛЬКО в логи, не пользователю
            // Root пользователь может работать БЕЗ подключенной базы данных
            if (! $isAvailable && $showError) {
                // Дополнительное логирование, если требуется
                try {
                    if (class_exists(Log::class)) {
                        Log::Warning('Database is not available (logged only, not shown to user)', [
                            'host' => defined('DB_HOST') ? constant('DB_HOST') : '',
                            'database' => defined('DB_NAME') ? constant('DB_NAME') : '',
                            'note' => 'Root user can work without database. Error logged only.',
                        ]);
                    }
                } catch (Throwable $logError) {
                    // Ignore logging errors
                }
                // НЕ выводим ошибку пользователю и НЕ делаем exit
                // Просто возвращаем false
            }

            return $isAvailable;
        } catch (Exception $e) {
            try {
                Log::error('DatabaseHelper: isDatabaseAvailable error', ['error' => $e->getMessage(), 'exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore logging errors
            }

            // ВАЖНО: Все ошибки выводятся ТОЛЬКО в логи, не пользователю
            // Root пользователь может работать БЕЗ подключенной базы данных
            if ($showError) {
                // Дополнительное логирование, если требуется
                try {
                    if (class_exists(Log::class)) {
                        Log::Warning('DatabaseHelper::isAvailable exception (logged only, not shown to user)', [
                            'error' => $e->getMessage(),
                            'host' => defined('DB_HOST') ? constant('DB_HOST') : '',
                            'database' => defined('DB_NAME') ? constant('DB_NAME') : '',
                            'note' => 'Root user can work without database. Error logged only.',
                        ]);
                    }
                } catch (Throwable $logError) {
                    // Ignore logging errors
                }
                // НЕ выводим ошибку пользователю и НЕ делаем exit
                // Просто возвращаем false
            }

            return false;
        }
    }

    // Отримання екземпляра Database
    public static function getInstance(): DatabaseInterface
    {
        return Database::getInstance();
    }

    // Перевірка існування таблиці в базі даних
    public static function tableExists(string $tableName): bool
    {
        try {
            if (! class_exists(Database::class)) {
                // Якщо Database ще не завантажено, намагаємося завантажити з нового розташування
                $dbFile = PathResolver::flowaxy() . DS . 'Infrastructure' . DS . 'Persistence' . DS . 'Database' . DS . 'Database.php';
                if (file_exists($dbFile)) {
                    require_once $dbFile;
                }
            }

            $db = Database::getInstance();
            $connection = $db->getConnection();

            $stmt = $connection->prepare('
                SELECT COUNT(*) as count
                FROM information_schema.tables
                WHERE table_schema = ?
                AND table_name = ?
            ');
            $dbName = defined('DB_NAME') ? constant('DB_NAME') : '';
            $stmt->execute([$dbName, $tableName]);
            $result = $stmt->fetch(PDO::FETCH_ASSOC);

            return isset($result['count']) && (int)$result['count'] > 0;
        } catch (Exception $e) {
            // Завантажуємо ExceptionHandler, якщо він ще не завантажений
            if (! class_exists(ExceptionHandler::class)) {
                $exceptionHandlerFile = PathResolver::flowaxy() . DS . 'Support' . DS . 'Helpers' . DS . 'ExceptionHandler.php';
                if (file_exists($exceptionHandlerFile) && is_readable($exceptionHandlerFile)) {
                    require_once $exceptionHandlerFile;
                }
            }

            if (class_exists(ExceptionHandler::class)) {
                ExceptionHandler::logError(
                    'Перевірка існування таблиці не вдалася',
                    $e,
                    ['table' => $tableName]
                );
            } else {
                // Fallback: використовуємо Log facade або error_log
                try {
                    if (class_exists(Log::class)) {
                        Log::Error('Перевірка існування таблиці не вдалася', [
                            'table' => $tableName,
                            'exception' => $e->getMessage(),
                            'file' => $e->getFile(),
                            'line' => $e->getLine(),
                        ]);
                    } else {
                        error_log('Table existence check failed: ' . $e->getMessage());
                    }
                } catch (Throwable $logError) {
                    // Ignore logging errors
                }
            }

            return false;
        }
    }

    // Перевірка існування всіх вказаних таблиць
    public static function checkTables(array $tables): array
    {
        $exists = [];
        $missing = [];

        foreach ($tables as $table) {
            if (self::tableExists($table)) {
                $exists[] = $table;
            } else {
                $missing[] = $table;
            }
        }

        return [
            'exists' => $exists,
            'missing' => $missing,
        ];
    }

    // Виконати SQL запит
    public static function query(string $query, array $params = []): \PDOStatement|false
    {
        // Завантажуємо ExceptionHandler, якщо він ще не завантажений
        if (! class_exists(ExceptionHandler::class)) {
            $exceptionHandlerFile = PathResolver::flowaxy() . DS . 'Support' . DS . 'Helpers' . DS . 'ExceptionHandler.php';
            if (file_exists($exceptionHandlerFile) && is_readable($exceptionHandlerFile)) {
                require_once $exceptionHandlerFile;
            }
        }

        if (! class_exists(ExceptionHandler::class)) {
            // Fallback: виконуємо без ExceptionHandler
            try {
                $db = Database::getInstance();
                $connection = $db->getConnection();
                $stmt = $connection->prepare($query);
                $stmt->execute($params);
                return $stmt;
            } catch (Throwable $e) {
                try {
                    if (class_exists(Log::class)) {
                        Log::Error('SQL query execution failed', [
                            'query' => $query,
                            'params' => $params,
                            'exception' => $e->getMessage(),
                        ]);
                    } else {
                        error_log('SQL query execution failed: ' . $e->getMessage());
                    }
                } catch (Throwable $logError) {
                    // Ignore logging errors
                }
                return false;
            }
        }

        return ExceptionHandler::safeExecute(
            function () use ($query, $params) {
                $db = Database::getInstance();
                $connection = $db->getConnection();
                $stmt = $connection->prepare($query);
                $stmt->execute($params);
                return $stmt;
            },
            false,
            'Помилка виконання SQL запиту',
            ['query' => $query]
        );
    }

    // Отримати один рядок
    public static function fetchOne(string $query, array $params = []): array|false
    {
        $stmt = self::query($query, $params);
        if ($stmt === false) {
            return false;
        }

        return $stmt->fetch(PDO::FETCH_ASSOC);
    }

    // Отримати всі рядки
    public static function fetchAll(string $query, array $params = []): array
    {
        $stmt = self::query($query, $params);
        if ($stmt === false) {
            return [];
        }

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    // Отримати значення одного поля
    public static function fetchValue(string $query, array $params = []): mixed
    {
        $result = self::fetchOne($query, $params);
        if ($result === false) {
            return null;
        }

        return reset($result);
    }

    // Почати транзакцію
    public static function beginTransaction(): bool
    {
        try {
            $db = Database::getInstance();
            $connection = $db->getConnection();

            return $connection->beginTransaction();
        } catch (Exception $e) {
            return false;
        }
    }

    // Підтвердити транзакцію
    public static function commit(): bool
    {
        try {
            $db = Database::getInstance();
            $connection = $db->getConnection();

            return $connection->commit();
        } catch (Exception $e) {
            return false;
        }
    }

    // Відкотити транзакцію
    public static function rollback(): bool
    {
        try {
            $db = Database::getInstance();
            $connection = $db->getConnection();

            return $connection->rollBack();
        } catch (Exception $e) {
            return false;
        }
    }

    // Виконати код в транзакції
    public static function transaction(callable $callback): mixed
    {
        self::beginTransaction();

        try {
            $result = $callback();
            self::commit();

            return $result;
        } catch (Exception $e) {
            self::rollback();
            throw $e;
        }
    }
}
