<?php

declare(strict_types=1);

namespace App\Core;

final class Session
{
    private const IDLE_TIMEOUT_SECONDS = 1800;
    private const REGENERATE_EVERY_SECONDS = 300;

    public static function start(): void
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        self::enforceSecurity();
    }

    private static function enforceSecurity(): void
    {
        $now = time();

        if (isset($_SESSION['_last_activity']) && ($now - (int) $_SESSION['_last_activity']) > self::IDLE_TIMEOUT_SECONDS) {
            self::invalidate();
            return;
        }

        $agent = substr(hash('sha256', (string) ($_SERVER['HTTP_USER_AGENT'] ?? 'unknown')), 0, 32);
        if (!isset($_SESSION['_user_agent_hash'])) {
            $_SESSION['_user_agent_hash'] = $agent;
        } elseif (!hash_equals((string) $_SESSION['_user_agent_hash'], $agent)) {
            self::invalidate();
            return;
        }

        $lastRegenerated = (int) ($_SESSION['_last_regenerated_at'] ?? 0);
        if ($lastRegenerated <= 0) {
            $_SESSION['_last_regenerated_at'] = $now;
        } elseif (($now - $lastRegenerated) >= self::REGENERATE_EVERY_SECONDS) {
            session_regenerate_id(true);
            $_SESSION['_last_regenerated_at'] = $now;
        }

        $_SESSION['_last_activity'] = $now;
    }

    public static function all(): array
    {
        self::start();
        return $_SESSION ?? [];
    }

    public static function get(string $key, mixed $default = null): mixed
    {
        self::start();
        return $_SESSION[$key] ?? $default;
    }

    public static function put(string $key, mixed $value): void
    {
        self::start();
        $_SESSION[$key] = $value;
    }

    public static function pull(string $key, mixed $default = null): mixed
    {
        self::start();
        $value = $_SESSION[$key] ?? $default;
        unset($_SESSION[$key]);
        return $value;
    }

    public static function forget(string $key): void
    {
        self::start();
        unset($_SESSION[$key]);
    }

    public static function has(string $key): bool
    {
        self::start();
        return array_key_exists($key, $_SESSION ?? []);
    }

    public static function regenerate(bool $deleteOldSession = true): void
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        session_regenerate_id($deleteOldSession);
        $_SESSION['_last_regenerated_at'] = time();
    }

    public static function flush(): void
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        $_SESSION = [];
    }

    public static function invalidate(): void
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        $_SESSION = [];

        if (ini_get('session.use_cookies')) {
            $params = session_get_cookie_params();
            setcookie(
                session_name(),
                '',
                time() - 42000,
                $params['path'] ?? '/',
                $params['domain'] ?? '',
                (bool) ($params['secure'] ?? false),
                (bool) ($params['httponly'] ?? true)
            );
        }

        session_destroy();

        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        $_SESSION = [];
        $_SESSION['_last_regenerated_at'] = time();
        $_SESSION['_last_activity'] = time();
    }
}
