<?php
/**
 * Klasa Auth - obsługa autoryzacji i sesji
 */

class Auth
{
    private Database $db;
    private Logger $logger;
    private static ?array $currentUser = null;

    public function __construct()
    {
        $this->db = Database::getInstance();
        $this->logger = new Logger();
    }

    /**
     * Zaloguj użytkownika
     */
    public function login(string $username, string $password, bool $remember = false): array
    {
        // Sprawdź rate limiting
        if (!$this->checkRateLimit($username)) {
            return ['success' => false, 'error' => 'Zbyt wiele prób logowania. Spróbuj za minutę.'];
        }

        // Pobierz użytkownika
        $user = $this->db->fetchOne(
            "SELECT * FROM users WHERE username = :username AND is_active = 1",
            ['username' => $username]
        );

        if (!$user || !password_verify($password, $user['password_hash'])) {
            $this->incrementRateLimit($username);
            $this->logger->log('login_failed', "Nieudana próba logowania: {$username}");
            return ['success' => false, 'error' => 'Nieprawidłowa nazwa użytkownika lub hasło.'];
        }

        // Wyczyść rate limit
        $this->clearRateLimit($username);

        // Utwórz sesję
        $token = $this->createSession($user['id'], $remember);

        // Aktualizuj last_login
        $this->db->execute(
            "UPDATE users SET last_login = datetime('now'), updated_at = datetime('now') WHERE id = :id",
            ['id' => $user['id']]
        );

        // Loguj sukces
        $this->logger->log('login_success', "Użytkownik zalogowany: {$username}", $user['id']);

        // Ustaw cookie
        $this->setSessionCookie($token, $remember);

        return ['success' => true, 'user' => $user];
    }

    /**
     * Wyloguj użytkownika
     */
    public function logout(): void
    {
        $token = $_COOKIE[SESSION_NAME] ?? null;
        
        if ($token) {
            // Pobierz user_id przed usunięciem sesji
            $session = $this->db->fetchOne(
                "SELECT user_id FROM sessions WHERE token = :token",
                ['token' => $token]
            );
            
            // Usuń sesję z bazy
            $this->db->execute(
                "DELETE FROM sessions WHERE token = :token",
                ['token' => $token]
            );
            
            // Loguj
            if ($session) {
                $this->logger->log('logout', 'Użytkownik wylogowany', $session['user_id']);
            }
        }

        // Usuń cookie
        setcookie(SESSION_NAME, '', [
            'expires' => time() - 3600,
            'path' => '/',
            'httponly' => true,
            'samesite' => 'Strict'
        ]);
        
        self::$currentUser = null;
    }

    /**
     * Sprawdź czy użytkownik jest zalogowany
     */
    public function check(): bool
    {
        return $this->user() !== null;
    }

    /**
     * Pobierz zalogowanego użytkownika
     */
    public function user(): ?array
    {
        if (self::$currentUser !== null) {
            return self::$currentUser;
        }

        $token = $_COOKIE[SESSION_NAME] ?? null;
        
        if (!$token) {
            return null;
        }

        $session = $this->db->fetchOne(
            "SELECT s.*, u.*, strftime('%s', s.last_activity) as last_activity_ts FROM sessions s 
             JOIN users u ON s.user_id = u.id 
             WHERE s.token = :token AND s.expires_at > datetime('now') AND u.is_active = 1",
            ['token' => $token]
        );

        if (!$session) {
            // Sesja wygasła lub nieprawidłowa
            $this->logout();
            return null;
        }

        // Pobierz aktualny timestamp z SQLite (żeby mieć spójność)
        $nowResult = $this->db->fetchOne("SELECT strftime('%s', 'now') as now_ts");
        $nowTs = (int)$nowResult['now_ts'];
        $oldActivityTs = $session['last_activity_ts'] ? (int)$session['last_activity_ts'] : null;

        // Aktualizuj last_activity NA POCZĄTKU
        $this->db->execute(
            "UPDATE sessions SET last_activity = datetime('now') WHERE token = :token",
            ['token' => $token]
        );

        // Sprawdź czy użytkownik wraca po >10 minutach nieaktywności z remember_me
        if ($session['remember_me'] && $oldActivityTs) {
            $inactiveSeconds = $nowTs - $oldActivityTs;
            
            // Więcej niż 10 minut (600 sekund) nieaktywności
            if ($inactiveSeconds > 600) {
                $this->logger->log('login_success_r', "Powrót użytkownika (remember me): {$session['username']}", $session['user_id']);
            }
        }

        // Przedłuż sesję jeśli remember_me
        if ($session['remember_me']) {
            $this->db->execute(
                "UPDATE sessions SET expires_at = datetime('now', '+30 days') WHERE token = :token",
                ['token' => $token]
            );
        }

        self::$currentUser = [
            'id' => $session['user_id'],
            'username' => $session['username'],
            'role' => $session['role'],
            'parent_id' => $session['parent_id'] ?? null,
            'device_limit' => $session['device_limit'],
            'playlist_limit' => $session['playlist_limit'] ?? 100,
            'max_users' => $session['max_users'] ?? 0,
            'max_devices' => $session['max_devices'] ?? 0,
            'max_playlists' => $session['max_playlists'] ?? 0,
            'last_login' => $session['last_login'],
            'created_at' => $session['created_at']
        ];

        return self::$currentUser;
    }

    /**
     * Sprawdź czy użytkownik jest administratorem
     */
    public function isAdmin(): bool
    {
        $user = $this->user();
        return $user && $user['role'] === 'admin';
    }

    /**
     * Sprawdź czy użytkownik jest resellerem
     */
    public function isReseller(): bool
    {
        $user = $this->user();
        return $user && $user['role'] === 'reseller';
    }

    /**
     * Sprawdź czy użytkownik jest administratorem lub resellerem
     */
    public function isManager(): bool
    {
        return $this->isAdmin() || $this->isReseller();
    }

    /**
     * Sprawdź czy użytkownik może zarządzać danym userId
     */
    public function canManageUser(int $targetUserId): bool
    {
        $user = $this->user();
        if (!$user) return false;
        
        if ($user['role'] === 'admin') {
            return true;
        }
        
        if ($user['role'] === 'reseller') {
            $userModel = new User();
            $targetUser = $userModel->getById($targetUserId);
            return $targetUser && $targetUser['parent_id'] == $user['id'];
        }
        
        return false;
    }

    /**
     * Pobierz ID użytkowników widocznych dla zalogowanego managera
     */
    public function getVisibleUserIds(): array
    {
        $user = $this->user();
        if (!$user) return [];
        
        $userModel = new User();
        return $userModel->getVisibleUserIds($user);
    }

    /**
     * Zmień hasło użytkownika
     */
    public function changePassword(int $userId, string $currentPassword, string $newPassword): array
    {
        $user = $this->db->fetchOne(
            "SELECT * FROM users WHERE id = :id",
            ['id' => $userId]
        );

        if (!$user) {
            return ['success' => false, 'error' => 'Użytkownik nie istnieje.'];
        }

        if (!password_verify($currentPassword, $user['password_hash'])) {
            $this->logger->log('password_change_failed', 'Nieprawidłowe aktualne hasło', $userId);
            return ['success' => false, 'error' => 'Nieprawidłowe aktualne hasło.'];
        }

        if (strlen($newPassword) < 6) {
            return ['success' => false, 'error' => 'Nowe hasło musi mieć minimum 6 znaków.'];
        }

        $hash = password_hash($newPassword, PASSWORD_BCRYPT, ['cost' => BCRYPT_COST]);
        
        $this->db->execute(
            "UPDATE users SET password_hash = :hash, updated_at = datetime('now') WHERE id = :id",
            ['hash' => $hash, 'id' => $userId]
        );

        $this->logger->log('password_changed', 'Hasło zostało zmienione', $userId);

        return ['success' => true];
    }

    /**
     * Resetuj hasło użytkownika (tylko admin)
     */
    public function resetPassword(int $userId, string $newPassword): array
    {
        if (strlen($newPassword) < 6) {
            return ['success' => false, 'error' => 'Hasło musi mieć minimum 6 znaków.'];
        }

        $hash = password_hash($newPassword, PASSWORD_BCRYPT, ['cost' => BCRYPT_COST]);
        
        $this->db->execute(
            "UPDATE users SET password_hash = :hash, updated_at = datetime('now') WHERE id = :id",
            ['hash' => $hash, 'id' => $userId]
        );

        // Usuń wszystkie sesje użytkownika
        $this->db->execute(
            "DELETE FROM sessions WHERE user_id = :id",
            ['id' => $userId]
        );

        $this->logger->log('password_reset', "Reset hasła dla użytkownika ID: {$userId}", $this->user()['id'] ?? null);

        return ['success' => true];
    }

    /**
     * Utwórz nową sesję
     */
    private function createSession(int $userId, bool $remember = false): string
    {
        // Usuń stare sesje tego użytkownika (max 5 aktywnych)
        $this->db->execute(
            "DELETE FROM sessions WHERE user_id = :id AND id NOT IN (
                SELECT id FROM sessions WHERE user_id = :id2 ORDER BY created_at DESC LIMIT 4
            )",
            ['id' => $userId, 'id2' => $userId]
        );

        $token = bin2hex(random_bytes(32));
        $expiresAt = $remember 
            ? date('Y-m-d H:i:s', time() + SESSION_REMEMBER_LIFETIME)
            : date('Y-m-d H:i:s', time() + SESSION_LIFETIME);

        $this->db->insert(
            "INSERT INTO sessions (user_id, token, ip, user_agent, remember_me, expires_at, last_activity) 
             VALUES (:user_id, :token, :ip, :user_agent, :remember, :expires, datetime('now'))",
            [
                'user_id' => $userId,
                'token' => $token,
                'ip' => $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
                'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
                'remember' => $remember ? 1 : 0,
                'expires' => $expiresAt
            ]
        );

        return $token;
    }

    /**
     * Ustaw cookie sesji
     */
    private function setSessionCookie(string $token, bool $remember = false): void
    {
        $expires = $remember ? time() + SESSION_REMEMBER_LIFETIME : 0;
        
        setcookie(SESSION_NAME, $token, [
            'expires' => $expires,
            'path' => '/',
            'httponly' => true,
            'samesite' => 'Strict',
            'secure' => isset($_SERVER['HTTPS'])
        ]);
    }

    /**
     * Generuj token CSRF
     */
    public function generateCsrfToken(): string
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
        
        if (empty($_SESSION[CSRF_TOKEN_NAME])) {
            $_SESSION[CSRF_TOKEN_NAME] = bin2hex(random_bytes(32));
        }
        
        return $_SESSION[CSRF_TOKEN_NAME];
    }

    /**
     * Weryfikuj token CSRF
     */
    public function verifyCsrfToken(?string $token): bool
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
        
        return !empty($token) && 
               !empty($_SESSION[CSRF_TOKEN_NAME]) && 
               hash_equals($_SESSION[CSRF_TOKEN_NAME], $token);
    }

    /**
     * Sprawdź rate limit
     */
    private function checkRateLimit(string $identifier): bool
    {
        // Wyczyść stare wpisy
        $this->db->execute(
            "DELETE FROM rate_limits WHERE first_attempt < datetime('now', '-1 minute')"
        );

        $result = $this->db->fetchOne(
            "SELECT attempts FROM rate_limits WHERE identifier = :id AND action = 'login'",
            ['id' => $identifier]
        );

        return !$result || $result['attempts'] < LOGIN_RATE_LIMIT;
    }

    /**
     * Zwiększ licznik rate limit
     */
    private function incrementRateLimit(string $identifier): void
    {
        $existing = $this->db->fetchOne(
            "SELECT id FROM rate_limits WHERE identifier = :id AND action = 'login'",
            ['id' => $identifier]
        );

        if ($existing) {
            $this->db->execute(
                "UPDATE rate_limits SET attempts = attempts + 1 WHERE identifier = :id AND action = 'login'",
                ['id' => $identifier]
            );
        } else {
            $this->db->insert(
                "INSERT INTO rate_limits (identifier, action, attempts) VALUES (:id, 'login', 1)",
                ['id' => $identifier]
            );
        }
    }

    /**
     * Wyczyść rate limit
     */
    private function clearRateLimit(string $identifier): void
    {
        $this->db->execute(
            "DELETE FROM rate_limits WHERE identifier = :id AND action = 'login'",
            ['id' => $identifier]
        );
    }
}
