<?php

namespace App\Services;


/**
 * Servicio de Seguridad
 * Maneja autenticación, tokens, encriptación y protección
 */
class SecurityService
{
    private Database $db;
    private Logger $logger;
    private string $encryptionKey;
    private string $jwtSecret;

    public function __construct()
    {
        $this->db = Database::getInstance();
        $this->logger = new Logger();
        $this->encryptionKey = $_ENV['ENCRYPTION_KEY'];
        $this->jwtSecret = $_ENV['JWT_SECRET'];
    }

    // ========================================================================
    // GENERACIÓN DE TOKENS
    // ========================================================================

    /**
     * Generar token único seguro
     */
    public function generateToken(int $length = 32): string
    {
        return bin2hex(random_bytes($length));
    }

    /**
     * Generar token CSRF
     */
    public function generateCSRFToken(): string
    {
        if (!isset($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = $this->generateToken(32);
        }
        return $_SESSION['csrf_token'];
    }

    /**
     * Verificar token CSRF
     */
    public function verifyCSRFToken(string $token): bool
    {
        if (!isset($_SESSION['csrf_token'])) {
            return false;
        }

        return hash_equals($_SESSION['csrf_token'], $token);
    }

    /**
     * Generar JWT (Simple implementation)
     */
    public function generateJWT(array $payload, int $expiresIn = 3600): string
    {
        $header = $this->base64UrlEncode(json_encode(['typ' => 'JWT', 'alg' => 'HS256']));

        $payload['iat'] = time();
        $payload['exp'] = time() + $expiresIn;
        $payloadEncoded = $this->base64UrlEncode(json_encode($payload));

        $signature = hash_hmac('sha256', "$header.$payloadEncoded", $this->jwtSecret, true);
        $signatureEncoded = $this->base64UrlEncode($signature);

        return "$header.$payloadEncoded.$signatureEncoded";
    }

    /**
     * Verificar y decodificar JWT
     */
    public function verifyJWT(string $token): ?array
    {
        $parts = explode('.', $token);
        if (count($parts) !== 3) {
            return null;
        }

        [$header, $payload, $signature] = $parts;

        // Verificar firma
        $validSignature = $this->base64UrlEncode(
            hash_hmac('sha256', "$header.$payload", $this->jwtSecret, true)
        );

        if (!hash_equals($signature, $validSignature)) {
            return null;
        }

        // Decodificar payload
        $payloadData = json_decode($this->base64UrlDecode($payload), true);

        // Verificar expiración
        if (isset($payloadData['exp']) && $payloadData['exp'] < time()) {
            return null;
        }

        return $payloadData;
    }

    private function base64UrlEncode(string $data): string
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    private function base64UrlDecode(string $data): string
    {
        return base64_decode(strtr($data, '-_', '+/'));
    }

    // ========================================================================
    // ENCRIPTACIÓN
    // ========================================================================

    /**
     * Encriptar datos sensibles
     */
    public function encrypt(string $data): string
    {
        $cipher = 'aes-256-cbc';
        $ivLength = openssl_cipher_iv_length($cipher);
        $iv = openssl_random_pseudo_bytes($ivLength);

        $encrypted = openssl_encrypt($data, $cipher, $this->encryptionKey, 0, $iv);

        return base64_encode($iv . $encrypted);
    }

    /**
     * Desencriptar datos
     */
    public function decrypt(string $data): string
    {
        $cipher = 'aes-256-cbc';
        $ivLength = openssl_cipher_iv_length($cipher);

        $decoded = base64_decode($data);
        $iv = substr($decoded, 0, $ivLength);
        $encrypted = substr($decoded, $ivLength);

        return openssl_decrypt($encrypted, $cipher, $this->encryptionKey, 0, $iv);
    }

    /**
     * Hash de password
     */
    public function hashPassword(string $password): string
    {
        return password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
    }

    /**
     * Verificar password
     */
    public function verifyPassword(string $password, string $hash): bool
    {
        return password_verify($password, $hash);
    }

    // ========================================================================
    // RATE LIMITING
    // ========================================================================

    /**
     * Verificar rate limit por IP
     */
    public function checkRateLimit(string $action, int $maxAttempts = 5, int $decayMinutes = 15): bool
    {
        $ip = $this->getClientIP();
        $key = "rate_limit:{$action}:{$ip}";

        // Buscar intentos recientes
        $cutoffTime = gmdate('Y-m-d H:i:s', time() - ($decayMinutes * 60));

        $count = $this->db->count(
            'access_logs',
            "ip_address = :ip AND accion = :action AND created_at > :cutoff",
            [':ip' => $ip, ':action' => $action, ':cutoff' => $cutoffTime]
        );

        if ($count >= $maxAttempts) {
            $this->logger->logSecurity(
                'rate_limit_exceeded',
                "Rate limit exceeded for action: {$action}",
                'warning',
                ['ip' => $ip, 'attempts' => $count]
            );
            return false;
        }

        return true;
    }

    /**
     * Registrar intento para rate limiting
     */
    public function recordAttempt(string $action): void
    {
        $this->logger->logSecurity($action, 'Attempt recorded', 'info');
    }

    // ========================================================================
    // VALIDACIÓN Y SANITIZACIÓN
    // ========================================================================

    /**
     * Validar email
     */
    public function validateEmail(string $email): bool
    {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

    /**
     * Sanitizar string
     */
    public function sanitizeString(string $string): string
    {
        return htmlspecialchars(strip_tags(trim($string)), ENT_QUOTES, 'UTF-8');
    }

    /**
     * Sanitizar email
     */
    public function sanitizeEmail(string $email): string
    {
        return filter_var(trim($email), FILTER_SANITIZE_EMAIL);
    }

    /**
     * Validar número de documento
     */
    public function validateDocumentNumber(string $number, string $type): bool
    {
        $number = trim($number);

        // Si el número está vacío, no es válido
        if (empty($number)) {
            return false;
        }

        // Pasaportes y documentos alfanuméricos: permitir letras y números
        $alphanumericTypes = ['PP', 'PAS', 'CE', 'DNI'];

        if (in_array($type, $alphanumericTypes)) {
            $clean = preg_replace('/[^A-Z0-9]/i', '', $number);
        } else {
            $clean = preg_replace('/[^0-9]/', '', $number);
        }

        // Si después de limpiar no queda nada, no es válido
        if (empty($clean)) {
            return false;
        }

        // Validación más flexible - solo verificar longitud mínima y máxima razonable
        switch ($type) {
            case 'CC': // Cédula Colombia - más flexible
                return strlen($clean) >= 4 && strlen($clean) <= 12;
            case 'TI': // Tarjeta de identidad
                return strlen($clean) >= 6 && strlen($clean) <= 12;
            case 'CE': // Cédula extranjería
                return strlen($clean) >= 4 && strlen($clean) <= 15;
            case 'PP':
            case 'PAS': // Pasaporte
                return strlen($clean) >= 3 && strlen($clean) <= 20;
            case 'RUT': // RUT
                return strlen($clean) >= 6 && strlen($clean) <= 15;
            case 'DNI': // DNI
                return strlen($clean) >= 4 && strlen($clean) <= 12;
            default:
                // Para cualquier otro tipo, ser muy flexible
                return strlen($clean) >= 3 && strlen($clean) <= 25;
        }
    }

    /**
     * Validar que una imagen sea válida
     */
    public function validateImage(string $base64Data): bool
    {
        if (empty($base64Data)) {
            return false;
        }

        // Limpiar espacios
        $base64Data = trim($base64Data);
        $base64Data = str_replace(' ', '', $base64Data);

        // Verificar que sea base64 válido
        if (!preg_match('/^data:image\/(png|jpg|jpeg);base64,/i', $base64Data)) {
            return false;
        }

        // Obtener datos sin header
        $imageData = preg_replace('/^data:image\/\w+;base64,/i', '', $base64Data);
        $decoded = \base64_decode($imageData, true);

        if ($decoded === false) {
            return false;
        }

        // Verificar tamaño (máximo 10MB)
        $maxSize = (int) ($_ENV['MAX_FILE_SIZE'] ?? 10485760);
        if (strlen($decoded) > $maxSize) {
            return false;
        }

        // Verificar que tenga contenido mínimo
        if (strlen($decoded) < 100) {
            return false;
        }

        return true;
    }

    /**
     * Validar firma en canvas (no debe estar vacía)
     */
    public function validateSignature(string $base64Data): bool
    {
        // Validación básica: verificar que exista
        if (empty($base64Data)) {
            return false;
        }

        // Limpiar espacios en blanco
        $base64Data = trim($base64Data);
        $base64Data = str_replace(' ', '', $base64Data);

        // Verificar formato data:image (con o sin espacios)
        if (!preg_match('/^data:image\/(png|jpeg|jpg);base64,/i', $base64Data)) {
            return false;
        }

        // Extraer datos base64
        $imageData = preg_replace('/^data:image\/\w+;base64,/i', '', $base64Data);
        $decoded = \base64_decode($imageData, true);

        if ($decoded === false) {
            return false;
        }

        // Verificar tamaño mínimo (una firma debe tener al menos 500 bytes)
        if (strlen($decoded) < 500) {
            return false;
        }

        return true;
    }

    // ========================================================================
    // PROTECCIÓN XSS
    // ========================================================================

    /**
     * Limpiar HTML peligroso (compatible con Quill y editores ricos)
     */
    public function cleanHTML(string $html): string
    {
        // Tags estructurales y de formato permitidos (Quill, TinyMCE, etc.)
        $allowedTags = implode('', [
            '<p>', '<br>', '<strong>', '<b>', '<em>', '<i>', '<u>', '<s>', '<strike>',
            '<ol>', '<ul>', '<li>',
            '<h1>', '<h2>', '<h3>', '<h4>', '<h5>', '<h6>',
            '<blockquote>', '<pre>', '<code>',
            '<span>', '<div>',
            '<table>', '<thead>', '<tbody>', '<tr>', '<th>', '<td>',
            '<a>', '<hr>',
        ]);

        // Strip tags fuera de la lista blanca
        $clean = strip_tags($html, $allowedTags);

        // Eliminar atributos peligrosos (on*, javascript:, etc.)
        // Permitir solo: class, style, href (sin js:), align, colspan, rowspan
        $clean = preg_replace('/\s+on\w+\s*=\s*["\'][^"\']*["\']/i', '', $clean);
        $clean = preg_replace('/\s+on\w+\s*=\s*[^\s>]*/i', '', $clean);
        $clean = preg_replace('/href\s*=\s*["\']?\s*javascript:[^"\'>\s]*/i', 'href="#"', $clean);

        return $clean;
    }

    // ========================================================================
    // UTILIDADES
    // ========================================================================

    /**
     * Obtener IP del cliente
     */
    public function getClientIP(): string
    {
        $headers = [
            'HTTP_CF_CONNECTING_IP', // Cloudflare
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'REMOTE_ADDR'
        ];

        foreach ($headers as $header) {
            if (!empty($_SERVER[$header])) {
                $ip = $_SERVER[$header];
                // Si hay múltiples IPs, tomar la primera
                if (strpos($ip, ',') !== false) {
                    $ip = trim(explode(',', $ip)[0]);
                }
                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    return $ip;
                }
            }
        }

        return $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
    }

    /**
     * Obtener User Agent
     */
    public function getUserAgent(): string
    {
        return $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
    }

    /**
     * Generar hash de archivo
     */
    public function hashFile(string $filepath): string
    {
        return hash_file('sha256', $filepath);
    }

    /**
     * Verificar si una solicitud es HTTPS
     */
    public function isSecureConnection(): bool
    {
        return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
            || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
            || (!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on');
    }

    /**
     * Forzar HTTPS (solo en producción)
     */
    public function forceHTTPS(): void
    {
        if ($_ENV['APP_ENV'] === 'production' && !$this->isSecureConnection()) {
            $url = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
            header('Location: ' . $url, true, 301);
            exit;
        }
    }
}
