<?php

/**
 * Клас для роботи з сесіями
 * Управління сесіями користувачів з додатковою безпекою
 *
 * @package Flowaxy\Infrastructure\Security
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Security;

// Завантажуємо Settings facade, якщо він ще не завантажений
if (! class_exists(\Flowaxy\Support\Facades\Settings::class)) {
    try {
        $flowaxyDir = \Flowaxy\Core\System\PathResolver::flowaxy();
        $settingsFile = $flowaxyDir . DS . 'Support' . DS . 'Facades' . DS . 'Settings.php';
        if (file_exists($settingsFile) && is_readable($settingsFile)) {
            require_once $settingsFile;
        }
    } catch (\Throwable $e) {
        // Якщо PathResolver недоступний, пропускаємо завантаження
    }
}

// Завантажуємо Facade базовий клас, якщо він ще не завантажений
if (! class_exists(\Flowaxy\Support\Facades\Facade::class)) {
    try {
        $flowaxyDir = \Flowaxy\Core\System\PathResolver::flowaxy();
        $facadeFile = $flowaxyDir . DS . 'Support' . DS . 'Facades' . DS . 'Facade.php';
        if (file_exists($facadeFile) && is_readable($facadeFile)) {
            require_once $facadeFile;
        }
    } catch (\Throwable $e) {
        // Якщо PathResolver недоступний, пропускаємо завантаження
    }
}

// Завантажуємо Logger класс, якщо він ще не завантажений (нужен для Log facade)
if (! class_exists(\Flowaxy\Infrastructure\Logging\Logger::class)) {
    try {
        $flowaxyDir = \Flowaxy\Core\System\PathResolver::flowaxy();
        $loggerFile = $flowaxyDir . DS . 'Infrastructure' . DS . 'Logging' . DS . 'Logger.php';
        if (file_exists($loggerFile) && is_readable($loggerFile)) {
            require_once $loggerFile;
        }
    } catch (\Throwable $e) {
        // Якщо PathResolver недоступний, пропускаємо завантаження
    }
}

// Завантажуємо Log facade, якщо він ще не завантажений
if (! class_exists(\Flowaxy\Support\Facades\Log::class)) {
    try {
        $flowaxyDir = \Flowaxy\Core\System\PathResolver::flowaxy();
        $logFile = $flowaxyDir . DS . 'Support' . DS . 'Facades' . DS . 'Log.php';
        if (file_exists($logFile) && is_readable($logFile)) {
            require_once $logFile;
        }
    } catch (\Throwable $e) {
        // Якщо PathResolver недоступний, пропускаємо завантаження
    }
}

use Flowaxy\Contracts\Security\SessionInterface;
use Flowaxy\Infrastructure\Config\SystemConfig;
use Flowaxy\Interface\Http\Cookie;
use Flowaxy\Support\Facades\Log;
use Flowaxy\Support\Facades\Settings;

// TODO: SystemConfig needs to be migrated
// TODO: PathResolver needs to be migrated
// TODO: Cookie needs to be migrated

final class Session implements SessionInterface
{
    private static bool $started = false;
    private static array $config = [
        'name' => 'PHPSESSID',
        'lifetime' => 7200, // Будет переопределено из настроек
        'domain' => '',
        'path' => '/',
        'secure' => false,
        'httponly' => true,
        'samesite' => 'Lax',
    ];

    /**
     * Ініціалізація сесії
     *
     * @param array<string, mixed> $config Конфігурація сесії
     * @return void
     */
    public static function start(array $config = []): void
    {
        if (self::$started || session_status() === PHP_SESSION_ACTIVE) {
            self::$started = true;

            return;
        }

        // Якщо заголовки вже відправлені, ми НЕ маємо права змінювати параметри сесії.
        // Щоб уникнути PHP warnings (session_name/session_set_cookie_params/ini_set після headers_sent),
        // просто логуємо попередження і виходимо.
        if (headers_sent()) {
            try {
                Log::Warning('Session::start: Headers already sent at start(), skipping session initialization');
            } catch (\Throwable $e) {
                // Ігноруємо помилки логування
            }

            return;
        }

        // Завантажуємо параметри з налаштувань, якщо доступні
        if (class_exists(SystemConfig::class)) {
            $systemConfig = SystemConfig::getInstance();
            $defaultConfig = [
                'name' => $systemConfig->getSessionName(),
                'lifetime' => $systemConfig->getSessionLifetime(),
            ];
            self::$config = array_merge(self::$config, $defaultConfig);
        }

        self::$config = array_merge(self::$config, $config);

        // Визначаємо secure на основі налаштувань з бази даних (якщо доступні)
        $isSecure = self::$config['secure'];

        // Перевіряємо налаштування протоколу з бази даних
        $protocolFromSettings = null;
        $dbConfigPath = \Flowaxy\Core\System\PathResolver::storageConfig() . DS . 'system' . DS . 'database.ini';
        // ВАЖЛИВО: якщо БД вимкнена (status=false) — DB_* константи не визначаються,
        // тому ми НЕ повинні лізти в Settings/БД взагалі.
        $dbEnabled = defined('DB_HOST') && defined('DB_NAME') && is_string(DB_NAME) && DB_NAME !== '';
        if ($dbEnabled && class_exists('Flowaxy\Support\Managers\SettingsManager') && file_exists($dbConfigPath)) {
            try {
                // Використовуємо правильний клас Settings замість SettingsFacade
                if (class_exists(\Flowaxy\Support\Facades\Settings::class)) {
                    $settingsManager = \Flowaxy\Support\Facades\Settings::manager();
                } elseif (class_exists(\Flowaxy\Support\Facades\SettingsFacade::class)) {
                    // Fallback для зворотної сумісності
                    $settingsManager = \Flowaxy\Support\Facades\SettingsFacade::manager();
                } else {
                    $settingsManager = null;
                }

                if ($settingsManager !== null) {
                    $protocolSetting = $settingsManager->get('site_protocol', 'auto');
                    if ($protocolSetting === 'https') {
                        $protocolFromSettings = 'https://';
                    } elseif ($protocolSetting === 'http') {
                        $protocolFromSettings = 'http://';
                    }
                }
            } catch (\Exception $e) {
                // Ігноруємо помилки при завантаженні налаштувань
            }
        }

        // Якщо в налаштуваннях явно вказано протокол, використовуємо його
        if ($protocolFromSettings === 'https://') {
            $isSecure = true;
        } elseif ($protocolFromSettings === 'http://') {
            $isSecure = false;
        } else {
            // Якщо налаштування 'auto' або недоступні, перевіряємо реальне з'єднання
            if ($isSecure) {
                // Додаткова перевірка реального протоколу
                $realHttps = (
                    (!empty(\Flowaxy\Infrastructure\Security\RequestFilter::server('HTTPS', '', 'string')) && \Flowaxy\Infrastructure\Security\RequestFilter::server('HTTPS', '', 'string') !== 'off') ||
                    (\Flowaxy\Infrastructure\Security\RequestFilter::server('REQUEST_SCHEME', '', 'string') === 'https') ||
                    ((int)\Flowaxy\Infrastructure\Security\RequestFilter::server('SERVER_PORT', 0, 'int') === 443) ||
                    (\Flowaxy\Infrastructure\Security\RequestFilter::server('HTTP_X_FORWARDED_PROTO', '', 'string') === 'https')
                );

                // Якщо реальне з'єднання HTTP, але secure=true, вимикаємо secure для сумісності
                if (! $realHttps) {
                    $isSecure = false;
                    Log::Warning("Session::start: secure=true but connection is HTTP, disabling secure flag for compatibility");
                }
            }
        }

        // SameSite налаштування (важливо для Edge)
        $samesite = self::$config['samesite'] ?? 'Lax';
        // Якщо SameSite=None, але secure=false, Edge блокує - змінюємо на Lax
        if ($samesite === 'None' && ! $isSecure) {
            $samesite = 'Lax';
            Log::Warning('Session::start: SameSite=None requires Secure flag, changing to Lax for compatibility');
        }

        session_name(self::$config['name']);

        // Визначаємо domain правильно для Edge
        // Edge дуже суворий до domain - краще використовувати порожній domain для точного співпадіння
        $cookieDomain = self::$config['domain'];
        if (empty($cookieDomain)) {
            // Для Edge краще використовувати порожній domain - браузер сам визначить домен
            // Це гарантує, що cookie працюватиме для точного домену
            $cookieDomain = '';
        }

        // ВАЖЛИВО: session_set_cookie_params() має викликатися ДО session_start()
        // Це критично для Edge та інших браузерів
        session_set_cookie_params([
            'lifetime' => self::$config['lifetime'],
            'path' => self::$config['path'],
            'domain' => $cookieDomain, // Використовуємо правильно визначений domain
            'secure' => $isSecure,
            'httponly' => self::$config['httponly'],
            'samesite' => $samesite,
        ]);

        // Налаштування параметрів сесії через ini_set (для сумісності)
        ini_set('session.cookie_lifetime', self::$config['lifetime']);
        ini_set('session.cookie_path', self::$config['path']);
        ini_set('session.cookie_domain', $cookieDomain); // Використовуємо правильно визначений domain
        ini_set('session.cookie_secure', $isSecure ? '1' : '0');
        ini_set('session.cookie_httponly', self::$config['httponly'] ? '1' : '0');

        // Настройка SameSite (PHP 8.4 поддерживает напрямую)
        ini_set('session.cookie_samesite', $samesite);

        if (! headers_sent()) {
            // Зберігаємо старий session_id перед стартом, якщо сесія вже існує
            $oldSessionId = session_id();
            $sessionExists = !empty($oldSessionId);

            session_start();
            self::$started = true;

            // Логуємо, чи сесія була відновлена або створена нова
            Log::Debug('Session::start: Session started', [
                'session_id' => session_id(),
                'old_session_id' => $oldSessionId,
                'session_restored' => $sessionExists && session_id() === $oldSessionId,
                'new_session' => !$sessionExists,
            ]);

            Log::Info('Session::start: Session started successfully', [
                'name' => self::$config['name'],
                'lifetime' => self::$config['lifetime'],
                'secure' => $isSecure,
                'samesite' => $samesite,
            ]);
        } else {
            Log::Warning('Session::start: Headers already sent, cannot start session');
        }
    }

    /**
     * Отримання значення з сесії
     *
     * @param string $key Ключ
     * @param mixed $default Значення за замовчуванням
     * @return mixed
     */
    public static function get(string $key, mixed $default = null): mixed
    {
        self::ensureStarted();

        return $_SESSION[$key] ?? $default;
    }

    /**
     * Встановлення значення в сесію
     *
     * @param string $key Ключ
     * @param mixed $value Значення
     * @return void
     */
    public static function set(string $key, $value): void
    {
        self::ensureStarted();
        $_SESSION[$key] = $value;
    }

    /**
     * Перевірка наявності ключа в сесії
     *
     * @param string $key Ключ
     * @return bool
     */
    public static function has(string $key): bool
    {
        self::ensureStarted();

        return isset($_SESSION[$key]);
    }

    /**
     * Видалення значення з сесії
     *
     * @param string $key Ключ
     * @return void
     */
    public static function remove(string $key): void
    {
        self::ensureStarted();
        unset($_SESSION[$key]);
    }

    /**
     * Отримання всіх даних сесії
     *
     * @return array<string, mixed>
     */
    public static function all(): array
    {
        self::ensureStarted();

        return $_SESSION ?? [];
    }

    /**
     * Очищення всіх даних сесії
     *
     * @return void
     */
    public static function clear(): void
    {
        self::ensureStarted();
        $_SESSION = [];
    }

    /**
     * Видалення сесії
     *
     * @return void
     */
    public static function destroy(): bool
    {
        if (! self::$started) {
            Log::Debug('Session::destroy: Session not started, nothing to destroy');
            return false;
        }

        Log::Info('Session::destroy: Destroying session');

        $_SESSION = [];

        // Використовуємо наш клас Cookie для видалення cookie
        $sessionName = session_name();
        if (\Flowaxy\Infrastructure\Security\RequestFilter::cookie($sessionName, null, 'string') !== null) {
            if (class_exists(Cookie::class)) {
                Cookie::set(session_name(), '', time() - 3600, self::$config['path'], self::$config['domain'], false, true);
            } else {
                setcookie(session_name(), '', time() - 3600, self::$config['path'], self::$config['domain']);
            }
        }

        session_destroy();
        self::$started = false;

        return true;
    }

    /**
     * Регенерація ID сесії
     *
     * @param bool $deleteOldSession Видаляти стару сесію
     * @return bool
     */
    public static function regenerate(bool $deleteOldSession = true): bool
    {
        self::ensureStarted();

        // Зберігаємо CSRF токен перед регенерацією, щоб він не був втрачений
        $csrfToken = $_SESSION['csrf_token'] ?? null;

        $result = session_regenerate_id($deleteOldSession);

        // Відновлюємо CSRF токен після регенерації, якщо він був
        if ($result && $csrfToken !== null) {
            $_SESSION['csrf_token'] = $csrfToken;
            Log::Debug('Session::regenerate: CSRF token preserved after regeneration');
        }

        if ($result) {
            Log::Info('Session::regenerate: Session ID regenerated', [
                'delete_old' => $deleteOldSession,
                'csrf_token_preserved' => $csrfToken !== null,
            ]);
        } elseif (!$result) {
            Log::Error('Session::regenerate: Failed to regenerate session ID');
        }

        return $result;
    }

    /**
     * Отримання ID сесії
     *
     * @return string
     */
    public static function getId(): string
    {
        self::ensureStarted();

        return session_id();
    }

    /**
     * Встановлення ID сесії
     *
     * @param string $id ID сесії
     * @return bool
     */
    public static function setId(string $id): bool
    {
        if (self::$started) {
            return false;
        }

        return session_id($id) !== '';
    }

    /**
     * Перевірка, чи запущена сесія
     *
     * @return bool
     */
    public static function isStarted(): bool
    {
        return self::$started && session_status() === PHP_SESSION_ACTIVE;
    }

    /**
     * Отримання Flash повідомлення (читається один раз)
     *
     * @param string $key Ключ
     * @param mixed $default Значення за замовчуванням
     * @return mixed
     */
    public static function flash(string $key, $default = null)
    {
        $value = self::get('_flash.' . $key, $default);
        self::remove('_flash.' . $key);

        return $value;
    }

    /**
     * Встановлення Flash повідомлення
     *
     * @param string $key Ключ
     * @param mixed $value Значення
     * @return void
     */
    public static function setFlash(string $key, $value): void
    {
        self::set('_flash.' . $key, $value);
    }

    /**
     * Переконатися, що сесія запущена
     *
     * @return void
     */
    private static function ensureStarted(): void
    {
        if (! self::$started) {
            self::start();
        }
    }
}
