<?php

/**
 * Сервіс для обробки помилок
 *
 * @package Flowaxy\Modules\Errors\Services
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Core\System\Errors\Services;

use Flowaxy\Core\System\PathResolver;
use Exception;
use Throwable;

use function class_exists;
use function constant;
use function defined;
use function file_exists;
use function function_exists;
use function get_class;
use function header;
use function headers_sent;
use function http_response_code;
use function in_array;
use function is_readable;
use function ltrim;
use function method_exists;
use function ob_end_clean;
use function ob_get_level;
use function parse_url;
use function php_sapi_name;
use function preg_replace;
use function rtrim;
use function session_status;
use function str_contains;
use function str_ends_with;
use function str_replace;
use function str_starts_with;
use function strlen;
use function substr;

final class ErrorHandler
{
    private ErrorRenderer $renderer;
    private ErrorMessages $messages;

    public function __construct()
    {
        $this->renderer = new ErrorRenderer();
        $this->messages = new ErrorMessages();
    }

    /**
     * Відображення сторінки помилки 500 з детальною інформацією
     *
     * @param Throwable|null $exception Виняток, який викликав помилку
     * @param array<string, mixed>|null $errorInfo Додаткова інформація про помилку (file, line, message)
     * @return never
     */
    public function showError500Page(?Throwable $exception = null, ?array $errorInfo = null): never
    {
        // Встановлюємо код відповіді
        if (!headers_sent()) {
            http_response_code(500);
            header('Content-Type: text/html; charset=UTF-8');
        }

        // Визначаємо, чи показувати детальну інформацію
        $showDebugInfo = false;
        $showStackTrace = false;
        $isAuthenticated = false;

        // Перевіряємо, чи користувач авторизований
        if (session_status() === PHP_SESSION_ACTIVE && function_exists('sessionManager')) {
            try {
                $session = sessionManager();
                if ($session && method_exists($session, 'has') && $session->has('admin_user_id')) {
                    $userId = (int)$session->get('admin_user_id');
                    if ($userId > 0) {
                        $isAuthenticated = true;
                    }
                }
            } catch (Exception $e) {
                // Якщо не вдалося перевірити сесію, просто ігноруємо
            }
        }

        // Перевіряємо, чи це сторінка логіну
        $requestUri = $_SERVER['REQUEST_URI'] ?? '/';
        $isLoginPage = str_ends_with($requestUri, '/admin/login') || str_ends_with($requestUri, '/admin/logout');

        // Перевіряємо DEBUG_MODE
        if (defined('DEBUG_MODE') && constant('DEBUG_MODE')) {
            $showDebugInfo = true;
            // STACK TRACE показуємо тільки для авторизованих користувачів
            if ($isAuthenticated) {
                $showStackTrace = true;
            }
        }

        // Перевіряємо, чи це локальне середовище
        $host = $_SERVER['HTTP_HOST'] ?? '';
        $isLocal = in_array($host, ['localhost', '127.0.0.1'], true) ||
                   str_ends_with($host, '.local') ||
                   str_contains($host, 'dev.flowaxy.com') ||
                   (isset($_SERVER['SERVER_ADDR']) && in_array($_SERVER['SERVER_ADDR'], ['127.0.0.1', '::1'], true));

        // Показуємо деталі в локальному середовищі
        if ($isLocal) {
            $showDebugInfo = true;
            // STACK TRACE показуємо тільки для авторизованих користувачів
            if ($isAuthenticated) {
                $showStackTrace = true;
            }
        }

        // Якщо користувач авторизований - показуємо повну інформацію включаючи STACK TRACE
        if ($isAuthenticated) {
            $showDebugInfo = true;
            $showStackTrace = true;
        }

        // Якщо це сторінка логіну і користувач не авторизований - не показуємо STACK TRACE
        if ($isLoginPage && !$isAuthenticated) {
            $showStackTrace = false;
        }

        // Функція для конвертації абсолютного шляху в відносний
        $getRelativePath = function(string $absolutePath): string {
            $rootDir = PathResolver::root();
            $rootDir = rtrim(str_replace('\\', '/', $rootDir), '/');
            $absolutePath = str_replace('\\', '/', $absolutePath);

            if (str_starts_with($absolutePath, $rootDir)) {
                $relativePath = substr($absolutePath, strlen($rootDir));
                return '/' . ltrim($relativePath, '/');
            }

            return $absolutePath;
        };

        // Обробляємо шлях файлу
        $filePath = $exception ? $exception->getFile() : ($errorInfo['file'] ?? 'Невідомий файл');
        $relativeFilePath = $filePath !== 'Невідомий файл' ? $getRelativePath($filePath) : $filePath;

        // Обробляємо trace для нормалізації шляхів
        $trace = null;
        if ($showStackTrace && $exception) {
            $traceString = $exception->getTraceAsString();
            $rootDir = PathResolver::root();
            $rootDirNormalized = str_replace('\\', '/', rtrim($rootDir, '/\\'));

            // Замінюємо абсолютний шлях на відносний
            $traceString = str_replace('\\', '/', $traceString);
            $traceString = str_replace($rootDirNormalized, '', $traceString);
            // Виправляємо подвійні слеші
            $traceString = preg_replace('#(/+)#', '/', $traceString);
            $trace = $traceString;
        }

        // Збираємо інформацію про помилку
        $errorDetails = [
            'message' => $exception ? $exception->getMessage() : ($errorInfo['message'] ?? 'Невідома помилка'),
            'file' => $relativeFilePath,
            'line' => $exception ? $exception->getLine() : ($errorInfo['line'] ?? 0),
            'code' => $exception ? $exception->getCode() : ($errorInfo['code'] ?? 0),
            'trace' => $trace,
            'type' => $exception ? get_class($exception) : ($errorInfo['type'] ?? 'Error'),
            'showDebug' => $showDebugInfo,
        ];

        // Отримуємо локалізовані повідомлення
        $localized = $this->messages->getMessage(500);
        $actions = $this->messages->getActions(500);

        // Підготовка даних для шаблону
        $templateData = [
            'httpCode' => 500,
            'errorCode' => '500',
            'errorTitle' => $localized['title'],
            'errorMessage' => $localized['message'],
            'title' => "500 - {$localized['title']}",
            'actions' => $actions,
            'debugInfo' => $showDebugInfo ? $errorDetails : null,
        ];

        $this->renderer->render('error', $templateData);
        /** @phpstan-ignore-next-line */
        exit;
    }

    /**
     * Відображення сторінки помилки за HTTP кодом
     *
     * @param int $httpCode HTTP код помилки (400, 404, 500, тощо)
     * @param string|null $customTitle Користувацький заголовок
     * @param string|null $customMessage Користувацьке повідомлення
     * @param array<string, mixed>|null $debugInfo Debug інформація (для 500+)
     * @return never
     */
    public function showHttpError(int $httpCode, ?string $customTitle = null, ?string $customMessage = null, ?array $debugInfo = null): void
    {
        // Очищаємо всі буфери виводу перед виводом помилки
        while (ob_get_level() > 0) {
            ob_end_clean();
        }

        // Встановлюємо код відповіді
        if (!headers_sent()) {
            http_response_code($httpCode);
            header('Content-Type: text/html; charset=UTF-8');
        }

        // Отримуємо локалізовані повідомлення
        $localized = $this->messages->getMessage($httpCode);
        $actions = $this->messages->getActions($httpCode);

        // Підготовка даних для шаблону
        $templateData = [
            'httpCode' => $httpCode,
            'errorCode' => (string)$httpCode,
            'errorTitle' => $customTitle ?? $localized['title'],
            'errorMessage' => $customMessage ?? $localized['message'],
            'title' => "{$httpCode} - " . ($customTitle ?? $localized['title']),
            'actions' => $actions,
            'debugInfo' => $debugInfo,
        ];

        $this->renderer->render('error', $templateData);
        exit;
    }

    /**
     * Перевірка стану системи (БД, таблиці) перед обробкою запитів
     * Якщо система не готова - показує 500 помилку
     *
     * @return void
     */
    public function checkSystemState(): void
    {
        $requestUri = $_SERVER['REQUEST_URI'] ?? '/';
        $path = \parse_url($requestUri, PHP_URL_PATH) ?? '/';

        // Установщик удален - система работает без проверки установки
        // Root пользователь позволяет войти в админку даже без БД
        // Проверки БД не блокируют работу системы - они только логируются

        // Перевіряємо доступність БД (только для логирования, не блокируем работу)
        try {
            // Перевіряємо чи визначені константи БД
            if (!defined('DB_HOST') || empty(DB_HOST) || !defined('DB_NAME') || empty(DB_NAME)) {
                // Логируем, но не блокируем работу - система может работать без БД
                try {
                    if (class_exists(\Flowaxy\Support\Facades\Log::class)) {
                        \Flowaxy\Support\Facades\Log::Warning('ErrorHandler::checkSystemState: Database configuration not defined', [
                            'note' => 'System continues to work without database. Root user can access admin panel.'
                        ]);
                    }
                } catch (\Throwable $logError) {
                    // Ignore logging errors
                }
                return; // Не блокируем работу
            }

            // Перевіряємо чи доступна БД
            if (!class_exists('DatabaseHelper')) {
                $helperFile = __DIR__ . '/../../Support/Helpers/DatabaseHelper.php';
                if (file_exists($helperFile)) {
                    require_once $helperFile;
                }
            }

            if (class_exists('DatabaseHelper') && method_exists('DatabaseHelper', 'isAvailable')) {
                if (!\DatabaseHelper::isAvailable()) {
                    // Логируем, но не блокируем работу
                    try {
                        if (class_exists(\Flowaxy\Support\Facades\Log::class)) {
                            \Flowaxy\Support\Facades\Log::Warning('ErrorHandler::checkSystemState: Database is not available', [
                                'note' => 'System continues to work without database. Root user can access admin panel.'
                            ]);
                        }
                    } catch (\Throwable $logError) {
                        // Ignore logging errors
                    }
                    return; // Не блокируем работу
                }

                // Перевіряємо чи існують обов'язкові таблиці (users - основна таблиця)
                // Только для логирования, не блокируем работу
                try {
                    if (method_exists('DatabaseHelper', 'tableExists') && !\DatabaseHelper::tableExists('users')) {
                        // Логируем, но не блокируем работу
                        try {
                            if (class_exists(\Flowaxy\Support\Facades\Log::class)) {
                                \Flowaxy\Support\Facades\Log::Info('ErrorHandler::checkSystemState: Users table does not exist', [
                                    'note' => 'System continues to work. Root user can access admin panel. Database can be configured later.'
                                ]);
                            }
                        } catch (\Throwable $logError) {
                            // Ignore logging errors
                        }
                        return; // Не блокируем работу
                    }
                } catch (Exception $tableException) {
                    // Логируем ошибку проверки таблицы, но не блокируем работу
                    try {
                        if (class_exists(\Flowaxy\Support\Facades\Log::class)) {
                            \Flowaxy\Support\Facades\Log::Warning('ErrorHandler::checkSystemState: Error checking database structure', [
                                'error' => $tableException->getMessage(),
                                'code' => $tableException->getCode(),
                                'note' => 'System continues to work. Root user can access admin panel.'
                            ]);
                        }
                    } catch (\Throwable $logError) {
                        // Ignore logging errors
                    }
                    return; // Не блокируем работу
                }
            }
        } catch (Exception $e) {
            // Логируем критическую ошибку БД, но не блокируем работу
            try {
                if (class_exists(\Flowaxy\Support\Facades\Log::class)) {
                    \Flowaxy\Support\Facades\Log::Error('ErrorHandler::checkSystemState: Critical database error', [
                        'error' => $e->getMessage(),
                        'code' => $e->getCode(),
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                        'note' => 'System continues to work. Root user can access admin panel.'
                    ]);
                }
            } catch (\Throwable $logError) {
                // Ignore logging errors
            }
            return; // Не блокируем работу
        }
    }
}
