<?php
namespace App\Core;

class Auth
{
    private static array $permissionCache = [];

    private static function startSession(array $user): void
    {
        session_regenerate_id(true);
        Security::rotateCsrf();
        $now = time();
        $_SESSION['user_id'] = (int) $user['id'];
        $_SESSION['role'] = (string) $user['role'];
        $_SESSION['auth_version'] = (int) ($user['auth_version'] ?? 1);
        $_SESSION['password_update_required'] = (int) ($user['must_change_password'] ?? 0) === 1;
        $_SESSION['auth_time'] = $now;
        $_SESSION['last_activity_at'] = $now;
        $_SESSION['last_regenerated_at'] = $now;
        $_SESSION['last_status_check_at'] = 0;
    }

    public static function attempt(string $email, string $password): bool
    {
        $email = strtolower(trim($email));
        $rateKey = 'login:' . sha1($email . '|' . Security::clientIp());
        if (!Security::rateLimit($rateKey, 8, 900)) {
            Security::logActivity(null, 'login_rate_limited', ['email' => $email]);
            return false;
        }
        if (!Database::isConnected()) {
            error_log('Login refused because the database is unavailable.');
            return false;
        }

        $user = Database::safeFetchOne('SELECT * FROM users WHERE LOWER(email)=? AND status<>"deleted" LIMIT 1', [$email]);
        if (!$user || !password_verify($password, (string) $user['password_hash'])) {
            Security::logActivity(null, 'login_failed', ['email' => $email]);
            return false;
        }
        if (!in_array((string) $user['status'], ['active', 'approved'], true)) {
            Security::logActivity((int) $user['id'], 'login_blocked', ['status' => $user['status']]);
            return false;
        }

        if (password_needs_rehash((string) $user['password_hash'], PASSWORD_DEFAULT)) {
            Database::safeExecute('UPDATE users SET password_hash=?,updated_at=NOW() WHERE id=?', [password_hash($password, PASSWORD_DEFAULT), $user['id']]);
        }
        self::startSession($user);
        Security::clearRateLimit($rateKey);
        Database::safeExecute('UPDATE users SET last_login_at=NOW() WHERE id=?', [$user['id']]);
        Database::safeExecute(
            'INSERT INTO login_history (user_id,ip_address,user_agent,created_at) VALUES (?,?,?,NOW())',
            [$user['id'], Security::clientIp(), Security::userAgent()]
        );
        Security::logActivity((int) $user['id'], 'login_success');
        return true;
    }

    public static function loginUser(array $user): void
    {
        self::startSession($user);
    }

    public static function check(): bool
    {
        return !empty($_SESSION['user_id']);
    }

    public static function id(): ?int
    {
        return isset($_SESSION['user_id']) ? (int) $_SESSION['user_id'] : null;
    }

    public static function role(): ?string
    {
        return isset($_SESSION['role']) ? (string) $_SESSION['role'] : null;
    }

    public static function requiresPasswordChange(): bool
    {
        return self::check() && !empty($_SESSION['password_update_required']);
    }

    public static function clearPasswordChangeRequirement(int $authVersion): void
    {
        $_SESSION['password_update_required'] = false;
        $_SESSION['auth_version'] = $authVersion;
        $_SESSION['auth_time'] = time();
        $_SESSION['last_activity_at'] = time();
        session_regenerate_id(true);
        $_SESSION['last_regenerated_at'] = time();
        Security::rotateCsrf();
    }

    public static function validateSession(): void
    {
        if (!self::check()) {
            return;
        }
        $now = time();
        $authTime = (int) ($_SESSION['auth_time'] ?? $now);
        $lastActivity = (int) ($_SESSION['last_activity_at'] ?? $authTime);
        if (($now - $lastActivity) > SESSION_IDLE_TIMEOUT || ($now - $authTime) > SESSION_ABSOLUTE_TIMEOUT) {
            Security::logActivity(self::id(), 'session_expired');
            self::logout(false);
            return;
        }
        $_SESSION['last_activity_at'] = $now;

        $lastRegenerated = (int) ($_SESSION['last_regenerated_at'] ?? 0);
        if (($now - $lastRegenerated) >= SESSION_REGENERATE_INTERVAL) {
            session_regenerate_id(true);
            $_SESSION['last_regenerated_at'] = $now;
        }

        $lastCheck = (int) ($_SESSION['last_status_check_at'] ?? 0);
        if (($now - $lastCheck) < SESSION_STATUS_RECHECK_INTERVAL) {
            return;
        }
        $_SESSION['last_status_check_at'] = $now;
        if (!Database::isConnected()) {
            self::logout(false);
            return;
        }
        $columns = Database::columns('users');
        $authVersionSelect = in_array('auth_version', $columns, true) ? 'auth_version' : '1 AS auth_version';
        $mustChangeSelect = in_array('must_change_password', $columns, true) ? 'must_change_password' : '0 AS must_change_password';
        $user = Database::safeFetchOne(
            "SELECT id,role,status,{$authVersionSelect},{$mustChangeSelect} FROM users WHERE id=? AND status<>\"deleted\" LIMIT 1",
            [self::id()]
        );
        if (!$user
            || !in_array((string) $user['status'], ['active', 'approved'], true)
            || (int) ($user['auth_version'] ?? 1) !== (int) ($_SESSION['auth_version'] ?? 1)) {
            self::logout(false);
            return;
        }
        $_SESSION['role'] = (string) $user['role'];
        $_SESSION['password_update_required'] = (int) ($user['must_change_password'] ?? 0) === 1;
    }

    public static function user(): ?array
    {
        if (!self::check() || !Database::isConnected()) {
            return null;
        }
        $user = Database::safeFetchOne(
            'SELECT id,role,name,email,phone,avatar,status,created_at FROM users WHERE id=? AND status<>"deleted" LIMIT 1',
            [self::id()]
        );
        if (!$user || !in_array((string) $user['status'], ['active', 'approved'], true)) {
            self::logout(false);
            return null;
        }
        return $user;
    }

    public static function requireLogin(string $login = 'public/login.php'): void
    {
        if (!self::check()) {
            header('Location: ' . \url($login));
            exit;
        }
    }

    public static function requireRole(array $roles, string $login = 'public/login.php'): void
    {
        self::requireLogin($login);
        $role = (string) self::role();
        $allowed = in_array($role, $roles, true);
        if (!$allowed) {
            self::deny();
        }

        if ($role === 'staff' && self::isAdminRequest()) {
            $moduleKey = self::currentAdminModuleKey();
            if (!self::canAdminModule($moduleKey)) {
                self::deny('You do not have permission to access this administration module.');
            }
        }
    }

    public static function requireAdminModule(string $moduleKey): void
    {
        self::requireRole(['admin', 'staff']);
        if (!self::canAdminModule($moduleKey)) {
            self::deny('You do not have permission to access this administration module.');
        }
    }

    public static function canAdminModule(string $moduleKey): bool
    {
        $role = (string) self::role();
        if ($role === 'admin') {
            return true;
        }
        if ($role !== 'staff' || !Database::isConnected()) {
            return false;
        }
        $moduleKey = self::normalizeModuleKey($moduleKey);
        if ($moduleKey === '') {
            return false;
        }
        if (array_key_exists($moduleKey, self::$permissionCache)) {
            return self::$permissionCache[$moduleKey];
        }
        if (!Database::tableExists('permissions') || !Database::tableExists('role_permissions') || !Database::tableExists('roles')) {
            return self::$permissionCache[$moduleKey] = false;
        }
        $row = Database::safeFetchOne(
            'SELECT 1 allowed
             FROM role_permissions rp
             JOIN roles r ON r.id=rp.role_id
             JOIN permissions p ON p.id=rp.permission_id
             WHERE r.code="staff" AND p.module=? AND p.action="access"
             LIMIT 1',
            [$moduleKey]
        );
        return self::$permissionCache[$moduleKey] = (bool) $row;
    }

    public static function currentAdminModuleKey(): string
    {
        $script = basename((string) ($_SERVER['SCRIPT_NAME'] ?? ''), '.php');
        if ($script === 'index') {
            return 'dashboard';
        }
        return self::normalizeModuleKey($script);
    }

    public static function normalizeModuleKey(string $moduleKey): string
    {
        return trim((string) preg_replace('/[^a-z0-9_-]+/i', '-', strtolower($moduleKey)), '-');
    }

    public static function logout(bool $logActivity = true): void
    {
        if ($logActivity && self::check()) {
            Security::logActivity(self::id(), 'logout');
        }
        $_SESSION = [];
        if (ini_get('session.use_cookies')) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', [
                'expires' => time() - 42000,
                'path' => $params['path'],
                'domain' => $params['domain'],
                'secure' => (bool) $params['secure'],
                'httponly' => (bool) $params['httponly'],
                'samesite' => $params['samesite'] ?? 'Lax',
            ]);
        }
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_destroy();
        }
    }

    private static function isAdminRequest(): bool
    {
        $script = str_replace('\\', '/', (string) ($_SERVER['SCRIPT_NAME'] ?? ''));
        return str_contains($script, '/admin/');
    }

    private static function deny(string $message = 'Access denied.'): never
    {
        http_response_code(403);
        $accept = strtolower((string) ($_SERVER['HTTP_ACCEPT'] ?? ''));
        if (str_contains((string) ($_SERVER['SCRIPT_NAME'] ?? ''), '/api/') || str_contains($accept, 'application/json')) {
            header('Content-Type: application/json; charset=utf-8');
            echo json_encode(['ok' => false, 'message' => $message], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            exit;
        }
        echo '<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Access denied</title></head><body style="font-family:system-ui;padding:40px"><h1>Access denied</h1><p>' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</p></body></html>';
        exit;
    }
}
