<?php
class Security {
    private $max_attempts = 5;
    private $lockout_time = 900; // 15 minutos

    public function __construct() {
        session_start();
    }

    public function validateInput($data) {
        if (is_array($data)) {
            return array_map([$this, 'validateInput'], $data);
        }

        // Limpiar y validar entrada
        $data = trim($data);
        $data = stripslashes($data);
        $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');

        return $data;
    }

    public function checkBruteForce($identifier) {
        $attempts_key = 'login_attempts_' . $identifier;
        $lockout_key = 'lockout_time_' . $identifier;

        // Verificar si está bloqueado
        if (isset($_SESSION[$lockout_key]) && $_SESSION[$lockout_key] > time()) {
            $remaining = $_SESSION[$lockout_key] - time();
            return [
                'blocked' => true,
                'remaining_time' => $remaining,
                'message' => "Demasiados intentos fallidos. Intenta de nuevo en " . ceil($remaining / 60) . " minutos."
            ];
        }

        return ['blocked' => false];
    }

    public function recordFailedAttempt($identifier) {
        $attempts_key = 'login_attempts_' . $identifier;
        $lockout_key = 'lockout_time_' . $identifier;

        if (!isset($_SESSION[$attempts_key])) {
            $_SESSION[$attempts_key] = 0;
        }

        $_SESSION[$attempts_key]++;

        if ($_SESSION[$attempts_key] >= $this->max_attempts) {
            $_SESSION[$lockout_key] = time() + $this->lockout_time;
            $_SESSION[$attempts_key] = 0;
            return true; // Bloqueado
        }

        return false; // No bloqueado aún
    }

    public function clearFailedAttempts($identifier) {
        $attempts_key = 'login_attempts_' . $identifier;
        $lockout_key = 'lockout_time_' . $identifier;

        unset($_SESSION[$attempts_key]);
        unset($_SESSION[$lockout_key]);
    }

    public function validateSession($required_permissions = []) {
        // Primero intentar validar por token (API)
        $headers = getallheaders();
        $token = null;

        // Buscar token en headers
        if (isset($headers['Authorization'])) {
            $token = str_replace('Bearer ', '', $headers['Authorization']);
        }

        // Si hay token, validar por token
        if ($token) {
            return $this->validateToken($token, $required_permissions);
        }

        // Si no hay token, validar por sesión PHP tradicional
        if (!isset($_SESSION['user_id']) || !isset($_SESSION['session_token'])) {
            return ['valid' => false, 'message' => 'Sesión no válida'];
        }

        // Verificar timeout de sesión (30 minutos)
        if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > 1800)) {
            $this->destroySession();
            return ['valid' => false, 'message' => 'Sesión expirada'];
        }

        $_SESSION['last_activity'] = time();

        // Verificar permisos si se especifican
        if (!empty($required_permissions)) {
            foreach ($required_permissions as $permission) {
                if (!$this->hasPermission($_SESSION['user_id'], $permission)) {
                    return ['valid' => false, 'message' => 'Permisos insuficientes'];
                }
            }
        }

        return ['valid' => true];
    }

    public function validateToken($token, $required_permissions = []) {
        global $conn;

        // Buscar token en la BD
        $query = "SELECT s.*, u.rol_id FROM sesiones s
                 JOIN usuarios u ON s.usuario_id = u.id
                 WHERE s.session_token = ? AND s.activa = 1";
        $stmt = $conn->prepare($query);
        $stmt->bind_param("s", $token);
        $stmt->execute();
        $result = $stmt->get_result();

        if (!$session = $result->fetch_assoc()) {
            return ['valid' => false, 'message' => 'Token inválido'];
        }

        // Verificar expiración (30 minutos de inactividad)
        $last_activity = strtotime($session['fecha_actividad']);
        if (time() - $last_activity > 1800) {
            // Desactivar sesión expirada
            $updateQuery = "UPDATE sesiones SET activa = 0 WHERE id = ?";
            $updateStmt = $conn->prepare($updateQuery);
            $updateStmt->bind_param("i", $session['id']);
            $updateStmt->execute();

            return ['valid' => false, 'message' => 'Token expirado'];
        }

        // Actualizar última actividad
        $updateQuery = "UPDATE sesiones SET fecha_actividad = NOW() WHERE id = ?";
        $updateStmt = $conn->prepare($updateQuery);
        $updateStmt->bind_param("i", $session['id']);
        $updateStmt->execute();

        // Establecer datos de sesión para compatibilidad
        $_SESSION['user_id'] = $session['usuario_id'];
        $_SESSION['session_token'] = $token;
        $_SESSION['rol_id'] = $session['rol_id'];

        // Verificar permisos si se especifican
        if (!empty($required_permissions)) {
            foreach ($required_permissions as $permission) {
                if (!$this->hasPermission($session['usuario_id'], $permission)) {
                    return ['valid' => false, 'message' => 'Permisos insuficientes'];
                }
            }
        }

        return ['valid' => true, 'user_id' => $session['usuario_id']];
    }

    public function createSession($user_id, $user_data = []) {
        global $conn;

        session_regenerate_id(true);
        $session_token = bin2hex(random_bytes(32));

        $_SESSION['user_id'] = $user_id;
        $_SESSION['session_token'] = $session_token;
        $_SESSION['login_time'] = time();
        $_SESSION['last_activity'] = time();
        $_SESSION['user_data'] = $user_data;

        // Guardar token en la base de datos
        $ip_address = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';

        $query = "INSERT INTO sesiones (usuario_id, session_token, ip_address, user_agent, fecha_inicio, fecha_actividad, activa)
                 VALUES (?, ?, ?, ?, NOW(), NOW(), 1)";
        $stmt = $conn->prepare($query);
        $stmt->bind_param("isss", $user_id, $session_token, $ip_address, $user_agent);
        $stmt->execute();

        return $session_token;
    }

    public function destroySession() {
        global $conn;

        // Desactivar token en la BD si existe
        if (isset($_SESSION['session_token'])) {
            $query = "UPDATE sesiones SET activa = 0 WHERE session_token = ?";
            $stmt = $conn->prepare($query);
            $stmt->bind_param("s", $_SESSION['session_token']);
            $stmt->execute();
        }

        session_unset();
        session_destroy();
    }

    public function hasPermission($user_id, $permission) {
        global $conn;

        $query = "SELECT COUNT(*) as count FROM usuarios u
                  JOIN roles r ON u.rol_id = r.id
                  JOIN rol_permisos rp ON r.id = rp.rol_id
                  JOIN permisos p ON rp.permiso_id = p.id
                  WHERE u.id = ? AND p.nombre = ? AND u.activo = 1 AND r.activo = 1 AND p.activo = 1";

        $stmt = $conn->prepare($query);
        $stmt->bind_param("is", $user_id, $permission);
        $stmt->execute();
        $result = $stmt->get_result();
        $row = $result->fetch_assoc();

        return $row['count'] > 0;
    }

    public function logSecurityEvent($event_type, $user_id = null, $details = '') {
        global $conn;

        $ip_address = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';

        $query = "INSERT INTO log_seguridad (evento, usuario_id, ip_address, user_agent, detalles, fecha)
                  VALUES (?, ?, ?, ?, ?, NOW())";

        $stmt = $conn->prepare($query);
        $stmt->bind_param("sisss", $event_type, $user_id, $ip_address, $user_agent, $details);
        return $stmt->execute();
    }

    public function sanitizeFilename($filename) {
        // Remover caracteres peligrosos de nombres de archivo
        $filename = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename);
        return $filename;
    }

    public function validateFileUpload($file, $allowed_types = ['image/jpeg', 'image/png', 'image/gif'], $max_size = 5242880) {
        if (!isset($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
            return ['valid' => false, 'message' => 'Archivo no válido'];
        }

        if ($file['size'] > $max_size) {
            return ['valid' => false, 'message' => 'Archivo demasiado grande'];
        }

        $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 ['valid' => false, 'message' => 'Tipo de archivo no permitido'];
        }

        return ['valid' => true];
    }

    public function generateCSRFToken() {
        if (!isset($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }
        return $_SESSION['csrf_token'];
    }

    public function validateCSRFToken($token) {
        return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
    }
}
?>