<?php
/**
 * Klasa LoginSecurity - bezpieczeństwo logowania
 * 
 * Funkcje:
 * - Historia logowań
 * - Blokada IP po nieudanych próbach
 * - Whitelist/Blacklist IP
 * - Geolokalizacja
 */

class LoginSecurity
{
    private Database $db;
    
    // Domyślne ustawienia (można nadpisać z konfiguracji)
    private int $maxAttempts = 5;
    private int $lockoutTime = 300; // 5 minut
    
    const RULE_WHITELIST = 'whitelist';
    const RULE_BLACKLIST = 'blacklist';
    
    public function __construct()
    {
        $this->db = Database::getInstance();
        $this->loadSettings();
    }
    
    /**
     * Wczytaj ustawienia z konfiguracji
     */
    private function loadSettings(): void
    {
        $configFile = __DIR__ . '/../../config/settings.json';
        if (file_exists($configFile)) {
            $settings = json_decode(file_get_contents($configFile), true);
            if (isset($settings['login_attempts_limit'])) {
                $this->maxAttempts = (int)$settings['login_attempts_limit'];
            }
            if (isset($settings['login_lockout_time'])) {
                $this->lockoutTime = (int)$settings['login_lockout_time'];
            }
        }
    }
    
    // ==================== HISTORIA LOGOWAŃ ====================
    
    /**
     * Zapisz logowanie
     */
    public function logLogin(int $userId, bool $success = true, ?string $failureReason = null): void
    {
        $ip = $this->getClientIp();
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        $browserInfo = $this->parseUserAgent($userAgent);
        $geoInfo = $this->getGeoInfo($ip);
        
        $this->db->insert(
            "INSERT INTO login_history 
             (user_id, ip_address, user_agent, browser, os, device_type, country, city, success, failure_reason, created_at)
             VALUES 
             (:user_id, :ip, :user_agent, :browser, :os, :device_type, :country, :city, :success, :failure_reason, datetime('now'))",
            [
                'user_id' => $userId,
                'ip' => $ip,
                'user_agent' => $userAgent,
                'browser' => $browserInfo['browser'],
                'os' => $browserInfo['os'],
                'device_type' => $browserInfo['device_type'],
                'country' => $geoInfo['country'],
                'city' => $geoInfo['city'],
                'success' => $success ? 1 : 0,
                'failure_reason' => $failureReason
            ]
        );
        
        // Jeśli sukces - wyczyść próby logowania
        if ($success) {
            $this->clearAttempts($ip);
        }
    }
    
    /**
     * Pobierz historię logowań użytkownika
     */
    public function getLoginHistory(int $userId, int $limit = 50): array
    {
        return $this->db->fetchAll(
            "SELECT * FROM login_history 
             WHERE user_id = :user_id 
             ORDER BY created_at DESC 
             LIMIT :limit",
            ['user_id' => $userId, 'limit' => $limit]
        );
    }
    
    /**
     * Pobierz wszystkie logowania (dla admina)
     */
    public function getAllLogins(int $limit = 100, int $offset = 0, ?array $filters = null): array
    {
        $where = [];
        $params = ['limit' => $limit, 'offset' => $offset];
        
        if (!empty($filters['user_id'])) {
            $where[] = "lh.user_id = :user_id";
            $params['user_id'] = $filters['user_id'];
        }
        
        if (!empty($filters['ip_address'])) {
            $where[] = "lh.ip_address LIKE :ip";
            $params['ip'] = '%' . $filters['ip_address'] . '%';
        }
        
        if (isset($filters['success'])) {
            $where[] = "lh.success = :success";
            $params['success'] = $filters['success'] ? 1 : 0;
        }
        
        if (!empty($filters['date_from'])) {
            $where[] = "lh.created_at >= :date_from";
            $params['date_from'] = $filters['date_from'];
        }
        
        if (!empty($filters['date_to'])) {
            $where[] = "lh.created_at <= :date_to";
            $params['date_to'] = $filters['date_to'];
        }
        
        $whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
        
        return $this->db->fetchAll(
            "SELECT lh.*, u.username
             FROM login_history lh
             LEFT JOIN users u ON lh.user_id = u.id
             {$whereClause}
             ORDER BY lh.created_at DESC
             LIMIT :limit OFFSET :offset",
            $params
        );
    }
    
    /**
     * Zlicz logowania
     */
    public function countLogins(?array $filters = null): int
    {
        $where = [];
        $params = [];
        
        if (!empty($filters['user_id'])) {
            $where[] = "user_id = :user_id";
            $params['user_id'] = $filters['user_id'];
        }
        
        if (isset($filters['success'])) {
            $where[] = "success = :success";
            $params['success'] = $filters['success'] ? 1 : 0;
        }
        
        $whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
        
        $result = $this->db->fetchOne(
            "SELECT COUNT(*) as count FROM login_history {$whereClause}",
            $params
        );
        
        return $result['count'] ?? 0;
    }
    
    // ==================== BLOKADA IP ====================
    
    /**
     * Sprawdź czy IP jest zablokowane
     */
    public function isIpBlocked(?string $ip = null): bool
    {
        $ip = $ip ?? $this->getClientIp();
        
        // Sprawdź blacklist
        if ($this->isBlacklisted($ip)) {
            return true;
        }
        
        // Sprawdź nieudane próby
        $attempt = $this->db->fetchOne(
            "SELECT * FROM login_attempts WHERE ip_address = :ip",
            ['ip' => $ip]
        );
        
        if ($attempt && $attempt['blocked_until']) {
            if (strtotime($attempt['blocked_until']) > time()) {
                return true;
            } else {
                // Blokada wygasła - wyczyść
                $this->clearAttempts($ip);
            }
        }
        
        return false;
    }
    
    /**
     * Pobierz czas pozostały do odblokowania
     */
    public function getBlockedUntil(?string $ip = null): ?string
    {
        $ip = $ip ?? $this->getClientIp();
        
        $attempt = $this->db->fetchOne(
            "SELECT blocked_until FROM login_attempts WHERE ip_address = :ip",
            ['ip' => $ip]
        );
        
        return $attempt['blocked_until'] ?? null;
    }
    
    /**
     * Zarejestruj nieudaną próbę logowania
     */
    public function recordFailedAttempt(?string $username = null, ?string $ip = null): array
    {
        $ip = $ip ?? $this->getClientIp();
        
        $attempt = $this->db->fetchOne(
            "SELECT * FROM login_attempts WHERE ip_address = :ip",
            ['ip' => $ip]
        );
        
        if ($attempt) {
            $attempts = $attempt['attempts'] + 1;
            $blockedUntil = null;
            
            if ($attempts >= $this->maxAttempts) {
                $blockedUntil = date('Y-m-d H:i:s', time() + $this->lockoutTime);
            }
            
            $this->db->execute(
                "UPDATE login_attempts 
                 SET attempts = :attempts, 
                     last_attempt = datetime('now'),
                     blocked_until = :blocked_until,
                     username = COALESCE(:username, username)
                 WHERE ip_address = :ip",
                [
                    'attempts' => $attempts,
                    'blocked_until' => $blockedUntil,
                    'username' => $username,
                    'ip' => $ip
                ]
            );
        } else {
            $attempts = 1;
            $blockedUntil = null;
            
            if ($attempts >= $this->maxAttempts) {
                $blockedUntil = date('Y-m-d H:i:s', time() + $this->lockoutTime);
            }
            
            $this->db->insert(
                "INSERT INTO login_attempts (ip_address, username, attempts, blocked_until, first_attempt, last_attempt)
                 VALUES (:ip, :username, :attempts, :blocked_until, datetime('now'), datetime('now'))",
                [
                    'ip' => $ip,
                    'username' => $username,
                    'attempts' => $attempts,
                    'blocked_until' => $blockedUntil
                ]
            );
        }
        
        return [
            'attempts' => $attempts,
            'max_attempts' => $this->maxAttempts,
            'remaining' => max(0, $this->maxAttempts - $attempts),
            'blocked' => $blockedUntil !== null,
            'blocked_until' => $blockedUntil
        ];
    }
    
    /**
     * Wyczyść próby logowania dla IP
     */
    public function clearAttempts(?string $ip = null): void
    {
        $ip = $ip ?? $this->getClientIp();
        
        $this->db->execute(
            "DELETE FROM login_attempts WHERE ip_address = :ip",
            ['ip' => $ip]
        );
    }
    
    /**
     * Pobierz listę zablokowanych IP
     */
    public function getBlockedIps(): array
    {
        return $this->db->fetchAll(
            "SELECT * FROM login_attempts 
             WHERE blocked_until > datetime('now')
             ORDER BY blocked_until DESC"
        );
    }
    
    /**
     * Odblokuj IP ręcznie
     */
    public function unblockIp(string $ip): bool
    {
        $this->db->execute(
            "DELETE FROM login_attempts WHERE ip_address = :ip",
            ['ip' => $ip]
        );
        
        return true;
    }
    
    // ==================== WHITELIST / BLACKLIST ====================
    
    /**
     * Sprawdź czy IP jest na whitelist
     */
    public function isWhitelisted(string $ip, ?int $userId = null): bool
    {
        $params = ['ip' => $ip, 'type' => self::RULE_WHITELIST];
        $userCondition = $userId ? "AND (user_id = :user_id OR user_id IS NULL)" : "AND user_id IS NULL";
        
        if ($userId) {
            $params['user_id'] = $userId;
        }
        
        $result = $this->db->fetchOne(
            "SELECT id FROM ip_rules 
             WHERE ip_address = :ip 
             AND rule_type = :type
             AND (expires_at IS NULL OR expires_at > datetime('now'))
             {$userCondition}",
            $params
        );
        
        return $result !== null;
    }
    
    /**
     * Sprawdź czy IP jest na blacklist
     */
    public function isBlacklisted(string $ip, ?int $userId = null): bool
    {
        $params = ['ip' => $ip, 'type' => self::RULE_BLACKLIST];
        $userCondition = $userId ? "AND (user_id = :user_id OR user_id IS NULL)" : "";
        
        if ($userId) {
            $params['user_id'] = $userId;
        }
        
        $result = $this->db->fetchOne(
            "SELECT id FROM ip_rules 
             WHERE ip_address = :ip 
             AND rule_type = :type
             AND (expires_at IS NULL OR expires_at > datetime('now'))
             {$userCondition}",
            $params
        );
        
        return $result !== null;
    }
    
    /**
     * Dodaj regułę IP
     */
    public function addIpRule(
        string $ip, 
        string $type = self::RULE_BLACKLIST, 
        ?int $userId = null, 
        ?string $reason = null,
        ?string $expiresAt = null,
        ?int $createdBy = null
    ): int {
        return $this->db->insert(
            "INSERT INTO ip_rules (ip_address, rule_type, user_id, reason, expires_at, created_by, created_at)
             VALUES (:ip, :type, :user_id, :reason, :expires_at, :created_by, datetime('now'))",
            [
                'ip' => $ip,
                'type' => $type,
                'user_id' => $userId,
                'reason' => $reason,
                'expires_at' => $expiresAt,
                'created_by' => $createdBy
            ]
        );
    }
    
    /**
     * Usuń regułę IP
     */
    public function removeIpRule(int $id): bool
    {
        $this->db->execute(
            "DELETE FROM ip_rules WHERE id = :id",
            ['id' => $id]
        );
        
        return $this->db->getLastStatement()->rowCount() > 0;
    }
    
    /**
     * Pobierz wszystkie reguły IP
     */
    public function getIpRules(?string $type = null, ?int $userId = null): array
    {
        $where = [];
        $params = [];
        
        if ($type) {
            $where[] = "r.rule_type = :type";
            $params['type'] = $type;
        }
        
        if ($userId !== null) {
            $where[] = "(r.user_id = :user_id OR r.user_id IS NULL)";
            $params['user_id'] = $userId;
        }
        
        $whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
        
        return $this->db->fetchAll(
            "SELECT r.*, 
                    u.username as target_username,
                    c.username as created_by_username
             FROM ip_rules r
             LEFT JOIN users u ON r.user_id = u.id
             LEFT JOIN users c ON r.created_by = c.id
             {$whereClause}
             ORDER BY r.created_at DESC",
            $params
        );
    }
    
    // ==================== HELPER METHODS ====================
    
    /**
     * Pobierz prawdziwe IP klienta
     */
    public function getClientIp(): string
    {
        $headers = [
            'HTTP_CF_CONNECTING_IP',     // Cloudflare
            'HTTP_X_FORWARDED_FOR',      // Proxy
            'HTTP_X_REAL_IP',            // Nginx
            'HTTP_CLIENT_IP',
            'REMOTE_ADDR'
        ];
        
        foreach ($headers as $header) {
            if (!empty($_SERVER[$header])) {
                $ip = $_SERVER[$header];
                // X-Forwarded-For może zawierać wiele IP
                if (strpos($ip, ',') !== false) {
                    $ip = trim(explode(',', $ip)[0]);
                }
                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    return $ip;
                }
            }
        }
        
        return '0.0.0.0';
    }
    
    /**
     * Parsuj User Agent
     */
    private function parseUserAgent(string $userAgent): array
    {
        $result = [
            'browser' => 'Unknown',
            'os' => 'Unknown',
            'device_type' => 'Desktop'
        ];
        
        // Browser detection
        if (preg_match('/Firefox\/(\d+)/i', $userAgent, $m)) {
            $result['browser'] = 'Firefox ' . $m[1];
        } elseif (preg_match('/Edg\/(\d+)/i', $userAgent, $m)) {
            $result['browser'] = 'Edge ' . $m[1];
        } elseif (preg_match('/Chrome\/(\d+)/i', $userAgent, $m)) {
            $result['browser'] = 'Chrome ' . $m[1];
        } elseif (preg_match('/Safari\/(\d+)/i', $userAgent) && !preg_match('/Chrome/i', $userAgent)) {
            $result['browser'] = 'Safari';
        } elseif (preg_match('/Opera|OPR\/(\d+)/i', $userAgent, $m)) {
            $result['browser'] = 'Opera';
        }
        
        // OS detection
        if (preg_match('/Windows NT 10/i', $userAgent)) {
            $result['os'] = 'Windows 10/11';
        } elseif (preg_match('/Windows NT 6\.3/i', $userAgent)) {
            $result['os'] = 'Windows 8.1';
        } elseif (preg_match('/Windows NT 6\.1/i', $userAgent)) {
            $result['os'] = 'Windows 7';
        } elseif (preg_match('/Mac OS X/i', $userAgent)) {
            $result['os'] = 'macOS';
        } elseif (preg_match('/Linux/i', $userAgent)) {
            $result['os'] = 'Linux';
        } elseif (preg_match('/Android/i', $userAgent)) {
            $result['os'] = 'Android';
        } elseif (preg_match('/iOS|iPhone|iPad/i', $userAgent)) {
            $result['os'] = 'iOS';
        }
        
        // Device type
        if (preg_match('/Mobile|Android|iPhone/i', $userAgent)) {
            $result['device_type'] = 'Mobile';
        } elseif (preg_match('/Tablet|iPad/i', $userAgent)) {
            $result['device_type'] = 'Tablet';
        }
        
        return $result;
    }
    
    /**
     * Pobierz informacje geograficzne (placeholder - wymaga zewnętrznego API)
     */
    private function getGeoInfo(string $ip): array
    {
        // Można zintegrować z:
        // - ip-api.com (darmowe)
        // - ipinfo.io
        // - MaxMind GeoIP
        
        // Dla lokalnych IP
        if (in_array($ip, ['127.0.0.1', '::1']) || strpos($ip, '192.168.') === 0 || strpos($ip, '10.') === 0) {
            return ['country' => 'Local', 'city' => 'Local'];
        }
        
        // Placeholder - w produkcji użyj prawdziwego API
        return ['country' => null, 'city' => null];
    }
    
    /**
     * Statystyki bezpieczeństwa
     */
    public function getSecurityStats(int $days = 30): array
    {
        $stats = [];
        
        // Logowania
        $loginStats = $this->db->fetchOne(
            "SELECT 
                COUNT(*) as total,
                SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful,
                SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as failed,
                COUNT(DISTINCT ip_address) as unique_ips
             FROM login_history
             WHERE created_at >= datetime('now', '-' || :days || ' days')",
            ['days' => $days]
        );
        $stats['logins'] = $loginStats;
        
        // Zablokowane IP
        $blocked = $this->db->fetchOne(
            "SELECT COUNT(*) as count FROM login_attempts WHERE blocked_until > datetime('now')"
        );
        $stats['blocked_ips'] = $blocked['count'] ?? 0;
        
        // Reguły IP
        $rules = $this->db->fetchOne(
            "SELECT 
                SUM(CASE WHEN rule_type = 'whitelist' THEN 1 ELSE 0 END) as whitelist,
                SUM(CASE WHEN rule_type = 'blacklist' THEN 1 ELSE 0 END) as blacklist
             FROM ip_rules
             WHERE expires_at IS NULL OR expires_at > datetime('now')"
        );
        $stats['ip_rules'] = $rules;
        
        // Top failed IPs
        $stats['top_failed_ips'] = $this->db->fetchAll(
            "SELECT ip_address, COUNT(*) as count
             FROM login_history
             WHERE success = 0 
             AND created_at >= datetime('now', '-' || :days || ' days')
             GROUP BY ip_address
             ORDER BY count DESC
             LIMIT 10",
            ['days' => $days]
        );
        
        return $stats;
    }
}
