<?php
/**
 * Klasa IpSecurity - zarządzanie bezpieczeństwem IP
 * 
 * - Blokada po nieudanych logowaniach
 * - Blacklist/Whitelist IP
 * - Historia logowań
 */

class IpSecurity
{
    private Database $db;
    private int $maxAttempts;
    private int $lockoutTime;
    
    public function __construct()
    {
        $this->db = Database::getInstance();
        
        // Wczytaj ustawienia
        $settings = $this->loadSettings();
        $this->maxAttempts = $settings['login_attempts_limit'] ?? 5;
        $this->lockoutTime = $settings['login_lockout_time'] ?? 300;
    }
    
    /**
     * Wczytaj ustawienia z pliku
     */
    private function loadSettings(): array
    {
        $configFile = __DIR__ . '/../../config/settings.json';
        if (file_exists($configFile)) {
            return json_decode(file_get_contents($configFile), true) ?? [];
        }
        return [];
    }
    
    /**
     * Sprawdź czy IP jest zablokowane
     */
    public function isIpBlocked(string $ip, ?int $userId = null): array
    {
        // Sprawdź globalny blacklist
        $blocked = $this->db->fetchOne(
            "SELECT * FROM ip_blacklist 
             WHERE ip_address = :ip 
             AND user_id IS NULL 
             AND (expires_at IS NULL OR expires_at > datetime('now'))",
            ['ip' => $ip]
        );
        
        if ($blocked) {
            return ['blocked' => true, 'reason' => $blocked['reason'] ?? 'IP na czarnej liście', 'type' => 'blacklist'];
        }
        
        // Sprawdź blacklist dla użytkownika
        if ($userId) {
            $blocked = $this->db->fetchOne(
                "SELECT * FROM ip_blacklist 
                 WHERE ip_address = :ip 
                 AND user_id = :user_id 
                 AND (expires_at IS NULL OR expires_at > datetime('now'))",
                ['ip' => $ip, 'user_id' => $userId]
            );
            
            if ($blocked) {
                return ['blocked' => true, 'reason' => $blocked['reason'] ?? 'IP zablokowane dla tego użytkownika', 'type' => 'blacklist_user'];
            }
        }
        
        // Sprawdź blokadę po nieudanych logowaniach
        $failed = $this->db->fetchOne(
            "SELECT * FROM failed_logins 
             WHERE ip_address = :ip 
             AND blocked_until > datetime('now')",
            ['ip' => $ip]
        );
        
        if ($failed) {
            $remainingTime = strtotime($failed['blocked_until']) - time();
            return [
                'blocked' => true, 
                'reason' => "Zbyt wiele nieudanych prób logowania. Spróbuj za " . ceil($remainingTime / 60) . " min.",
                'type' => 'failed_attempts',
                'remaining_seconds' => $remainingTime
            ];
        }
        
        return ['blocked' => false];
    }
    
    /**
     * Sprawdź czy IP jest na whitelist użytkownika
     */
    public function isIpWhitelisted(string $ip, int $userId): bool
    {
        // Sprawdź czy użytkownik ma włączony whitelist
        $hasWhitelist = $this->db->fetchOne(
            "SELECT COUNT(*) as count FROM ip_whitelist WHERE user_id = :user_id",
            ['user_id' => $userId]
        );
        
        // Jeśli nie ma żadnych wpisów w whitelist, przepuść
        if ($hasWhitelist['count'] == 0) {
            return true;
        }
        
        // Sprawdź czy IP jest na liście
        $whitelisted = $this->db->fetchOne(
            "SELECT * FROM ip_whitelist WHERE user_id = :user_id AND ip_address = :ip",
            ['user_id' => $userId, 'ip' => $ip]
        );
        
        return $whitelisted !== null;
    }
    
    /**
     * Zarejestruj nieudane logowanie
     */
    public function recordFailedLogin(string $ip, ?string $username = null): void
    {
        $existing = $this->db->fetchOne(
            "SELECT * FROM failed_logins WHERE ip_address = :ip",
            ['ip' => $ip]
        );
        
        if ($existing) {
            $attempts = $existing['attempts'] + 1;
            $blockedUntil = null;
            
            if ($attempts >= $this->maxAttempts) {
                $blockedUntil = date('Y-m-d H:i:s', time() + $this->lockoutTime);
            }
            
            $this->db->execute(
                "UPDATE failed_logins 
                 SET attempts = :attempts, 
                     last_attempt = datetime('now'),
                     username = COALESCE(:username, username),
                     blocked_until = :blocked_until
                 WHERE ip_address = :ip",
                [
                    'attempts' => $attempts,
                    'username' => $username,
                    'blocked_until' => $blockedUntil,
                    'ip' => $ip
                ]
            );
        } else {
            $this->db->execute(
                "INSERT INTO failed_logins (ip_address, username, attempts) 
                 VALUES (:ip, :username, 1)",
                ['ip' => $ip, 'username' => $username]
            );
        }
    }
    
    /**
     * Wyczyść nieudane logowania dla IP (po udanym logowaniu)
     */
    public function clearFailedLogins(string $ip): void
    {
        $this->db->execute(
            "DELETE FROM failed_logins WHERE ip_address = :ip",
            ['ip' => $ip]
        );
    }
    
    /**
     * Zarejestruj logowanie w historii
     */
    public function recordLogin(int $userId, string $ip, bool $success = true, ?string $failureReason = null): int
    {
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        $parsed = $this->parseUserAgent($userAgent);
        $geo = $this->getGeoLocation($ip);
        
        return $this->db->insert(
            "INSERT INTO login_history 
             (user_id, ip_address, user_agent, browser, os, device_type, country, city, success, failure_reason)
             VALUES (:user_id, :ip, :user_agent, :browser, :os, :device_type, :country, :city, :success, :failure_reason)",
            [
                'user_id' => $userId,
                'ip' => $ip,
                'user_agent' => $userAgent,
                'browser' => $parsed['browser'],
                'os' => $parsed['os'],
                'device_type' => $parsed['device_type'],
                'country' => $geo['country'],
                'city' => $geo['city'],
                'success' => $success ? 1 : 0,
                'failure_reason' => $failureReason
            ]
        );
    }
    
    /**
     * Parsuj User-Agent
     */
    private function parseUserAgent(string $userAgent): array
    {
        $browser = 'Nieznana';
        $os = 'Nieznany';
        $deviceType = 'desktop';
        
        // Wykryj przeglądarkę
        if (preg_match('/Firefox\/(\d+)/i', $userAgent, $m)) {
            $browser = 'Firefox ' . $m[1];
        } elseif (preg_match('/Edg\/(\d+)/i', $userAgent, $m)) {
            $browser = 'Edge ' . $m[1];
        } elseif (preg_match('/Chrome\/(\d+)/i', $userAgent, $m)) {
            $browser = 'Chrome ' . $m[1];
        } elseif (preg_match('/Safari\/(\d+)/i', $userAgent) && !preg_match('/Chrome/i', $userAgent)) {
            $browser = 'Safari';
        } elseif (preg_match('/Opera|OPR\/(\d+)/i', $userAgent, $m)) {
            $browser = 'Opera';
        }
        
        // Wykryj system operacyjny
        if (preg_match('/Windows NT 10/i', $userAgent)) {
            $os = 'Windows 10/11';
        } elseif (preg_match('/Windows NT 6\.3/i', $userAgent)) {
            $os = 'Windows 8.1';
        } elseif (preg_match('/Windows NT 6\.2/i', $userAgent)) {
            $os = 'Windows 8';
        } elseif (preg_match('/Windows NT 6\.1/i', $userAgent)) {
            $os = 'Windows 7';
        } elseif (preg_match('/Mac OS X/i', $userAgent)) {
            $os = 'macOS';
        } elseif (preg_match('/Linux/i', $userAgent)) {
            $os = 'Linux';
        } elseif (preg_match('/Android/i', $userAgent)) {
            $os = 'Android';
        } elseif (preg_match('/iOS|iPhone|iPad/i', $userAgent)) {
            $os = 'iOS';
        }
        
        // Wykryj typ urządzenia
        if (preg_match('/Mobile|Android|iPhone/i', $userAgent)) {
            $deviceType = 'mobile';
        } elseif (preg_match('/Tablet|iPad/i', $userAgent)) {
            $deviceType = 'tablet';
        }
        
        return compact('browser', 'os', 'deviceType');
    }
    
    /**
     * Pobierz geolokalizację IP (uproszczona wersja)
     */
    private function getGeoLocation(string $ip): array
    {
        // Dla lokalnych IP
        if (in_array($ip, ['127.0.0.1', '::1']) || strpos($ip, '192.168.') === 0 || strpos($ip, '10.') === 0) {
            return ['country' => 'Lokalny', 'city' => 'Localhost'];
        }
        
        // Próba użycia ip-api.com (darmowe, bez klucza)
        $cacheFile = sys_get_temp_dir() . '/ip_geo_' . md5($ip) . '.json';
        
        // Sprawdź cache (24h)
        if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < 86400) {
            $cached = json_decode(file_get_contents($cacheFile), true);
            if ($cached) {
                return $cached;
            }
        }
        
        // Zapytanie do API (z timeoutem)
        $ctx = stream_context_create(['http' => ['timeout' => 2]]);
        $response = @file_get_contents("http://ip-api.com/json/{$ip}?fields=country,city", false, $ctx);
        
        if ($response) {
            $data = json_decode($response, true);
            if ($data && isset($data['country'])) {
                $result = [
                    'country' => $data['country'] ?? 'Nieznany',
                    'city' => $data['city'] ?? 'Nieznane'
                ];
                file_put_contents($cacheFile, json_encode($result));
                return $result;
            }
        }
        
        return ['country' => 'Nieznany', 'city' => 'Nieznane'];
    }
    
    /**
     * Pobierz historię logowań użytkownika
     */
    public function getLoginHistory(int $userId, int $limit = 50, int $offset = 0): array
    {
        return $this->db->fetchAll(
            "SELECT * FROM login_history 
             WHERE user_id = :user_id 
             ORDER BY created_at DESC 
             LIMIT :limit OFFSET :offset",
            ['user_id' => $userId, 'limit' => $limit, 'offset' => $offset]
        );
    }
    
    /**
     * Pobierz wszystkie logowania (dla admina)
     */
    public function getAllLoginHistory(int $limit = 50, int $offset = 0, ?int $userId = null): array
    {
        $sql = "SELECT lh.*, u.username 
                FROM login_history lh
                JOIN users u ON lh.user_id = u.id";
        $params = ['limit' => $limit, 'offset' => $offset];
        
        if ($userId) {
            $sql .= " WHERE lh.user_id = :user_id";
            $params['user_id'] = $userId;
        }
        
        $sql .= " ORDER BY lh.created_at DESC LIMIT :limit OFFSET :offset";
        
        return $this->db->fetchAll($sql, $params);
    }
    
    /**
     * Zlicz logowania
     */
    public function countLoginHistory(?int $userId = null): int
    {
        if ($userId) {
            $result = $this->db->fetchOne(
                "SELECT COUNT(*) as count FROM login_history WHERE user_id = :user_id",
                ['user_id' => $userId]
            );
        } else {
            $result = $this->db->fetchOne("SELECT COUNT(*) as count FROM login_history");
        }
        return $result['count'] ?? 0;
    }
    
    // ==================== BLACKLIST ====================
    
    /**
     * Dodaj IP do blacklist
     */
    public function addToBlacklist(string $ip, ?int $userId = null, ?string $reason = null, ?string $expiresAt = null, ?int $createdBy = null): bool
    {
        // Sprawdź czy już istnieje
        $existing = $this->db->fetchOne(
            "SELECT id FROM ip_blacklist WHERE ip_address = :ip AND (user_id = :user_id OR (user_id IS NULL AND :user_id IS NULL))",
            ['ip' => $ip, 'user_id' => $userId]
        );
        
        if ($existing) {
            return false;
        }
        
        $this->db->execute(
            "INSERT INTO ip_blacklist (ip_address, user_id, reason, expires_at, created_by)
             VALUES (:ip, :user_id, :reason, :expires_at, :created_by)",
            [
                'ip' => $ip,
                'user_id' => $userId,
                'reason' => $reason,
                'expires_at' => $expiresAt,
                'created_by' => $createdBy
            ]
        );
        
        return true;
    }
    
    /**
     * Usuń IP z blacklist
     */
    public function removeFromBlacklist(int $id): bool
    {
        $this->db->execute("DELETE FROM ip_blacklist WHERE id = :id", ['id' => $id]);
        return true;
    }
    
    /**
     * Pobierz blacklist
     */
    public function getBlacklist(?int $userId = null): array
    {
        if ($userId === null) {
            return $this->db->fetchAll(
                "SELECT b.*, u.username as user_name, c.username as created_by_name
                 FROM ip_blacklist b
                 LEFT JOIN users u ON b.user_id = u.id
                 LEFT JOIN users c ON b.created_by = c.id
                 ORDER BY b.created_at DESC"
            );
        } else {
            return $this->db->fetchAll(
                "SELECT b.*, c.username as created_by_name
                 FROM ip_blacklist b
                 LEFT JOIN users c ON b.created_by = c.id
                 WHERE b.user_id = :user_id OR b.user_id IS NULL
                 ORDER BY b.created_at DESC",
                ['user_id' => $userId]
            );
        }
    }
    
    // ==================== WHITELIST ====================
    
    /**
     * Dodaj IP do whitelist użytkownika
     */
    public function addToWhitelist(string $ip, int $userId, ?string $description = null, ?int $createdBy = null): bool
    {
        $existing = $this->db->fetchOne(
            "SELECT id FROM ip_whitelist WHERE ip_address = :ip AND user_id = :user_id",
            ['ip' => $ip, 'user_id' => $userId]
        );
        
        if ($existing) {
            return false;
        }
        
        $this->db->execute(
            "INSERT INTO ip_whitelist (ip_address, user_id, description, created_by)
             VALUES (:ip, :user_id, :description, :created_by)",
            [
                'ip' => $ip,
                'user_id' => $userId,
                'description' => $description,
                'created_by' => $createdBy
            ]
        );
        
        return true;
    }
    
    /**
     * Usuń IP z whitelist
     */
    public function removeFromWhitelist(int $id): bool
    {
        $this->db->execute("DELETE FROM ip_whitelist WHERE id = :id", ['id' => $id]);
        return true;
    }
    
    /**
     * Pobierz whitelist użytkownika
     */
    public function getWhitelist(int $userId): array
    {
        return $this->db->fetchAll(
            "SELECT w.*, c.username as created_by_name
             FROM ip_whitelist w
             LEFT JOIN users c ON w.created_by = c.id
             WHERE w.user_id = :user_id
             ORDER BY w.created_at DESC",
            ['user_id' => $userId]
        );
    }
    
    /**
     * Pobierz zablokowane IP (failed logins)
     */
    public function getBlockedIps(): array
    {
        return $this->db->fetchAll(
            "SELECT * FROM failed_logins 
             WHERE blocked_until > datetime('now')
             ORDER BY blocked_until DESC"
        );
    }
    
    /**
     * Odblokuj IP
     */
    public function unblockIp(string $ip): bool
    {
        $this->db->execute(
            "UPDATE failed_logins SET blocked_until = NULL, attempts = 0 WHERE ip_address = :ip",
            ['ip' => $ip]
        );
        return true;
    }
    
    /**
     * Statystyki logowań
     */
    public function getLoginStats(?int $userId = null, int $days = 30): array
    {
        $params = ['days' => $days];
        $userFilter = '';
        
        if ($userId) {
            $userFilter = 'AND user_id = :user_id';
            $params['user_id'] = $userId;
        }
        
        // Logowania per dzień
        $perDay = $this->db->fetchAll(
            "SELECT date(created_at) as date, 
                    SUM(success) as successful,
                    SUM(1 - success) as failed
             FROM login_history 
             WHERE created_at > datetime('now', '-' || :days || ' days') {$userFilter}
             GROUP BY date(created_at)
             ORDER BY date ASC",
            $params
        );
        
        // Top IP
        $topIps = $this->db->fetchAll(
            "SELECT ip_address, COUNT(*) as count, 
                    SUM(success) as successful,
                    MAX(country) as country
             FROM login_history 
             WHERE created_at > datetime('now', '-' || :days || ' days') {$userFilter}
             GROUP BY ip_address
             ORDER BY count DESC
             LIMIT 10",
            $params
        );
        
        // Top przeglądarki
        $topBrowsers = $this->db->fetchAll(
            "SELECT browser, COUNT(*) as count
             FROM login_history 
             WHERE created_at > datetime('now', '-' || :days || ' days') {$userFilter}
             GROUP BY browser
             ORDER BY count DESC
             LIMIT 5",
            $params
        );
        
        // Top kraje
        $topCountries = $this->db->fetchAll(
            "SELECT country, COUNT(*) as count
             FROM login_history 
             WHERE created_at > datetime('now', '-' || :days || ' days') {$userFilter}
             GROUP BY country
             ORDER BY count DESC
             LIMIT 10",
            $params
        );
        
        return [
            'per_day' => $perDay,
            'top_ips' => $topIps,
            'top_browsers' => $topBrowsers,
            'top_countries' => $topCountries
        ];
    }
}
