<?php
declare(strict_types=1);

namespace App\Web;

use App\Security\CsrfService;
use App\Users\TrustService;
use PDO;
use PDOException;

final class Session
{
    public static function start(): void
    {
        (new CsrfService())->token();
    }

    /** @return array<string, mixed>|null */
    public static function user(): ?array
    {
        self::start();
        $user = $_SESSION['auth_user'] ?? null;

        return is_array($user) && $user !== [] ? $user : null;
    }

    /** Reload current authorization state and reject revoked browser sessions. */
    public static function refresh(PDO $pdo): ?array
    {
        $sessionUser = self::user();
        $userId = filter_var($sessionUser['id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($userId === false) {
            self::forgetUser();
            return null;
        }

        try {
            $statement = $pdo->prepare(
                'SELECT id,name,username,email,bio,avatar_url,role,account_type,trust_level,login_disabled,created_by_admin_id,
                        status,points,preferred_locale,email_verified_at,auth_version,created_at,updated_at
                 FROM users WHERE id=:id LIMIT 1'
            );
            $statement->execute(['id' => $userId]);
        } catch (PDOException $exception) {
            $driverCode = (int) ($exception->errorInfo[1] ?? 0);
            if ($driverCode !== 1054 && (string) $exception->getCode() !== '42S22') throw $exception;

            try {
                // Migration 036 may not exist yet while auth_version from migration 032 does.
                $statement = $pdo->prepare(
                    "SELECT id,name,username,email,bio,avatar_url,role,
                            'member' AS account_type,0 AS trust_level,0 AS login_disabled,NULL AS created_by_admin_id,
                            status,points,preferred_locale,email_verified_at,auth_version,created_at,updated_at
                     FROM users WHERE id=:id LIMIT 1"
                );
                $statement->execute(['id' => $userId]);
            } catch (PDOException $legacyException) {
                $legacyDriverCode = (int) ($legacyException->errorInfo[1] ?? 0);
                if ($legacyDriverCode !== 1054 && (string) $legacyException->getCode() !== '42S22') throw $legacyException;
                // Very old databases predating migration 032 use version 1 once so an
                // authenticated administrator can reach the upgrade action safely.
                $statement = $pdo->prepare(
                    "SELECT id,name,username,email,bio,avatar_url,role,
                            'member' AS account_type,0 AS trust_level,0 AS login_disabled,NULL AS created_by_admin_id,
                            status,points,preferred_locale,email_verified_at,1 AS auth_version,created_at,updated_at
                     FROM users WHERE id=:id LIMIT 1"
                );
                $statement->execute(['id' => $userId]);
            }
        }
        $current = $statement->fetch(PDO::FETCH_ASSOC);
        if (!is_array($current) || !TrustService::canLogin($current)) {
            self::forgetUser();
            return null;
        }

        $sessionVersion = (int) ($sessionUser['auth_version'] ?? 0);
        $currentVersion = max(1, (int) ($current['auth_version'] ?? 1));
        if ($sessionVersion > 0 && $sessionVersion !== $currentVersion) {
            self::forgetUser();
            return null;
        }

        $_SESSION['auth_user'] = $current;
        return $current;
    }

    /** @param array<string, mixed> $user */
    public static function login(array $user, bool $remember = false): void
    {
        self::start();
        session_regenerate_id(true);
        $_SESSION['auth_user'] = $user;
        if ($remember) {
            $lifetime = 30 * 86400; // 30 days
            $params = session_get_cookie_params();
            $path = CsrfService::cookiePath();
            $secure = CsrfService::isHttps();
            setcookie(session_name(), session_id(), [
                'expires' => time() + $lifetime,
                'path' => $path,
                'domain' => (string) ($params['domain'] ?? ''),
                'secure' => $secure,
                'httponly' => true,
                'samesite' => 'Lax',
            ]);
        }
        (new CsrfService())->rotate();
    }

    public static function setPreferredLocale(string $locale): void
    {
        self::start();
        $locale = strtolower(trim($locale));

        if ($locale === '' || preg_match('/^[a-z]{2,3}(?:-[a-z0-9]{2,8})*$/', $locale) !== 1) {
            return;
        }

        $user = $_SESSION['auth_user'] ?? null;
        if (!is_array($user) || $user === []) {
            return;
        }

        $user['preferred_locale'] = $locale;
        $_SESSION['auth_user'] = $user;
    }

    /** Replace session user fields after a profile/account update without rotating the session id. */
    public static function replaceUser(array $user): void
    {
        self::start();
        if ($user !== []) $_SESSION['auth_user'] = $user;
    }

    public static function forgetUser(): void
    {
        self::start();
        unset($_SESSION['auth_user']);
        (new CsrfService())->rotate();
    }

    public static function logout(): void
    {
        self::start();
        unset($_SESSION['auth_user']);
        $_SESSION = [];
        $params = session_get_cookie_params();
        setcookie(session_name(), '', [
            'expires' => time() - 3600,
            'path' => CsrfService::cookiePath(),
            'domain' => (string) ($params['domain'] ?? ''),
            'secure' => CsrfService::isHttps(),
            'httponly' => true,
            'samesite' => 'Lax',
        ]);
        session_destroy();
    }
}
