<?php
namespace App\Core;

use App\Models\User;

final class Auth {
    public static function attempt(string $email, string $password): bool {
        $email = mb_strtolower(trim($email));
        $ip = (string)($_SERVER['REMOTE_ADDR'] ?? 'unknown');
        if (self::isRateLimited($email, $ip)) {
            \App\Services\SecurityEventMonitor::record('auth.rate_limited', 'critical', ['email_hash' => hash('sha256', $email)]);
            User::logConnection(null, $email, false, 'Trop de tentatives');
            return false;
        }
        $user = User::findByEmail($email);
        $valid = $user
            && $user['statut'] === 'actif'
            && (bool)$user['actif']
            && password_verify($password, (string)$user['mot_de_passe']);
        if (!$valid) {
            \App\Services\SecurityEventMonitor::record('auth.failed', 'warning', ['email_hash' => hash('sha256', $email)]);
            self::recordAttempt($email, $ip, false);
            User::logConnection($user ? (int)$user['id'] : null, $email, false, 'Identifiants invalides');
            usleep(random_int(180000, 420000));
            return false;
        }
        self::clearAuthenticationState();
        session_regenerate_id(true);
        $_SESSION['pending_mfa_user_id'] = (int)$user['id'];
        $_SESSION['pending_mfa_email'] = $email;
        $_SESSION['pending_mfa_started_at'] = time();
        $_SESSION['pending_mfa_mode'] = \App\Security\MfaService::isEnabled((int)$user['id'])
            ? 'challenge'
            : (\App\Security\MfaService::requiresEnrollment((int)$user['id']) ? 'enroll' : 'none');
        self::recordAttempt($email, $ip, true);
        \App\Services\SecurityEventMonitor::record('auth.password_verified', 'info');
        if ($_SESSION['pending_mfa_mode'] === 'none') {
            self::completePendingMfa();
        }
        return true;
    }

    public static function pendingMfaUserId(): ?int {
        $started=(int)($_SESSION['pending_mfa_started_at'] ?? 0);
        if ($started<=0 || time()-$started>600) {
            unset($_SESSION['pending_mfa_user_id'],$_SESSION['pending_mfa_email'],$_SESSION['pending_mfa_started_at'],$_SESSION['pending_mfa_mode'],$_SESSION['pending_return_to']);
            return null;
        }
        return !empty($_SESSION['pending_mfa_user_id']) ? (int)$_SESSION['pending_mfa_user_id'] : null;
    }

    public static function pendingMfaMode(): string { return (string)($_SESSION['pending_mfa_mode'] ?? ''); }
    public static function setPendingReturnTo(string $path): void { $_SESSION['pending_return_to']=self::safeReturnTo($path); }
    public static function consumePendingReturnTo(): string { $path=self::safeReturnTo((string)($_SESSION['pending_return_to'] ?? '/public/')); unset($_SESSION['pending_return_to']); return $path; }

    public static function completePendingMfa(): void {
        $userId=self::pendingMfaUserId();
        if (!$userId) throw new \RuntimeException('Défi MFA expiré.');
        $user=User::find($userId);
        if (!$user || $user['statut']!=='actif' || !(bool)$user['actif']) throw new \RuntimeException('Compte indisponible.');
        $email=(string)($_SESSION['pending_mfa_email'] ?? $user['email']);
        unset($_SESSION['pending_mfa_user_id'],$_SESSION['pending_mfa_email'],$_SESSION['pending_mfa_started_at'],$_SESSION['pending_mfa_mode'],$_SESSION['mfa_enrollment_secret'],$_SESSION['mfa_enrollment_uri']);
        session_regenerate_id(true);
        $_SESSION['user_id']=(int)$user['id'];
        $_SESSION['last_activity']=time();
        $_SESSION['authenticated_at']=time();
        $_SESSION['session_started_at']=time();
        $_SESSION['last_regenerated_at']=time();
        $_SESSION['security_version']=(int)($user['security_version'] ?? 1);
        $_SESSION['organisation_id']=(int)($user['organisation_id'] ?? 0);
        $_SESSION['session_token']=bin2hex(random_bytes(32));
        $_SESSION['mfa_verified_at']=time();
        self::registerSession((int)$user['id']);
        \App\Security\ZeroTrustEngine::initializeSession((int)$user['id']);
        \App\Services\SecurityEventMonitor::record('auth.succeeded', 'info', ['mfa'=>\App\Security\MfaService::isEnabled((int)$user['id'])]);
        User::updateLastLogin((int)$user['id']);
        User::logConnection((int)$user['id'],$email,true,null);
    }

    public static function check(): bool {
        if (empty($_SESSION['user_id'])) return false;
        $timeout = max(300, (int)($_ENV['SESSION_TIMEOUT'] ?? 1800));
        $absoluteTimeout = max($timeout, (int)($_ENV['SESSION_ABSOLUTE_TIMEOUT'] ?? 43200));
        if (!empty($_SESSION['last_activity']) && time() - (int)$_SESSION['last_activity'] > $timeout) {
            self::logout();
            return false;
        }
        if (empty($_SESSION['session_started_at']) || time() - (int)$_SESSION['session_started_at'] > $absoluteTimeout) {
            self::logout();
            return false;
        }
        $user = User::find((int)$_SESSION['user_id']);
        if (!$user || $user['statut'] !== 'actif' || !(bool)$user['actif']) {
            self::logout();
            return false;
        }
        if ((int)($user['security_version'] ?? 1) !== (int)($_SESSION['security_version'] ?? 0)
            || (int)($user['organisation_id'] ?? 0) <= 0
            || (int)($user['organisation_id'] ?? 0) !== (int)($_SESSION['organisation_id'] ?? 0)) {
            self::logout();
            return false;
        }
        if (!self::serverSessionIsActive((int)$user['id'])) {
            self::logout();
            return false;
        }
        self::rotateSessionIdentifierIfNeeded();
        $_SESSION['last_activity'] = time();
        self::touchSession();
        self::cleanupExpiredSessions();
        return true;
    }

    public static function id(): ?int { return self::check() ? (int)$_SESSION['user_id'] : null; }
    public static function user(): ?array { return self::id() ? User::find(self::id()) : null; }

    public static function requireLogin(): void {
        if (!self::check()) {
            $returnTo = self::safeReturnTo((string)($_SERVER['REQUEST_URI'] ?? '/public/'));
            header('Location: /public/login?return_to=' . rawurlencode($returnTo));
            exit;
        }
    }

    public static function requireRecentAuthentication(int $seconds = 900): void {
        self::requireLogin();
        if (time() - (int)($_SESSION['authenticated_at'] ?? 0) > $seconds) {
            $returnTo = self::safeReturnTo((string)($_SERVER['REQUEST_URI'] ?? '/public/'));
            self::logout();
            header('Location: /public/login?reauth=1&return_to=' . rawurlencode($returnTo));
            exit;
        }
    }

    public static function safeReturnTo(string $value): string {
        $value = trim($value);
        if ($value === '' || !str_starts_with($value, '/public/')) {
            return '/public/';
        }
        if (str_starts_with($value, '//') || str_contains($value, "\r") || str_contains($value, "\n")) {
            return '/public/';
        }
        return $value;
    }

    public static function logout(): void {
        \App\Services\SecurityEventMonitor::record('auth.logout', 'info');
        self::revokeCurrentSession();
        TenantContext::reset();
        $_SESSION = [];
        if (session_status() === PHP_SESSION_ACTIVE) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', [
                'expires' => time() - 42000,
                'path' => $params['path'] ?: '/',
                'domain' => $params['domain'] ?? '',
                'secure' => (bool)($params['secure'] ?? false),
                'httponly' => true,
                'samesite' => $params['samesite'] ?? 'Lax',
            ]);
            session_destroy();
        }
    }

    private static function isRateLimited(string $email, string $ip): bool {
        try {
            $stmt = Database::connection()->prepare(
                "SELECT COUNT(*)
                 FROM cda_login_attempts
                 WHERE attempted_at >= DATE_SUB(NOW(), INTERVAL 15 MINUTE)
                   AND success = 0
                   AND (email_hash = :email_hash OR ip_hash = :ip_hash)"
            );
            $stmt->execute([
                'email_hash' => hash('sha256', $email),
                'ip_hash' => hash('sha256', $ip),
            ]);
            return (int)$stmt->fetchColumn() >= 8;
        } catch (\Throwable) {
            return false;
        }
    }

    private static function recordAttempt(string $email, string $ip, bool $success): void {
        try {
            Database::connection()->prepare(
                "INSERT INTO cda_login_attempts(email_hash, ip_hash, success, attempted_at)
                 VALUES(:email_hash, :ip_hash, :success, NOW())"
            )->execute([
                'email_hash' => hash('sha256', $email),
                'ip_hash' => hash('sha256', $ip),
                'success' => $success ? 1 : 0,
            ]);
        } catch (\Throwable) {
        }
    }

    private static function registerSession(int $userId): void {
        try {
            Database::connection()->prepare(
                "INSERT INTO cda_user_sessions
                 (session_token_hash, utilisateur_id, security_version, ip_hash, user_agent_hash, last_seen_at, expires_at, created_at)
                 VALUES(:token, :user_id, :version, :ip, :agent, NOW(), DATE_ADD(NOW(), INTERVAL 12 HOUR), NOW())"
            )->execute([
                'token' => hash('sha256', (string)$_SESSION['session_token']),
                'user_id' => $userId,
                'version' => (int)$_SESSION['security_version'],
                'ip' => hash('sha256', (string)($_SERVER['REMOTE_ADDR'] ?? 'unknown')),
                'agent' => hash('sha256', (string)($_SERVER['HTTP_USER_AGENT'] ?? 'unknown')),
            ]);
        } catch (\Throwable) {
        }
    }

    private static function serverSessionIsActive(int $userId): bool {
        if (empty($_SESSION['session_token'])) return false;
        try {
            $stmt = Database::connection()->prepare(
                "SELECT 1 FROM cda_user_sessions
                 WHERE session_token_hash=:token AND utilisateur_id=:user_id
                   AND revoked_at IS NULL AND expires_at>NOW()
                   AND user_agent_hash=:agent LIMIT 1"
            );
            $stmt->execute([
                'token' => hash('sha256', (string)$_SESSION['session_token']),
                'user_id' => $userId,
                'agent' => hash('sha256', (string)($_SERVER['HTTP_USER_AGENT'] ?? 'unknown')),
            ]);
            return (bool)$stmt->fetchColumn();
        } catch (\Throwable) {
            return false;
        }
    }

    private static function touchSession(): void {
        try {
            Database::connection()->prepare(
                "UPDATE cda_user_sessions SET last_seen_at=NOW()
                 WHERE session_token_hash=:token AND revoked_at IS NULL"
            )->execute(['token' => hash('sha256', (string)($_SESSION['session_token'] ?? ''))]);
        } catch (\Throwable) {
        }
    }

    public static function revokeAllForUser(int $userId): void {
        if ($userId <= 0) return;
        Database::connection()->prepare(
            "UPDATE cda_user_sessions SET revoked_at=NOW() WHERE utilisateur_id=:user_id AND revoked_at IS NULL"
        )->execute(['user_id' => $userId]);
        Database::connection()->prepare(
            "UPDATE utilisateurs SET security_version=security_version+1 WHERE id=:user_id"
        )->execute(['user_id' => $userId]);
    }

    private static function rotateSessionIdentifierIfNeeded(): void {
        $interval = max(300, (int)($_ENV['SESSION_REGENERATE_INTERVAL'] ?? 900));
        if (time() - (int)($_SESSION['last_regenerated_at'] ?? 0) < $interval) return;
        session_regenerate_id(true);
        $_SESSION['last_regenerated_at'] = time();
    }

    private static function clearAuthenticationState(): void {
        foreach (['user_id','last_activity','authenticated_at','session_started_at','last_regenerated_at','security_version','organisation_id','session_token'] as $key) {
            unset($_SESSION[$key]);
        }
    }

    private static function cleanupExpiredSessions(): void {
        if (random_int(1, 100) !== 1) return;
        try {
            Database::connection()->exec(
                "DELETE FROM cda_user_sessions WHERE expires_at < DATE_SUB(NOW(), INTERVAL 7 DAY) OR revoked_at < DATE_SUB(NOW(), INTERVAL 30 DAY)"
            );
            Database::connection()->exec(
                "DELETE FROM cda_login_attempts WHERE attempted_at < DATE_SUB(NOW(), INTERVAL 30 DAY)"
            );
        } catch (\Throwable) {
        }
    }

    private static function revokeCurrentSession(): void {
        if (empty($_SESSION['session_token'])) return;
        try {
            Database::connection()->prepare(
                "UPDATE cda_user_sessions SET revoked_at=NOW()
                 WHERE session_token_hash=:token AND revoked_at IS NULL"
            )->execute(['token' => hash('sha256', (string)$_SESSION['session_token'])]);
        } catch (\Throwable) {
        }
    }
}
