<?php
/**
 * UserManager - Gestione utenti e autenticazione tramite cookie
 * PHP 8+ con MariaDB
 */

class UserManager {
    private PDO $db;
    private string $cookieName = 'auth_token';
    private int $cookieLifetime = 2592000; // 30 giorni in secondi
    private int $maxLoginAttempts = 5;
    private int $lockoutTime = 900; // 15 minuti in secondi
    
    public function __construct(PDO $db) {
        $this->db = $db;
        $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }
    
    /**
     * Registra un nuovo utente
     */
    public function register(string $username, string $email, string $password, string $fullName = null): bool {
        try {
            $passwordHash = password_hash($password, PASSWORD_ARGON2ID);
            
            $stmt = $this->db->prepare("
                INSERT INTO users (username, email, password_hash, full_name) 
                VALUES (:username, :email, :password_hash, :full_name)
            ");
            
            $result = $stmt->execute([
                'username' => $username,
                'email' => $email,
                'password_hash' => $passwordHash,
                'full_name' => $fullName
            ]);
            
            if ($result) {
                $userId = $this->db->lastInsertId();
                $this->logActivity($userId, $username, 'REGISTER', "Nuovo utente registrato: $username");
            }
            
            return $result;
        } catch (PDOException $e) {
            error_log("Errore registrazione: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Login utente con username/email e password
     */
    public function login(string $identifier, string $password, bool $rememberMe = true): array {
        // Verifica blocco per troppi tentativi
        if ($this->isAccountLocked($identifier)) {
            return [
                'success' => false,
                'message' => 'Account temporaneamente bloccato per troppi tentativi falliti'
            ];
        }
        
        // Cerca utente per username o email
        $stmt = $this->db->prepare("
            SELECT id, username, email, password_hash, is_active, full_name 
            FROM users 
            WHERE (username = :identifier OR email = :identifier) AND is_active = 1
        ");
        $stmt->execute(['identifier' => $identifier]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if (!$user || !password_verify($password, $user['password_hash'])) {
            $this->logFailedAttempt($identifier);
            $this->logActivity(null, $identifier, 'LOGIN_FAILED', "Tentativo di login fallito");
            return [
                'success' => false,
                'message' => 'Credenziali non valide'
            ];
        }
        
        // Login riuscito - pulisce tentativi falliti
        $this->clearFailedAttempts($identifier);
        
        // Aggiorna last_login
        $stmt = $this->db->prepare("UPDATE users SET last_login = NOW() WHERE id = :id");
        $stmt->execute(['id' => $user['id']]);
        
        // Crea token di autenticazione se richiesto
        if ($rememberMe) {
            $this->createAuthToken($user['id']);
        }
        
        // Salva in sessione dati base
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['username'] = $user['username'];
        $_SESSION['full_name'] = $user['full_name'];
        
        $this->logActivity($user['id'], $user['username'], 'LOGIN', "Login effettuato");
        
        return [
            'success' => true,
            'user' => [
                'id' => $user['id'],
                'username' => $user['username'],
                'email' => $user['email'],
                'full_name' => $user['full_name']
            ]
        ];
    }
    
    /**
     * Re-autenticazione con solo password (quando cookie è scaduto ma username è memorizzato)
     */
    public function reAuthenticate(string $username, string $password): array {
        return $this->login($username, $password, true);
    }
    
    /**
     * Verifica validità sessione/cookie
     */
    public function checkAuth(): ?array {
        // Prima controlla se esiste sessione attiva
        if (isset($_SESSION['user_id'])) {
            return $this->getUserById($_SESSION['user_id']);
        }
        
        // Altrimenti verifica cookie
        if (isset($_COOKIE[$this->cookieName])) {
            return $this->validateAuthToken($_COOKIE[$this->cookieName]);
        }
        
        return null;
    }
    
    /**
     * Crea token di autenticazione e imposta cookie
     */
    private function createAuthToken(int $userId): bool {
        try {
            // Genera selector e token casuali
            $selector = bin2hex(random_bytes(16));
            $token = bin2hex(random_bytes(32));
            $tokenHash = hash('sha256', $token);
            
            $expiresAt = date('Y-m-d H:i:s', time() + $this->cookieLifetime);
            
            // Salva nel database
            $stmt = $this->db->prepare("
                INSERT INTO auth_tokens (user_id, token, selector, expires_at, ip_address, user_agent)
                VALUES (:user_id, :token, :selector, :expires_at, :ip, :ua)
            ");
            
            $stmt->execute([
                'user_id' => $userId,
                'token' => $tokenHash,
                'selector' => $selector,
                'expires_at' => $expiresAt,
                'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
                'ua' => $_SERVER['HTTP_USER_AGENT'] ?? null
            ]);
            
            // Imposta cookie (selector:token)
            $cookieValue = $selector . ':' . $token;
            setcookie(
                $this->cookieName,
                $cookieValue,
                time() + $this->cookieLifetime,
                '/',
                '',
                true, // Secure (solo HTTPS in produzione)
                true  // HttpOnly
            );
            
            return true;
        } catch (Exception $e) {
            error_log("Errore creazione token: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Valida token di autenticazione dal cookie
     */
    private function validateAuthToken(string $cookieValue): ?array {
        try {
            $parts = explode(':', $cookieValue);
            if (count($parts) !== 2) {
                return null;
            }
            
            [$selector, $token] = $parts;
            $tokenHash = hash('sha256', $token);
            
            // Cerca token nel database
            $stmt = $this->db->prepare("
                SELECT at.*, u.id, u.username, u.email, u.full_name, u.is_active
                FROM auth_tokens at
                JOIN users u ON at.user_id = u.id
                WHERE at.selector = :selector AND at.expires_at > NOW() AND u.is_active = 1
            ");
            $stmt->execute(['selector' => $selector]);
            $result = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if (!$result || !hash_equals($result['token'], $tokenHash)) {
                $this->deleteAuthToken($selector);
                return null;
            }
            
            // Token valido - rigenera sessione
            $_SESSION['user_id'] = $result['id'];
            $_SESSION['username'] = $result['username'];
            $_SESSION['full_name'] = $result['full_name'];
            
            // Aggiorna last_login
            $stmt = $this->db->prepare("UPDATE users SET last_login = NOW() WHERE id = :id");
            $stmt->execute(['id' => $result['id']]);
            
            $this->logActivity($result['id'], $result['username'], 'AUTO_LOGIN', "Login automatico via cookie");
            
            return [
                'id' => $result['id'],
                'username' => $result['username'],
                'email' => $result['email'],
                'full_name' => $result['full_name']
            ];
        } catch (Exception $e) {
            error_log("Errore validazione token: " . $e->getMessage());
            return null;
        }
    }
    
    /**
     * Logout - elimina sessione e cookie
     */
    public function logout(): void {
        $userId = $_SESSION['user_id'] ?? null;
        $username = $_SESSION['username'] ?? 'unknown';
        
        // Elimina token dal database se presente
        if (isset($_COOKIE[$this->cookieName])) {
            $parts = explode(':', $_COOKIE[$this->cookieName]);
            if (count($parts) === 2) {
                $this->deleteAuthToken($parts[0]);
            }
        }
        
        // Elimina cookie
        setcookie($this->cookieName, '', time() - 3600, '/', '', true, true);
        
        // Distrugge sessione
        session_unset();
        session_destroy();
        
        if ($userId) {
            $this->logActivity($userId, $username, 'LOGOUT', "Logout effettuato");
        }
    }
    
    /**
     * Elimina token dal database
     */
    private function deleteAuthToken(string $selector): void {
        try {
            $stmt = $this->db->prepare("DELETE FROM auth_tokens WHERE selector = :selector");
            $stmt->execute(['selector' => $selector]);
        } catch (Exception $e) {
            error_log("Errore eliminazione token: " . $e->getMessage());
        }
    }
    
    /**
     * Ottiene utente per ID
     */
    public function getUserById(int $userId): ?array {
        $stmt = $this->db->prepare("
            SELECT id, username, email, full_name, is_active, created_at, last_login
            FROM users WHERE id = :id AND is_active = 1
        ");
        $stmt->execute(['id' => $userId]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);
        return $user ?: null;
    }
    
    /**
     * Verifica se account è bloccato per troppi tentativi
     */
    private function isAccountLocked(string $identifier): bool {
        $stmt = $this->db->prepare("
            SELECT COUNT(*) as attempts
            FROM failed_login_attempts
            WHERE username = :identifier 
            AND ip_address = :ip
            AND attempted_at > DATE_SUB(NOW(), INTERVAL :lockout SECOND)
        ");
        $stmt->execute([
            'identifier' => $identifier,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
            'lockout' => $this->lockoutTime
        ]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return $result['attempts'] >= $this->maxLoginAttempts;
    }
    
    /**
     * Registra tentativo di login fallito
     */
    private function logFailedAttempt(string $identifier): void {
        try {
            $stmt = $this->db->prepare("
                INSERT INTO failed_login_attempts (username, ip_address)
                VALUES (:username, :ip)
            ");
            $stmt->execute([
                'username' => $identifier,
                'ip' => $_SERVER['REMOTE_ADDR'] ?? ''
            ]);
        } catch (Exception $e) {
            error_log("Errore log tentativo fallito: " . $e->getMessage());
        }
    }
    
    /**
     * Pulisce tentativi falliti dopo login riuscito
     */
    private function clearFailedAttempts(string $identifier): void {
        try {
            $stmt = $this->db->prepare("
                DELETE FROM failed_login_attempts 
                WHERE username = :username AND ip_address = :ip
            ");
            $stmt->execute([
                'username' => $identifier,
                'ip' => $_SERVER['REMOTE_ADDR'] ?? ''
            ]);
        } catch (Exception $e) {
            error_log("Errore pulizia tentativi: " . $e->getMessage());
        }
    }
    
    /**
     * Log attività utente
     */
    public function logActivity(?int $userId, string $username, string $action, string $description = null): void {
        try {
            $stmt = $this->db->prepare("
                INSERT INTO user_activity_log (user_id, username, action, description, ip_address, user_agent)
                VALUES (:user_id, :username, :action, :description, :ip, :ua)
            ");
            $stmt->execute([
                'user_id' => $userId,
                'username' => $username,
                'action' => $action,
                'description' => $description,
                'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
                'ua' => $_SERVER['HTTP_USER_AGENT'] ?? null
            ]);
        } catch (Exception $e) {
            error_log("Errore log attività: " . $e->getMessage());
        }
    }
    
    /**
     * Pulizia token scaduti (da eseguire periodicamente)
     */
    public function cleanExpiredTokens(): int {
        try {
            $stmt = $this->db->prepare("DELETE FROM auth_tokens WHERE expires_at < NOW()");
            $stmt->execute();
            return $stmt->rowCount();
        } catch (Exception $e) {
            error_log("Errore pulizia token: " . $e->getMessage());
            return 0;
        }
    }
    
    /**
     * Ottiene username da cookie (per form di re-autenticazione)
     */
    public function getRememberedUsername(): ?string {
        if (!isset($_COOKIE[$this->cookieName])) {
            return null;
        }
        
        $parts = explode(':', $_COOKIE[$this->cookieName]);
        if (count($parts) !== 2) {
            return null;
        }
        
        try {
            $stmt = $this->db->prepare("
                SELECT u.username
                FROM auth_tokens at
                JOIN users u ON at.user_id = u.id
                WHERE at.selector = :selector
            ");
            $stmt->execute(['selector' => $parts[0]]);
            $result = $stmt->fetch(PDO::FETCH_ASSOC);
            return $result['username'] ?? null;
        } catch (Exception $e) {
            return null;
        }
    }
}