<?php
// src/helpers/SecurityHelper.php

class SecurityHelper {
    
    // Límite de intentos de login
    const MAX_LOGIN_ATTEMPTS = 5;
    const LOCKOUT_DURATION = 15 * 60; // 15 minutos en segundos
    
    /**
     * Obtener la IP real del cliente
     */
    private static function get_client_ip() {
        $ip_keys = [
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_FORWARDED',
            'HTTP_X_CLUSTER_CLIENT_IP',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED',
            'REMOTE_ADDR'
        ];
        
        foreach ($ip_keys as $key) {
            if (array_key_exists($key, $_SERVER) && !empty($_SERVER[$key])) {
                $ip_list = explode(',', $_SERVER[$key]);
                $client_ip = trim(end($ip_list));
                
                if (filter_var($client_ip, FILTER_VALIDATE_IP)) {
                    return $client_ip;
                }
            }
        }
        
        return 'unknown';
    }
    
    /**
     * Registrar intento de login fallido
     */
    public static function recordFailedAttempt($db, $identifier, $isEmail = false) {
        $rate_key = $isEmail ? "user:" . strtolower($identifier) : "ip:" . $identifier;
        
        // Buscar intentos existentes
        $stmt = $db->prepare("SELECT * FROM login_attempts WHERE rate_key = ?");
        $stmt->execute([$rate_key]);
        $attempt = $stmt->fetch();
        
        if ($attempt) {
            // Actualizar intento existente
            $new_attempts = $attempt['attempts'] + 1;
            $locked_until = null;
            
            // Bloquear si supera el máximo de intentos
            if ($new_attempts >= self::MAX_LOGIN_ATTEMPTS) {
                $locked_until = date('Y-m-d H:i:s', time() + self::LOCKOUT_DURATION);
            }
            
            $update = $db->prepare("
                UPDATE login_attempts 
                SET attempts = ?, last_attempt = NOW(), locked_until = ?
                WHERE rate_key = ?
            ");
            $update->execute([$new_attempts, $locked_until, $rate_key]);
        } else {
            // Crear nuevo registro
            $insert = $db->prepare("
                INSERT INTO login_attempts (id, rate_key, attempts, first_attempt, last_attempt)
                VALUES (UUID(), ?, 1, NOW(), NOW())
            ");
            $insert->execute([$rate_key]);
        }
        
        return true;
    }
    
    /**
     * Verificar si está bloqueado por rate limiting
     */
    public static function isRateLimited($db, $identifier, $isEmail = false) {
        $rate_key = $isEmail ? "user:" . strtolower($identifier) : "ip:" . $identifier;
        
        $stmt = $db->prepare("
            SELECT attempts, locked_until 
            FROM login_attempts 
            WHERE rate_key = ? AND locked_until > NOW()
        ");
        $stmt->execute([$rate_key]);
        $attempt = $stmt->fetch();
        
        return $attempt ? $attempt['locked_until'] : false;
    }
    
    /**
     * Limpiar intentos exitosos
     */
    public static function clearFailedAttempts($db, $identifier, $isEmail = false) {
        $rate_key = $isEmail ? "user:" . strtolower($identifier) : "ip:" . $identifier;
        
        $delete = $db->prepare("DELETE FROM login_attempts WHERE rate_key = ?");
        return $delete->execute([$rate_key]);
    }
    
    /**
     * Registrar evento de seguridad extendido
     */
    public static function logSecurityEvent($db, $action, $details = null, $user_id = null, $severity = 'medium', $admin_reason = null) {
        $ip_address = self::get_client_ip();
        $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
        
        $stmt = $db->prepare("
            INSERT INTO security_logs (id, user_id, ip_address, user_agent, action, details, severity, admin_reason)
            VALUES (UUID(), ?, ?, ?, ?, ?, ?, ?)
        ");
        return $stmt->execute([$user_id, $ip_address, $user_agent, $action, $details, $severity, $admin_reason]);
    }

    /**
     * Detectar actividad sospechosa
     */
    public static function detectSuspiciousActivity($db, $user_id, $action, $threshold = 10) {
        // Contar acciones similares en los últimos 5 minutos
        $stmt = $db->prepare("
            SELECT COUNT(*) as count 
            FROM security_logs 
            WHERE user_id = ? AND action = ? AND created_at >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
        ");
        $stmt->execute([$user_id, $action]);
        $count = $stmt->fetch()['count'];
        
        if ($count >= $threshold) {
            // Bloquear usuario temporalmente
            $lock_until = date('Y-m-d H:i:s', time() + (30 * 60)); // 30 minutos
            $db->prepare("UPDATE users SET locked_until = ? WHERE id = ?")->execute([$lock_until, $user_id]);
            
            self::logSecurityEvent($db, 'user_auto_blocked', 
                "Usuario bloqueado automáticamente por actividad sospechosa: $action ($count intentos)", 
                $user_id, 'high');
                
            return true;
        }
        
        return false;
    }

    /**
     * Escanear contenido para detectar spam
     */
    public static function detectSpamContent($text) {
        $spam_patterns = [
            '/http[s]?:\/\/[^\s]*/i', // URLs
            '/[0-9]{4,}/', // Números largos (posibles números de teléfono)
            '/[^\s]{20,}/', // Palabras muy largas
            '/\b(?:free|money|profit|win|prize|offer|discount)\b/i', // Palabras de spam comunes
        ];
        
        $score = 0;
        foreach ($spam_patterns as $pattern) {
            $matches = [];
            preg_match_all($pattern, $text, $matches);
            $score += count($matches[0]);
        }
        
        return $score >= 3; // Si tiene 3 o más indicadores de spam
    }
    
    /**
     * Generar token seguro
     */
    public static function generateToken($length = 64) {
        return bin2hex(random_bytes($length / 2));
    }
    
    /**
     * Limpiar tokens expirados
     */
    public static function cleanupExpiredTokens($db) {
        // Limpiar verificaciones de email expiradas
        $db->prepare("DELETE FROM email_verifications WHERE expires_at <= NOW()")->execute();
        
        // Limpiar reseteos de contraseña expirados
        $db->prepare("DELETE FROM password_resets WHERE expires_at <= NOW()")->execute();
        
        // Limpiar intentos de login muy antiguos (más de 30 días)
        $db->prepare("DELETE FROM login_attempts WHERE last_attempt <= DATE_SUB(NOW(), INTERVAL 30 DAY)")->execute();
        
        return true;
    }
    
    /**
     * Protección avanzada contra XSS
     */
    public static function sanitizeInput($input, $allow_html = false) {
        if (is_array($input)) {
            return array_map(fn($item) => self::sanitizeInput($item, $allow_html), $input);
        }
        
        $input = trim($input);
        
        if ($allow_html) {
            // Permitir HTML seguro solo si es necesario
            $allowed_tags = '<p><br><strong><em><u><a><ul><ol><li><span>';
            $input = strip_tags($input, $allowed_tags);
            
            // Validar atributos en tags permitidos
            $input = self::sanitizeHtmlAttributes($input);
        } else {
            // Escapar completamente para texto plano
            $input = htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        }
        
        // Eliminar caracteres de control (excepto tab, newline, carriage return)
        $input = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $input);
        
        // Normalizar caracteres Unicode
        if (function_exists('normalizer_normalize')) {
            $input = normalizer_normalize($input);
            if ($input === false) {
                $input = mb_convert_encoding($input, 'UTF-8', 'UTF-8');
            }
        }
        
        return $input;
    }
    
    private static function sanitizeHtmlAttributes($html) {
        if (empty($html)) return $html;
        
        $dom = new DOMDocument();
        @$dom->loadHTML('<?xml encoding="UTF-8">' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        
        $allowed_attributes = [
            'a' => ['href', 'title', 'target', 'rel'],
            'span' => ['class', 'style'],
            'p' => ['class', 'style'],
            'strong' => [],
            'em' => [],
            'u' => [],
            'ul' => [],
            'ol' => [],
            'li' => [],
            'br' => [],
        ];
        
        $xpath = new DOMXPath($dom);
        foreach ($xpath->query('//@*') as $attribute) {
            $node = $attribute->ownerElement;
            $tag_name = $node->tagName;
            $attr_name = $attribute->name;
            
            if (!isset($allowed_attributes[$tag_name]) || 
                !in_array($attr_name, $allowed_attributes[$tag_name])) {
                $node->removeAttribute($attr_name);
                continue;
            }
            
            // Sanitizar valores de atributos específicos
            if ($attr_name === 'href') {
                $value = $attribute->value;
                if (!self::isSafeUrl($value)) {
                    $node->removeAttribute('href');
                }
            }
            
            if ($attr_name === 'style') {
                $value = $attribute->value;
                if (!self::isSafeCss($value)) {
                    $node->removeAttribute('style');
                }
            }
        }
        
        // Convertir de vuelta a HTML
        $html = $dom->saveHTML();
        return strip_tags($html, array_keys($allowed_attributes));
    }
    
    private static function isSafeUrl($url) {
        // Permitir solo URLs http, https, mailto y tel
        if (preg_match('/^(https?|mailto|tel):/i', $url)) {
            return true;
        }
        
        // Permitir URLs relativas
        if (preg_match('/^[\/#?]/', $url)) {
            return true;
        }
        
        return false;
    }
    
    private static function isSafeCss($css) {
        // Lista de propiedades CSS permitidas
        $allowed_properties = [
            'color', 'background-color', 'font-size', 'font-weight', 
            'text-align', 'text-decoration', 'margin', 'padding'
        ];
        
        $declarations = explode(';', $css);
        foreach ($declarations as $declaration) {
            if (empty(trim($declaration))) continue;
            
            $parts = explode(':', $declaration, 2);
            if (count($parts) < 2) continue;
            
            list($property, $value) = array_map('trim', $parts);
            
            if (!in_array(strtolower($property), $allowed_properties)) {
                return false;
            }
            
            // Validar valores CSS (prevenir expression(), javascript:, etc.)
            if (preg_match('/expression|javascript|eval|url\([^)]*data:/i', $value)) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * Protección avanzada contra Brute Force
     */
    public static function checkBruteForceAdvanced($db, $identifier, $isEmail = false) {
        $rate_key = $isEmail ? "user:" . strtolower($identifier) : "ip:" . $identifier;
        
        // Verificar en login_attempts
        $stmt = $db->prepare("
            SELECT attempts, locked_until 
            FROM login_attempts 
            WHERE rate_key = ? AND locked_until > NOW()
        ");
        $stmt->execute([$rate_key]);
        $attempt = $stmt->fetch();
        
        if ($attempt) {
            return $attempt['locked_until'];
        }
        
        // Verificar en security_logs (más completo)
        $stmt = $db->prepare("
            SELECT COUNT(*) as attempts 
            FROM security_logs 
            WHERE (ip_address = ? OR user_id = ?) 
            AND action = 'login_failed' 
            AND created_at >= DATE_SUB(NOW(), INTERVAL 15 MINUTE)
        ");
        
        $stmt->execute([$identifier, $identifier]);
        $recent_attempts = $stmt->fetch()['attempts'];
        
        // Bloquear si hay más de 10 intentos en 15 minutos
        if ($recent_attempts >= 10) {
            $lock_until = date('Y-m-d H:i:s', time() + (30 * 60)); // 30 minutos
            
            if ($isEmail) {
                $db->prepare("UPDATE users SET locked_until = ? WHERE email = ?")
                   ->execute([$lock_until, $identifier]);
            }
            
            self::logSecurityEvent($db, 'brute_force_block', 
                "Bloqueo por brute force: $identifier", null, 'high');
            
            return $lock_until;
        }
        
        return false;
    }
    
    /**
     * Validación de archivos subidos
     */
    public static function validateUploadedFile($file, $allowed_types = ['image/jpeg', 'image/png', 'image/webp']) {
        if ($file['error'] !== UPLOAD_ERR_OK) {
            return false;
        }
        
        // Verificar tipo MIME real
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $mime_type = finfo_file($finfo, $file['tmp_name']);
        finfo_close($finfo);
        
        if (!in_array($mime_type, $allowed_types)) {
            return false;
        }
        
        // Verificar extensión vs tipo MIME
        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        $expected_extensions = [
            'image/jpeg' => ['jpg', 'jpeg'],
            'image/png' => ['png'],
            'image/webp' => ['webp']
        ];
        
        if (!in_array($extension, $expected_extensions[$mime_type] ?? [])) {
            return false;
        }
        
        // Verificar que es una imagen válida
        if (strpos($mime_type, 'image/') === 0) {
            $image_info = getimagesize($file['tmp_name']);
            if (!$image_info) {
                return false;
            }
        }
        
        // Tamaño máximo: 10MB
        if ($file['size'] > 10 * 1024 * 1024) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Prevención de CSRF avanzada
     */
    public static function validateCSRFAdvanced($db) {
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $csrf_token = $_POST['csrf'] ?? '';
            
            if (empty($csrf_token) || $csrf_token !== ($_SESSION['csrf'] ?? '')) {
                self::logSecurityEvent($db, 'csrf_attempt', 
                    'Intento de CSRF detectado', null, 'high');
                
                http_response_code(419);
                die('Token de seguridad inválido. Por favor, recarga la página.');
            }
            
            // Rotar token después de uso válido
            $_SESSION['csrf'] = bin2hex(random_bytes(32));
        }
    }
    
    /**
     * Headers de seguridad HTTP
     */
    public static function setSecurityHeaders() {
        header('X-Content-Type-Options: nosniff');
        header('X-Frame-Options: DENY');
        header('X-XSS-Protection: 1; mode=block');
        header('Referrer-Policy: strict-origin-when-cross-origin');
        
        // CSP básico
        $csp = [
            "default-src 'self'",
            "script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com",
            "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com",
            "img-src 'self' data: https:",
            "connect-src 'self'",
            "frame-ancestors 'none'"
        ];
        
        header("Content-Security-Policy: " . implode('; ', $csp));
    }
}