<?php
/**
 * Firewall Bootstrap Class
 * 
 * Initializes and runs the firewall checks on each request
 * 
 * @since 2.1.0
 * @since 2.2.0 - Added country blocking checks and custom pattern blocking
 * @since 2.2.2 - Enhanced admin bypass logic and redirect parameter validation
 * @since 2.2.4 - Added enhanced Facebook user traffic detection and logging
 * @package VMPFence
 * @subpackage Firewall
 */

if (!defined('ABSPATH')) exit;

// Load Live Traffic Logger and dependencies for logging WAF events
if (!class_exists('VMPFence_Request_Model')) {
    require_once VMPFENCE_PLUGIN_DIR . 'tools/models/class-request-model.php';
}
if (!class_exists('VMPFence_Live_Traffic_Logger')) {
    require_once VMPFENCE_PLUGIN_DIR . 'tools/models/class-live-traffic-logger.php';
}

class VMPFence_Firewall_Bootstrap {
    
    private $config;
    private $request;
    
    public function __construct() {
        $this->config = new VMPFence_Firewall_Config();
    }
    
    public function init() {
        if (!$this->config->is_feature_enabled('firewall_enabled')) {
            return;
        }
        
        $this->parse_request();
        
        // PRIORITY 0: Allow logged-in administrators BEFORE any security checks
        // This prevents admin lockouts from IP blocking or other automated blocks
        // WordPress still enforces its own nonce verification and capability checks
        if ($this->is_logged_in_admin()) {
            // SECURITY: Never log admin bypasses in production - exposes attack surface
            // Only log in development/staging environments to prevent information disclosure
            if (!VMPFence_Secure_Logger::is_production() && VMPFence_Config::get('debug_mode', false)) {
                VMPFence_Secure_Logger::debug('Firewall bypass for logged-in admin', [
                    'user_id' => get_current_user_id(),
                    'uri' => parse_url($this->request['uri'], PHP_URL_PATH)
                ]);
            }
            return;
        }
        
        // PRIORITY 1: Check whitelists, blocked IPs, and country blocking
        // These apply to non-admin users including unauthenticated wp-login.php access
        try {
            $this->check_whitelist();
            $this->check_blocked_ips(); // Check blocked IPs
            $this->check_country(); // Check country blocking (uses blocked_countries table)
        } catch (VMPFence_Allow_Exception $e) {
            // Request is whitelisted - allow immediately
            $this->log_event('allow', 'Request whitelisted', $e);
            return;
        } catch (VMPFence_Block_Exception $e) {
            // Country or IP blocked - handle immediately with proper 403 page
            if (isset($e->country_code)) {
                $this->log_event('block_country', 'Country block: ' . $e->country_name, $e);
                $this->block_country_request($e);
            } else {
                $this->log_event('block_ip', 'IP block', $e);
                $this->block_request($e);
            }
        }
        
        // PRIORITY 2: Skip remaining firewall checks for admin-ajax.php and admin area
        // (This catches admin requests where user might not be fully authenticated yet)
        if ($this->is_admin_request()) {
            // SECURITY: Never log admin bypasses in production - exposes attack surface
            // Only log in development/staging environments to prevent information disclosure
            if (!VMPFence_Secure_Logger::is_production() && VMPFence_Config::get('debug_mode', false)) {
                VMPFence_Secure_Logger::debug('Firewall bypass for admin request', ['uri' => parse_url($this->request['uri'], PHP_URL_PATH)]);
            }
            return;
        }
        try {
            $this->check_custom_patterns(); // Check custom pattern blocking
            $this->check_blocklist();
            $this->check_brute_force();
            $this->check_rate_limit();
            $this->check_blocking_rules();
            $this->check_waf_rules();
            
        } catch (VMPFence_Allow_Exception $e) {
            // Request is whitelisted - allow immediately
            $this->log_event('allow', 'Request whitelisted', $e);
            return;
            
        } catch (VMPFence_Block_XSS_Exception $e) {
            // XSS attack detected
            $this->log_event('block_xss', 'WAF block: XSS', $e);
            $this->block_xss_request($e);
            
        } catch (VMPFence_Block_SQLi_Exception $e) {
            // SQLi attack detected
            $this->log_event('block_sqli', 'WAF block: SQL Injection', $e);
            $this->block_sqli_request($e);
            
        } catch (VMPFence_Block_Exception $e) {
            // Check if this is a country block (has country_code property)
            if (isset($e->country_code)) {
                $this->log_event('block_country', 'Country block: ' . $e->country_name, $e);
                $this->block_country_request($e);
            } else {
                // Generic WAF block
                $this->log_event('block', 'WAF block: Rule violation', $e);
                $this->block_request($e);
            }
            
        } catch (VMPFence_Pattern_Block_Exception $e) {
            // Pattern blocking exception
            $this->log_event('block', 'Pattern block: ' . $e->get_pattern_type(), $e);
            $this->block_pattern_request($e);
        }
    }
    
    private function parse_request() {
        $uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
        $path = parse_url($uri, PHP_URL_PATH);
        
        // DEFENSIVE: Handle parse_url returning false on malformed URLs
        if ($path === false || $path === null) {
            $path = '/';
        }
        
        $this->request = array(
            'ip' => VMPFence_Utils::get_real_ip(),
            'uri' => $uri,
            'path' => $path,
            'query_string' => isset($_SERVER['QUERY_STRING']) ? wp_unslash($_SERVER['QUERY_STRING']) : '',  // Raw query string (not URL decoded)
            'method' => isset($_SERVER['REQUEST_METHOD']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'])) : 'GET',
            'params' => array_merge($_GET, $_POST),  // <-- Add this for WAF
            'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? wp_unslash($_SERVER['HTTP_USER_AGENT']) : '',  // Don't sanitize - preserve for pattern matching
            'referer' => isset($_SERVER['HTTP_REFERER']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'])) : ''
        );
    }
    
    /**
     * Check if the current request is from a logged-in WordPress administrator
     * This allows authenticated admins to bypass WAF for legitimate admin functions
     * 
     * @return bool True if logged-in admin with manage_options capability or super admin
     */
    private function is_logged_in_admin() {
        // Early check: If WordPress core not loaded yet (before plugins_loaded hook)
        // Return false to defer admin bypass until WordPress is ready
        if (!did_action('plugins_loaded')) {
            return false;
        }
        
        // Check 1: WordPress auth cookie must exist
        if (!defined('LOGGED_IN_COOKIE') || !isset($_COOKIE[LOGGED_IN_COOKIE])) {
            return false;
        }
        
        // Check 2: Validate auth cookie if WordPress functions are available
        if (function_exists('wp_validate_auth_cookie')) {
            $user_id = wp_validate_auth_cookie('', 'logged_in');
            if (!$user_id) {
                return false;
            }
            
            // Check 3a: Multisite super admin (has network-wide privileges)
            if (function_exists('is_multisite') && function_exists('is_super_admin')) {
                if (is_multisite() && is_super_admin($user_id)) {
                    return true;
                }
            }
            
            // Check 3b: Single-site admin or subsite admin with manage_options
            if (function_exists('user_can')) {
                return user_can($user_id, 'manage_options');
            }
            
            // Fallback: Check via WP_User object
            $user = new WP_User($user_id);
            return $user->has_cap('manage_options');
        }
        
        // Check 3: Fallback to standard WordPress functions
        if (function_exists('is_user_logged_in') && function_exists('current_user_can')) {
            // Check super admin first in multisite
            if (function_exists('is_multisite') && function_exists('is_super_admin')) {
                if (is_multisite() && is_super_admin()) {
                    return true;
                }
            }
            return is_user_logged_in() && current_user_can('manage_options');
        }
        
        // If WordPress functions not loaded yet, return false (safer to run WAF)
        return false;
    }
    
    /**
     * Check if the current request is an admin/AJAX request that should bypass firewall
     * Note: Logged-in admin check is now done in init() before this method is called
     *
     * @return bool True if admin request
     */
    private function is_admin_request() {
        $uri = $this->request['uri'];
        $path = $this->request['path']; // Use path only (no query string)
        $user_agent = $this->request['user_agent'];

        // ALWAYS ALLOW - Safe internal WordPress operations that must bypass:
        // WP-CLI, wp-cron, admin-ajax, REST API, AJAX constant, is_admin() context
        
        if ((defined('WP_CLI') && WP_CLI) ||
            strpos($path, 'admin-ajax.php') !== false ||
            strpos($path, 'wp-cron.php') !== false ||
            strpos($path, '/wp-json/') !== false ||
            (defined('DOING_AJAX') && DOING_AJAX) ||
            (function_exists('is_admin') && is_admin())) {
            
            if (!VMPFence_Secure_Logger::is_production() && VMPFence_Config::get('debug_mode', false)) {
                VMPFence_Secure_Logger::debug('Firewall bypass for safe admin operation', ['uri' => $path]);
            }
            return true;
        }
        
        // CONDITIONAL ALLOW for /wp-admin/ and /wp-login.php paths:
        // Only bypass firewall for legitimate browsers (not bots/curl/automation)
        
        if (strpos($path, 'wp-admin') !== false ||
            strpos($path, 'wp-login.php') !== false ||
            strpos($path, '/admin/') !== false) {
            
            // Use existing visitor classifier (already loaded by vmpfence.php)
            if (class_exists('VMPFence_Visitor_Classifier')) {
                try {
                    if (VMPFence_Visitor_Classifier::is_human($user_agent)) {
                        // Legitimate browser - allow admin access
                        // NOTE: Brute force protection still enforced via WordPress authenticate hooks
                        if (!VMPFence_Secure_Logger::is_production() && VMPFence_Config::get('debug_mode', false)) {
                            VMPFence_Secure_Logger::debug('Firewall bypass for legitimate browser on admin path', ['uri' => $path]);
                        }
                        return true;
                    }
                } catch (Exception $e) {
                    // If classifier fails, treat as bot (enforce WAF) - fail secure
                    if (class_exists('VMPFence_Secure_Logger')) {
                        VMPFence_Secure_Logger::error('Visitor classifier exception in admin bypass', ['error' => $e->getMessage()]);
                    }
                }
            }
            
            // Bot/curl/automation - enforce ALL security checks (WAF rules, rate limit, custom patterns)
            return false;
        }
        
        return false;
    }
    
    /**
     * 
     * @since 2.0.0
     * @since 2.2.4 Added enhanced Facebook user traffic detection (not just crawler)
     * 
     */
    private function check_whitelist() {
        $current_ip = $this->request['ip'];
        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
        
        // PRIORITY 1: Check IP Whitelist (whitelisted_ips_bypass setting)
        // This allows specific IPs to bypass ALL firewall rules
        if (class_exists('VMPFence_IP_Whitelist_Controller')) {
            if (VMPFence_IP_Whitelist_Controller::is_whitelisted($current_ip)) {
                $e = new VMPFence_Allow_Exception('IP whitelisted via IP whitelist');
                throw $e;
            }
        }
        
        // PRIORITY 2: Check Service Allowlist (whitelisted_services setting)
        // This allows trusted services (Google, Facebook crawlers/users, Uptime monitors, etc.) to bypass firewall rules
        // Facebook user traffic (fbclid, utm_source, app UA, referrer) is now handled inside the service controller
        if (class_exists('VMPFence_Service_Allowlist_Controller')) {
            if (VMPFence_Service_Allowlist_Controller::is_allowlisted_service($current_ip, $user_agent)) {
                $e = new VMPFence_Allow_Exception('Allowlisted service: ' . $user_agent);
                throw $e;
            }
        }
        
        // PRIORITY 3: Check private IP ranges (only if enabled)
        // Allow all private IP ranges for development
        $whitelist_private_ips = $this->config->get_setting('whitelist_private_ips');
        if ($whitelist_private_ips == '1') {
            // Only whitelist if enabled (for production use)
            $whitelisted_ips = array(
                '127.0.0.1',
                '::1',
            );
            
            // Check exact match
            if (in_array($current_ip, $whitelisted_ips)) {
                $e = new VMPFence_Allow_Exception('Localhost IP whitelisted');
                throw $e;
            }
            
            // Check private IP ranges (192.168.x.x, 10.x.x.x, 172.16-31.x.x)
            if ($this->is_private_ip($current_ip)) {
                $e = new VMPFence_Allow_Exception('Private network IP whitelisted');
                throw $e;
            }
        }
        
        return false;
    }
    
    /**
     * Check if IP is in private network range
     *
     * @param string $ip IP address to check
     * @return bool True if private IP
     */
    private function is_private_ip($ip) {
        // Validate IP
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
            return false;
        }
        
        // Check if it's a private IP range
        return !filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
        );
    }
    
    private function check_blocked_ips() {
        // Check if IP is in blocked list
        if (class_exists('VMPFence_Blocked_IP')) {
            if (VMPFence_Blocked_IP::is_blocked($this->request['ip'])) {
                // Update last attempt time for the most recent active block only
                global $wpdb;
                $table = $wpdb->prefix . VMPFence_Constants::TABLE_BLOCKED_IPS;
                
                // Get the most recent active block ID for this IP
                $latest_id = $wpdb->get_var($wpdb->prepare(
                    "SELECT id FROM {$table} WHERE ip_address = %s AND is_active = 1 ORDER BY id DESC LIMIT 1",
                    $this->request['ip']
                ));
                
                // Update only the latest record
                if ($latest_id) {
                    $wpdb->query($wpdb->prepare(
                        "UPDATE {$table} SET last_attempt = %s WHERE id = %d",
                        current_time('mysql', 1),
                        $latest_id
                    ));
                }
                
                // Post-hoc pattern check: Increment match counts for any patterns that match this request
                // This ensures pattern analytics reflect all blocks, including manually blocked IPs
                if (class_exists('VMPFence_Pattern_Matcher')) {
                    VMPFence_Pattern_Matcher::increment_matching_patterns($this->request);
                }
                
                // Get block details for logging
                $block_details = VMPFence_Blocked_IP::get_blocked_ip_details($this->request['ip']);
                $reason = isset($block_details['reason']) ? $block_details['reason'] : 'IP address is blocked';
                
                // Throw block exception (with silent flag to prevent event logging)
                // Already-blocked IPs don't need to log events on every request
                $e = new VMPFence_Block_Exception($reason);
                $e->set_silent(true); // Mark as silent to skip logging in log() method
                throw $e;
            }
        }
        return false;
    }
    
    private function check_blocklist() {
        return false;
    }
    
    private function check_brute_force() {
        return false;
    }
    
    private function check_rate_limit() {
        return false;
    }
    
    private function check_country() {
        // Check country-based blocking using wp_vmpfence_blocked_countries table
        if (class_exists('VMPFence_Country_Blocker')) {
            // Pass IP to country blocker
            // Throws VMPFence_Block_Exception if country is blocked
            VMPFence_Country_Blocker::check_visitor($this->request['ip']);
        }
        return false;
    }
    
    /**
     * Check custom pattern blocking (hostname, user_agent, referrer, ip_range)
     * Throws VMPFence_Pattern_Block_Exception if pattern matches
     * @since 2.2.0
     */
    private function check_custom_patterns() {
        if (!$this->config->is_feature_enabled('custom_patterns_enabled')) {
            return false;
        }
        
        if (class_exists('VMPFence_Pattern_Matcher')) {
            // Will throw VMPFence_Pattern_Block_Exception if blocked
            VMPFence_Pattern_Matcher::check($this->request);
        }
        
        return false;
    }
    
    private function check_blocking_rules() {
        return false;
    }
    
    private function check_waf_rules() {
        if (!$this->config->is_feature_enabled('waf_enabled')) {
            return false;
        }

        $uri = $this->request['uri'];
        $path = $this->request['path'];
        
        // 🛡️ REDIRECT PARAMETER VALIDATION - MUST RUN BEFORE ANY SKIPS
        // Validate redirect parameters across ALL WordPress pages to prevent:
        // 1. Path traversal attacks (../ in any encoding, including triple-encoding)
        // 2. Open redirect attacks (external domains)
        // 3. Protocol-relative URL attacks (//evil.com)
        // 4. Dangerous protocol attacks (javascript:, data:, etc)
        // 5. Array injection attacks (using ?param[]=value)
        
        // Initialize Live Traffic logger once to avoid race conditions
        $traffic_logger = null;
        if (class_exists('VMPFence_Live_Traffic_Logger')) {
            $traffic_logger = VMPFence_Live_Traffic_Logger::getInstance();
            if (method_exists($traffic_logger, 'initLogRequest')) {
                $traffic_logger->initLogRequest();
            }
        }
        
        // SKIP REDIRECT VALIDATION for legitimate human browsers on admin paths
        // They're logging in/out normally and don't need aggressive redirect checks
        $is_human_on_admin = false;
        if ((strpos($path, 'wp-admin') !== false || strpos($path, 'wp-login.php') !== false) &&
            class_exists('VMPFence_Visitor_Classifier')) {
            try {
                $is_human_on_admin = VMPFence_Visitor_Classifier::is_human($this->request['user_agent']);
            } catch (Exception $e) {
                // If classifier fails, treat as bot (don't skip validation) - fail secure
                $is_human_on_admin = false;
                if (class_exists('VMPFence_Secure_Logger')) {
                    VMPFence_Secure_Logger::error('Visitor classifier exception in redirect validation', ['error' => $e->getMessage()]);
                }
            }
        }
        
        if ($is_human_on_admin) {
            // Skip redirect validation for human browsers on admin - proceed to normal WAF bypass check
            // This prevents false positives on legitimate logout/login redirects
        } else {
        
        $redirect_params = array('redirect', 'redirect_to', 'return', 'returnto', 'url', 'next');
        foreach ($redirect_params as $param) {
            if (isset($_GET[$param]) || isset($_POST[$param])) {
                $redirect_value = $_GET[$param] ?? $_POST[$param];
                
                // SECURITY: Reject array parameters (array injection attack)
                // Example attack: ?redirect[0]=../wp-admin/ bypasses string checks
                if (is_array($redirect_value)) {
                    if (class_exists('VMPFence_Secure_Logger')) {
                        VMPFence_Secure_Logger::warning('Array injection in redirect parameter blocked', array(
                            'parameter' => $param,
                            'ip' => $this->request['ip']
                        ));
                    }
                    
                    if ($traffic_logger) {
                        $traffic_logger->setAction('blocked:array-injection', 'Array injection in redirect parameter', array(
                            'parameter' => $param
                        ));
                        $traffic_logger->setStatusCode(403);
                    }
                    
                    $e = new VMPFence_Block_Exception('Array injection in redirect parameter detected');
                    $e->set_violated_rules(array(array(
                        'rule_id' => 'builtin-array-injection',
                        'description' => 'Array parameter injection attempt',
                        'pattern' => 'Array injection'
                    )));
                    throw $e;
                }
                
                // Skip empty strings explicitly (more precise than empty())
                // Note: is_string() check is redundant since we already blocked arrays
                if (!is_string($redirect_value) || strlen($redirect_value) === 0) {
                    continue;
                }
                
                // SECURITY: Block null byte injection (%00 or \x00)
                // Null bytes can truncate paths or bypass filters in some contexts
                if (strpos($redirect_value, "\0") !== false || strpos($redirect_value, '%00') !== false) {
                    if (class_exists('VMPFence_Secure_Logger')) {
                        VMPFence_Secure_Logger::warning('Null byte injection in redirect blocked', array(
                            'parameter' => $param,
                            'ip' => $this->request['ip']
                        ));
                    }
                    
                    if ($traffic_logger) {
                        $traffic_logger->setAction('blocked:null-byte', 'Null byte injection in redirect', array(
                            'parameter' => $param
                        ));
                        $traffic_logger->setStatusCode(403);
                    }
                    
                    $e = new VMPFence_Block_Exception('Null byte injection in redirect parameter detected');
                    $e->set_violated_rules(array(array(
                        'rule_id' => 'builtin-null-byte',
                        'description' => 'Null byte (\\x00 or %00) in redirect parameter',
                        'pattern' => 'Null byte injection'
                    )));
                    throw $e;
                }
                
                // Recursively URL-decode to catch triple/quadruple encoding attacks
                // Attacker: %25252e%25252e%25252f (triple-encoded ../)
                // After this: ../ (fully decoded)
                $decoded_value = $redirect_value;
                $decode_iterations = 0;
                $max_decode_iterations = 5; // Prevent infinite loop on malformed input
                
                while ($decode_iterations < $max_decode_iterations) {
                    $previous = $decoded_value;
                    $decoded_value = urldecode($decoded_value);
                    $decode_iterations++;
                    
                    // Stop if no more changes (fully decoded)
                    if ($previous === $decoded_value) {
                        break;
                    }
                }
                
                // 1. Block path traversal patterns (../ or ..\) in ANY form
                // Check both original and fully-decoded to catch all encoding levels
                // Pattern covers: ../, ..\, ..;, and various encoded forms
                if (preg_match('/\\.\\.\\/|\\.\\.\\\\/i', $redirect_value) || 
                    preg_match('/\\.\\.\\/|\\.\\.\\\\/i', $decoded_value) ||
                    preg_match('/\\.\\.;|\\.\\.%2f|\\.\\.%2F|\\.\\.%5c|\\.\\.%5C|\\.\\.%c0%af|\\.\\.%C0%AF/i', $redirect_value) ||
                    preg_match('/\\.{3,}[\\/\\\\]/i', $redirect_value) ||
                    preg_match('/\\.{3,}[\\/\\\\]/i', $decoded_value)) {
                    
                    if (class_exists('VMPFence_Secure_Logger')) {
                        VMPFence_Secure_Logger::warning('Path traversal in redirect parameter blocked', array(
                            'parameter' => $param,
                            'value' => substr($redirect_value, 0, 100),
                            'decoded' => substr($decoded_value, 0, 100),
                            'ip' => $this->request['ip']
                        ));
                    }
                    
                    if ($traffic_logger) {
                        $traffic_logger->setAction('blocked:redirect-traversal', 'Path traversal in redirect parameter', array(
                            'parameter' => $param,
                            'pattern' => 'Path traversal detected'
                        ));
                        $traffic_logger->setStatusCode(403);
                    }
                    
                    $e = new VMPFence_Block_Exception('Path traversal in redirect parameter detected');
                    $e->set_violated_rules(array(array(
                        'rule_id' => 'builtin-redirect-traversal',
                        'description' => 'Path traversal pattern (../) in redirect parameter',
                        'pattern' => 'Path traversal'
                    )));
                    throw $e;
                }
                
                // 2. Block dangerous protocols (javascript:, data:, vbscript:, file:, php:, jar:, etc)
                // Added: php (PHP wrapper), jar (Java), tel/sms (mobile), intent (Android), view-source (Firefox)
                if (preg_match('/^(javascript|data|vbscript|file|about|php|jar|tel|sms|intent|view-source):/i', $redirect_value)) {
                    if (class_exists('VMPFence_Secure_Logger')) {
                        VMPFence_Secure_Logger::warning('Dangerous protocol in redirect parameter blocked', array(
                            'parameter' => $param,
                            'value' => substr($redirect_value, 0, 100),
                            'ip' => $this->request['ip']
                        ));
                    }
                    
                    if ($traffic_logger) {
                        $traffic_logger->setAction('blocked:dangerous-protocol', 'Dangerous protocol in redirect', array(
                            'parameter' => $param
                        ));
                        $traffic_logger->setStatusCode(403);
                    }
                    
                    $e = new VMPFence_Block_Exception('Dangerous protocol in redirect parameter detected');
                    $e->set_violated_rules(array(array(
                        'rule_id' => 'builtin-dangerous-protocol',
                        'description' => 'Dangerous protocol (javascript:, data:, etc) in redirect',
                        'pattern' => 'Dangerous protocol'
                    )));
                    throw $e;
                }
                
                // 3. Check protocol-relative URLs (//evil.com) or full URLs
                if (preg_match('/^\\/\\/|^https?:\\/\\//i', $redirect_value)) {
                    // Parse URL with error handling
                    $parsed = parse_url($redirect_value);
                    
                    // Handle malformed URLs
                    if ($parsed === false || !is_array($parsed)) {
                        if (class_exists('VMPFence_Secure_Logger')) {
                            VMPFence_Secure_Logger::warning('Malformed URL in redirect parameter blocked', array(
                                'parameter' => $param,
                                'value' => substr($redirect_value, 0, 100),
                                'ip' => $this->request['ip']
                            ));
                        }
                        
                        if ($traffic_logger) {
                            $traffic_logger->setAction('blocked:malformed-redirect', 'Malformed URL in redirect', array(
                                'parameter' => $param
                            ));
                            $traffic_logger->setStatusCode(403);
                        }
                        
                        $e = new VMPFence_Block_Exception('Malformed URL in redirect parameter detected');
                        $e->set_violated_rules(array(array(
                            'rule_id' => 'builtin-malformed-redirect',
                            'description' => 'Malformed URL in redirect parameter',
                            'pattern' => 'Malformed URL'
                        )));
                        throw $e;
                    }
                    
                    // Special case: Protocol-relative URL with no host (//)
                    // parse_url('//') returns array('path' => '//') with no 'host' key
                    // This is suspicious and should be blocked
                    if (preg_match('/^\\/\\//', $redirect_value) && !isset($parsed['host'])) {
                        if (class_exists('VMPFence_Secure_Logger')) {
                            VMPFence_Secure_Logger::warning('Invalid protocol-relative URL blocked', array(
                                'parameter' => $param,
                                'value' => substr($redirect_value, 0, 100),
                                'ip' => $this->request['ip']
                            ));
                        }
                        
                        if ($traffic_logger) {
                            $traffic_logger->setAction('blocked:invalid-protocol-relative', 'Invalid protocol-relative URL', array(
                                'parameter' => $param
                            ));
                            $traffic_logger->setStatusCode(403);
                        }
                        
                        $e = new VMPFence_Block_Exception('Invalid protocol-relative URL in redirect parameter');
                        $e->set_violated_rules(array(array(
                            'rule_id' => 'builtin-invalid-protocol-relative',
                            'description' => 'Protocol-relative URL without valid host',
                            'pattern' => 'Invalid protocol-relative URL'
                        )));
                        throw $e;
                    }
                    
                    // Validate same-origin (block external domains)
                    // SECURITY: Strip port from HTTP_HOST for comparison (site.com:8080 -> site.com)
                    // SECURITY: Use SERVER_NAME as fallback if HTTP_HOST is missing/untrusted
                    $current_host_with_port = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '';
                    
                    // SECURITY: Block if HTTP_HOST is empty (prevents bypass via missing Host header)
                    if (empty($current_host_with_port)) {
                        if (class_exists('VMPFence_Secure_Logger')) {
                            VMPFence_Secure_Logger::warning('Redirect with missing HTTP_HOST blocked', array(
                                'parameter' => $param,
                                'value' => substr($redirect_value, 0, 100),
                                'ip' => $this->request['ip']
                            ));
                        }
                        
                        if ($traffic_logger) {
                            $traffic_logger->setAction('blocked:missing-host', 'Redirect without HTTP_HOST header', array(
                                'parameter' => $param
                            ));
                            $traffic_logger->setStatusCode(403);
                        }
                        
                        $e = new VMPFence_Block_Exception('Redirect validation failed: missing host information');
                        $e->set_violated_rules(array(array(
                            'rule_id' => 'builtin-missing-host',
                            'description' => 'HTTP_HOST header missing during redirect validation',
                            'pattern' => 'Missing host'
                        )));
                        throw $e;
                    }
                    
                    $current_host = preg_replace('/:\d+$/', '', $current_host_with_port);
                    
                    if (isset($parsed['host']) && !empty($parsed['host'])) {
                        $target_host = $parsed['host'];
                        
                        // SECURITY: Block credential injection (http://yoursite.com@evil.com)
                        // The @ character allows attacker to disguise external domain
                        if (strpos($redirect_value, '@') !== false) {
                            if (class_exists('VMPFence_Secure_Logger')) {
                                VMPFence_Secure_Logger::warning('Credential injection in redirect blocked', array(
                                    'parameter' => $param,
                                    'value' => substr($redirect_value, 0, 100),
                                    'ip' => $this->request['ip']
                                ));
                            }
                            
                            if ($traffic_logger) {
                                $traffic_logger->setAction('blocked:credential-injection', 'Credential injection in redirect URL', array(
                                    'parameter' => $param
                                ));
                                $traffic_logger->setStatusCode(403);
                            }
                            
                            $e = new VMPFence_Block_Exception('Credential injection in redirect URL detected');
                            $e->set_violated_rules(array(array(
                                'rule_id' => 'builtin-credential-injection',
                                'description' => 'URL contains @ character (credential injection)',
                                'pattern' => 'Credential injection'
                            )));
                            throw $e;
                        }
                        
                        // SECURITY: Detect IDN homograph attacks (punycode spoofing)
                        // Example: аррӏе.com (Cyrillic) looks like apple.com but is xn--80ak6aa92e.com
                        // Block any domain with 'xn--' (punycode) that differs from current site
                        if (strpos($target_host, 'xn--') !== false || preg_match('/[^\x00-\x7F]/', $target_host)) {
                            if (class_exists('VMPFence_Secure_Logger')) {
                                VMPFence_Secure_Logger::warning('IDN homograph attack in redirect blocked', array(
                                    'parameter' => $param,
                                    'target_host' => substr($target_host, 0, 100),
                                    'ip' => $this->request['ip']
                                ));
                            }
                            
                            if ($traffic_logger) {
                                $traffic_logger->setAction('blocked:idn-homograph', 'IDN homograph attack in redirect', array(
                                    'parameter' => $param
                                ));
                                $traffic_logger->setStatusCode(403);
                            }
                            
                            $e = new VMPFence_Block_Exception('IDN homograph attack in redirect URL detected');
                            $e->set_violated_rules(array(array(
                                'rule_id' => 'builtin-idn-homograph',
                                'description' => 'Punycode or non-ASCII domain in redirect',
                                'pattern' => 'IDN homograph'
                            )));
                            throw $e;
                        }
                        
                        // SECURITY: Normalize target host (strip port for comparison)
                        // parse_url() may include port in host or separate 'port' key
                        $target_host_normalized = preg_replace('/:\d+$/', '', $target_host);
                        
                        // SECURITY: Block SSRF to localhost/internal IPs (even if different from current_host)
                        // Protect against: 127.0.0.1, localhost, 192.168.x.x, 10.x.x.x, 169.254.x.x, [::1]
                        // SMART BYPASS: Allow internal IPs in development environments or for logged-in admins
                        $is_internal_ip = false;
                        
                        // Detect if we're in a development environment by checking if server is on private network
                        $server_ip = $_SERVER['SERVER_ADDR'] ?? $_SERVER['LOCAL_ADDR'] ?? '127.0.0.1';
                        $is_dev_environment = $this->is_private_ip($server_ip) || 
                                            in_array($server_ip, array('127.0.0.1', '::1')) ||
                                            strpos($_SERVER['HTTP_HOST'] ?? '', 'localhost') !== false;
                        
                        // Skip SSRF internal IP blocking if:
                        // 1. Development environment (server on private network)
                        // 2. User is logged-in admin (they can access wp-admin legitimately)
                        $skip_ssrf_check = $is_dev_environment || $this->is_logged_in_admin();
                        
                        if (!$skip_ssrf_check) {
                            // PRODUCTION: Block internal IPs to prevent SSRF attacks
                            
                            // Check for localhost names
                            if (in_array(strtolower($target_host_normalized), array('localhost', 'localhost.localdomain'))) {
                                $is_internal_ip = true;
                            }
                            
                            // Check for IPv6 localhost [::1] or ::1
                            if (preg_match('/^\[?::1\]?$/', $target_host_normalized)) {
                                $is_internal_ip = true;
                            }
                            
                            // Check for IPv4 private ranges
                            // Note: Remove brackets for IPv6 format before IP check
                            $host_for_ip_check = trim($target_host_normalized, '[]');
                            if (filter_var($host_for_ip_check, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
                                // 127.0.0.0/8 (loopback), 10.0.0.0/8 (private), 172.16.0.0/12 (private), 192.168.0.0/16 (private), 169.254.0.0/16 (link-local)
                                $ip_long = ip2long($host_for_ip_check);
                                if ($ip_long !== false) {
                                    if ((($ip_long & 0xFF000000) === 0x7F000000) ||  // 127.0.0.0/8
                                        (($ip_long & 0xFF000000) === 0x0A000000) ||  // 10.0.0.0/8
                                        (($ip_long & 0xFFF00000) === 0xAC100000) ||  // 172.16.0.0/12
                                        (($ip_long & 0xFFFF0000) === 0xC0A80000) ||  // 192.168.0.0/16
                                        (($ip_long & 0xFFFF0000) === 0xA9FE0000)) {  // 169.254.0.0/16 (AWS metadata)
                                        $is_internal_ip = true;
                                    }
                                }
                            }
                            
                            // Block if internal/private IP detected
                            if ($is_internal_ip) {
                                if (class_exists('VMPFence_Secure_Logger')) {
                                    VMPFence_Secure_Logger::warning('SSRF attempt to internal IP blocked', array(
                                        'parameter' => $param,
                                        'target_host' => substr($target_host, 0, 100),
                                        'ip' => $this->request['ip']
                                    ));
                                }
                                
                                if ($traffic_logger) {
                                    $traffic_logger->setAction('blocked:ssrf-internal', 'SSRF to internal/private IP blocked', array(
                                        'parameter' => $param,
                                        'target_host' => substr($target_host, 0, 100)
                                    ));
                                    $traffic_logger->setStatusCode(403);
                                }
                                
                                $e = new VMPFence_Block_Exception('SSRF attempt to internal IP detected');
                                $e->set_violated_rules(array(array(
                                    'rule_id' => 'builtin-ssrf-internal',
                                    'description' => 'Redirect to localhost/private IP blocked',
                                    'pattern' => 'SSRF to internal IP'
                                )));
                                throw $e;
                            }
                        }
                        
                        // SECURITY: Block external domains (including IP addresses)
                        if ($target_host_normalized !== $current_host) {
                            // Safe escaping for logging (avoid XSS in logs)
                            $safe_host = function_exists('esc_html') ? esc_html($target_host) : htmlspecialchars($target_host, ENT_QUOTES, 'UTF-8');
                            
                            if (class_exists('VMPFence_Secure_Logger')) {
                                VMPFence_Secure_Logger::warning('Open redirect to external domain blocked', array(
                                    'parameter' => $param,
                                    'target_host' => substr($safe_host, 0, 100),
                                    'current_host' => $current_host,
                                    'ip' => $this->request['ip']
                                ));
                            }
                            
                            if ($traffic_logger) {
                                $traffic_logger->setAction('blocked:open-redirect', 'Open redirect to external domain', array(
                                    'parameter' => $param,
                                    'target_host' => substr($safe_host, 0, 100)
                                ));
                                $traffic_logger->setStatusCode(403);
                            }
                            
                            $e = new VMPFence_Block_Exception('Open redirect to external domain detected');
                            $e->set_violated_rules(array(array(
                                'rule_id' => 'builtin-open-redirect',
                                'description' => 'Redirect to external domain blocked',
                                'pattern' => 'External domain: ' . $safe_host
                            )));
                            throw $e;
                        }
                    }
                }
            }
        }
        
        } // End if ($is_human_on_admin) - close redirect validation skip block
        
        // ❌ REMOVED: Old bypass for wp-admin (now handled by is_admin_request() at line 75)
        // Human browsers bypass at line 75 via is_admin_request()
        // Bots/automation MUST go through WAF rules below (don't skip!)
        
        // Special handling for wp-login.php: Only allow specific WordPress parameters
        if ($path === '/wordpress/wp-login.php' || $path === '/wp-login.php') {
            
            // WordPress legitimate parameters that can contain URLs or should be skipped
            // NOTE: redirect_to is in this list BUT already validated above (lines 384-761)
            // We keep it here to prevent duplicate WAF rule checks on already-secured params
            $allowed_params = array('redirect_to', 'action', 'reauth', 'loggedout', 'registration', 'checkemail', 'error', 'interim-login', 'testcookie', 'log', 'pwd', 'wp-submit', 'rememberme', '_wpnonce', 'wp_lang');
            
            // Check for unauthorized query parameters (in $_GET and $_POST for wp-login.php)
            $has_unauthorized = false;
            $all_login_params = array_merge($_GET, $_POST);
            foreach ($all_login_params as $param_key => $param_value) {
                if (!in_array($param_key, $allowed_params)) {
                    $has_unauthorized = true;
                    break;
                }
            }
            
            // If no unauthorized params found, remove allowed params from merged params array
            if (!$has_unauthorized) {
                foreach ($allowed_params as $param_name) {
                    if (isset($this->request['params'][$param_name])) {
                        unset($this->request['params'][$param_name]);
                    }
                }
                
                // Remove legitimate HTTP headers that contain URLs to prevent false positives
                $safe_headers = array('ORIGIN', 'HOST', 'REFERER');
                foreach ($safe_headers as $header) {
                    if (isset($this->request['params'][$header])) {
                        unset($this->request['params'][$header]);
                    }
                }
            }
            
            // Check REFERER header - only allow if it's from same domain or wp-login.php itself
            // NOTE: REFERER is already removed above, but keeping this for backwards compatibility
            if (isset($this->request['params']['REFERER']) && !empty($this->request['params']['REFERER'])) {
                $referer = $this->request['params']['REFERER'];
                $server_host = $_SERVER['HTTP_HOST'] ?? '';
                
                // Parse referer to check if it's from same domain
                $referer_host = parse_url($referer, PHP_URL_HOST);
                
                if ($referer_host === $server_host) {
                    // Referer is from same domain - check if it's from expected WordPress pages
                    $referer_path = parse_url($referer, PHP_URL_PATH);
                    $expected_paths = array('/wordpress/wp-login.php', '/wp-login.php', '/wordpress/wp-admin/', '/wp-admin/');
                    
                    $is_expected = false;
                    foreach ($expected_paths as $expected_path) {
                        if (strpos($referer_path, $expected_path) !== false) {
                            $is_expected = true;
                            break;
                        }
                    }
                    
                    if ($is_expected) {
                        unset($this->request['params']['REFERER']);
                    }
                }
            }
        }

        // Instantiate WAF Controller
        $controller = new VMPFence_WAF_Controller();
        
        // Evaluate request
        $result = $controller->evaluate_request($this->request);
        // Handle 'allow' action (whitelist rule matched)
        if ($result['action'] === 'allow') {
            $e = new VMPFence_Allow_Exception('WAF whitelist rule matched');
            $e->set_violated_rules($result['violations']);
            throw $e;
        }

        // Handle 'block' action (threshold exceeded)
        if ($result['action'] === 'block') {
            // Log to Live Traffic
            if (class_exists('VMPFence_Live_Traffic_Logger') && class_exists('VMPFence_Request_Model')) {
                $logger = VMPFence_Live_Traffic_Logger::getInstance();
                
                // Initialize request if not already done
                $logger->initLogRequest();
                
                // Prepare action data with rule details
                $action_data = array(
                    'failedRules' => array_map(function($v) { return $v['rule_id'] ?? 0; }, $result['violations']),
                    'violations' => $result['violations'],
                    'score' => $result['score'],
                    'scores_by_category' => $result['scores_by_category'] ?? array()
                );
                
                // Determine category for description
                $highest_category = $controller->get_highest_category();
                $description = "WAF blocked {$highest_category} attack (score: {$result['score']})";
                
                $logger->setAction('blocked:waf', $description, $action_data);
                $logger->setStatusCode(403);
                // Note: Shutdown hook will save automatically, no need to call logHit()
            }
            
            // Determine exception type by checking highest category
            $highest_category = $controller->get_highest_category();
            
            if ($highest_category === 'XSS') {
                $e = new VMPFence_Block_XSS_Exception('XSS attack detected');
            } elseif ($highest_category === 'SQLi') {
                $e = new VMPFence_Block_SQLi_Exception('SQL injection detected');
            } else {
                $e = new VMPFence_Block_Exception('WAF rule violation');
            }
            
            $e->set_violated_rules($result['violations']);
            $e->set_score($result['score']);
            throw $e;
        }

        // Handle 'log' action (violations recorded but below threshold)
        if ($result['action'] === 'log' && !empty($result['violations'])) {
            VMPFence_WAF_Log::log_violation_details(
                $result['violations'],
                $result['score'],
                'log'
            );
            
            // Log to Live Traffic
            if (class_exists('VMPFence_Live_Traffic_Logger') && class_exists('VMPFence_Request_Model')) {
                $logger = VMPFence_Live_Traffic_Logger::getInstance();
                
                // Initialize request if not already done
                $logger->initLogRequest();
                
                // Prepare action data
                $action_data = array(
                    'failedRules' => array_map(function($v) { return $v['rule_id'] ?? 0; }, $result['violations']),
                    'violations' => $result['violations'],
                    'score' => $result['score'],
                    'scores_by_category' => $result['scores_by_category'] ?? array()
                );
                
                $logger->setAction('logged:waf', "WAF logged suspicious activity (score: {$result['score']})", $action_data);
                // Note: Shutdown hook will save automatically, no need to call logHit()
            }
        }

        return false;
    }
    
    public function block_request($exception) {
        status_header(403);
        
        $reason = $exception->getMessage();
        $rules = $exception->get_violated_rules();
        $score = method_exists($exception, 'get_score') ? $exception->get_score() : 0;
        
        // Build rules HTML
        $rules_html = '';
        if (!empty($rules) && is_array($rules)) {
            $rules_html = '<div class="rules-section">';
            $rules_html .= '<p><strong>Violated Rules:</strong></p>';
            $rules_html .= '<ul class="rules-list">';
            foreach ($rules as $rule) {
                $rule_id = isset($rule['id']) ? $rule['id'] : (isset($rule['rule_id']) ? $rule['rule_id'] : 'unknown');
                $rule_desc = isset($rule['description']) ? $rule['description'] : '';
                $rule_category = isset($rule['category']) ? $rule['category'] : '';
                $rule_severity = isset($rule['severity']) ? $rule['severity'] : '';
                
                $rules_html .= '<li>';
                $rules_html .= '<strong>Rule ID:</strong> ' . esc_html($rule_id);
                if ($rule_category) {
                    $rules_html .= ' <span class="badge">' . esc_html(strtoupper($rule_category)) . '</span>';
                }
                if ($rule_severity) {
                    $rules_html .= ' <span class="severity severity-' . esc_attr($rule_severity) . '">' . esc_html(ucfirst($rule_severity)) . '</span>';
                }
                if ($rule_desc) {
                    $rules_html .= '<br><span class="rule-desc">' . esc_html($rule_desc) . '</span>';
                }
                $rules_html .= '</li>';
            }
            $rules_html .= '</ul></div>';
        }
        
        echo '<!DOCTYPE html>
<html>
<head>
    <title>403 Forbidden</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 50px; background: #f8f9fa; }
        .container { max-width: 700px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        h1 { color: #d32f2f; margin-top: 0; }
        .details { background: #f5f5f5; padding: 15px; border-radius: 5px; margin-top: 20px; }
        .details p { margin: 8px 0; }
        .rules-section { margin-top: 20px; padding: 15px; background: #fff3cd; border-left: 4px solid #ffc107; border-radius: 4px; }
        .rules-list { list-style: none; padding: 0; margin: 10px 0 0 0; }
        .rules-list li { padding: 10px; margin: 8px 0; background: white; border-radius: 4px; border: 1px solid #e0e0e0; }
        .badge { display: inline-block; padding: 2px 8px; background: #007bff; color: white; border-radius: 3px; font-size: 11px; font-weight: bold; margin-left: 5px; }
        .severity { display: inline-block; padding: 2px 8px; border-radius: 3px; font-size: 11px; font-weight: bold; margin-left: 5px; }
        .severity-low { background: #28a745; color: white; }
        .severity-medium { background: #ffc107; color: #333; }
        .severity-high { background: #fd7e14; color: white; }
        .severity-critical { background: #dc3545; color: white; }
        .rule-desc { display: block; margin-top: 5px; color: #666; font-size: 13px; font-style: italic; }
        .footer { margin-top: 20px; padding-top: 15px; border-top: 1px solid #e0e0e0; color: #666; font-size: 12px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>🚫 Access Denied</h1>
        <p>Your access to this resource has been blocked by VMPFence security.</p>
        <div class="details">
            <p><strong>Reason:</strong> ' . esc_html($reason) . '</p>
            <p><strong>Time:</strong> ' . esc_html(current_time('mysql')) . '</p>
            <p><strong>IP:</strong> ' . esc_html($this->request['ip']) . '</p>
            ' . ($score > 0 ? '<p><strong>Threat Score:</strong> ' . esc_html($score) . '</p>' : '') . '
        </div>
        ' . $rules_html . '
        <div class="footer">
            <p>If you believe this is an error, please contact the site administrator with the information above.</p>
        </div>
    </div>
</body>
</html>';
        exit;
    }
    
    public function block_xss_request($exception) {
        status_header(403);
        
        $rules = $exception->get_violated_rules();
        
        // Build rules HTML
        $rules_html = '';
        if (!empty($rules) && is_array($rules)) {
            $rules_html = '<div class="rules-section">';
            $rules_html .= '<p><strong>Violated XSS Rules:</strong></p>';
            $rules_html .= '<ul class="rules-list">';
            foreach ($rules as $rule) {
                $rule_id = isset($rule['id']) ? $rule['id'] : (isset($rule['rule_id']) ? $rule['rule_id'] : 'unknown');
                $rule_desc = isset($rule['description']) ? $rule['description'] : '';
                $rule_pattern = isset($rule['pattern']) ? $rule['pattern'] : '';
                
                $rules_html .= '<li>';
                $rules_html .= '<strong>Rule ID:</strong> ' . esc_html($rule_id) . ' <span class="badge badge-xss">XSS</span>';
                if ($rule_desc) {
                    $rules_html .= '<br><span class="rule-desc">' . esc_html($rule_desc) . '</span>';
                }
                if ($rule_pattern && strlen($rule_pattern) < 100) {
                    $rules_html .= '<br><span class="rule-pattern">Pattern: <code>' . esc_html($rule_pattern) . '</code></span>';
                }
                $rules_html .= '</li>';
            }
            $rules_html .= '</ul></div>';
        }
        
        echo '<!DOCTYPE html>
<html>
<head>
    <title>403 Forbidden - XSS Attack Blocked</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 50px; background: #f8f9fa; }
        .container { max-width: 700px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        h1 { color: #d32f2f; margin-top: 0; }
        .xss-warning { background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 20px; border-radius: 4px; }
        .details { background: #f5f5f5; padding: 15px; border-radius: 5px; margin-top: 20px; }
        .details p { margin: 8px 0; }
        .rules-section { margin-top: 20px; padding: 15px; background: #ffe6e6; border-left: 4px solid #dc3545; border-radius: 4px; }
        .rules-list { list-style: none; padding: 0; margin: 10px 0 0 0; }
        .rules-list li { padding: 10px; margin: 8px 0; background: white; border-radius: 4px; border: 1px solid #e0e0e0; }
        .badge { display: inline-block; padding: 2px 8px; background: #007bff; color: white; border-radius: 3px; font-size: 11px; font-weight: bold; margin-left: 5px; }
        .badge-xss { background: #dc3545; }
        .rule-desc { display: block; margin-top: 5px; color: #666; font-size: 13px; font-style: italic; }
        .rule-pattern { display: block; margin-top: 5px; color: #495057; font-size: 12px; }
        .rule-pattern code { background: #f8f9fa; padding: 2px 6px; border-radius: 3px; font-family: monospace; }
        .footer { margin-top: 20px; padding-top: 15px; border-top: 1px solid #e0e0e0; color: #666; font-size: 12px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>⚠️ XSS Attack Blocked</h1>
        <div class="xss-warning">
            <strong>Cross-Site Scripting (XSS) Detected</strong>
            <p>Your request contains potentially malicious script code.</p>
        </div>
        <div class="details">
            <p><strong>Time:</strong> ' . esc_html(current_time('mysql')) . '</p>
            <p><strong>IP:</strong> ' . esc_html($this->request['ip']) . '</p>
        </div>
        ' . $rules_html . '
        <div class="footer">
            <p>If you believe this is an error, please contact the site administrator with the information above.</p>
        </div>
    </div>
</body>
</html>';
        exit;
    }
    
    public function block_sqli_request($exception) {
        status_header(403);
        
        $rules = $exception->get_violated_rules();
        
        // Build rules HTML
        $rules_html = '';
        if (!empty($rules) && is_array($rules)) {
            $rules_html = '<div class="rules-section">';
            $rules_html .= '<p><strong>Violated SQL Injection Rules:</strong></p>';
            $rules_html .= '<ul class="rules-list">';
            foreach ($rules as $rule) {
                $rule_id = isset($rule['id']) ? $rule['id'] : (isset($rule['rule_id']) ? $rule['rule_id'] : 'unknown');
                $rule_desc = isset($rule['description']) ? $rule['description'] : '';
                $rule_pattern = isset($rule['pattern']) ? $rule['pattern'] : '';
                
                $rules_html .= '<li>';
                $rules_html .= '<strong>Rule ID:</strong> ' . esc_html($rule_id) . ' <span class="badge badge-sqli">SQLi</span>';
                if ($rule_desc) {
                    $rules_html .= '<br><span class="rule-desc">' . esc_html($rule_desc) . '</span>';
                }
                if ($rule_pattern && strlen($rule_pattern) < 100) {
                    $rules_html .= '<br><span class="rule-pattern">Pattern: <code>' . esc_html($rule_pattern) . '</code></span>';
                }
                $rules_html .= '</li>';
            }
            $rules_html .= '</ul></div>';
        }
        
        echo '<!DOCTYPE html>
<html>
<head>
    <title>403 Forbidden - SQL Injection Blocked</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 50px; background: #f8f9fa; }
        .container { max-width: 700px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        h1 { color: #d32f2f; margin-top: 0; }
        .sqli-warning { background: #ffebee; border-left: 4px solid #f44336; padding: 15px; margin-top: 20px; border-radius: 4px; }
        .details { background: #f5f5f5; padding: 15px; border-radius: 5px; margin-top: 20px; }
        .details p { margin: 8px 0; }
        .rules-section { margin-top: 20px; padding: 15px; background: #ffe6e6; border-left: 4px solid #dc3545; border-radius: 4px; }
        .rules-list { list-style: none; padding: 0; margin: 10px 0 0 0; }
        .rules-list li { padding: 10px; margin: 8px 0; background: white; border-radius: 4px; border: 1px solid #e0e0e0; }
        .badge { display: inline-block; padding: 2px 8px; background: #007bff; color: white; border-radius: 3px; font-size: 11px; font-weight: bold; margin-left: 5px; }
        .badge-sqli { background: #f44336; }
        .rule-desc { display: block; margin-top: 5px; color: #666; font-size: 13px; font-style: italic; }
        .rule-pattern { display: block; margin-top: 5px; color: #495057; font-size: 12px; }
        .rule-pattern code { background: #f8f9fa; padding: 2px 6px; border-radius: 3px; font-family: monospace; }
        .footer { margin-top: 20px; padding-top: 15px; border-top: 1px solid #e0e0e0; color: #666; font-size: 12px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>🛡️ SQL Injection Blocked</h1>
        <div class="sqli-warning">
            <strong>SQL Injection Attack Detected</strong>
            <p>Your request contains potentially malicious SQL code.</p>
        </div>
        <div class="details">
            <p><strong>Time:</strong> ' . esc_html(current_time('mysql')) . '</p>
            <p><strong>IP:</strong> ' . esc_html($this->request['ip']) . '</p>
        </div>
        ' . $rules_html . '
        <div class="footer">
            <p>If you believe this is an error, please contact the site administrator with the information above.</p>
        </div>
    </div>
</body>
</html>';
        exit;
    }
    
    /**
     * Handle country-based blocking
     * @since 2.2.0
     * @param VMPFence_Block_Exception $exception The block exception
     */
    public function block_country_request($exception) {
        // Check for redirect action FIRST (before setting 403 header)
        $block_action = 'message';
        if (class_exists('VMPFence_Config')) {
            $block_action = VMPFence_Config::get('country_block_action', 'message');
        }
        
        // If redirect action is selected, check for redirect URL
        if ($block_action === 'redirect') {
            $redirect_url = get_option('vmpfence_country_block_redirect', '');
            
            if (!empty($redirect_url)) {
                wp_redirect($redirect_url);
                exit;
            }
            // If redirect URL is empty, fall back to message
        }
        
        // Show 403 page (default or fallback)
        status_header(403);
        
        // Extract country information from exception's block_data
        $block_data = $exception->get_block_data();
        $country_code = isset($block_data['country_code']) ? $block_data['country_code'] : 'XX';
        $country_name = isset($block_data['country_name']) ? $block_data['country_name'] : 'Unknown';
        $block_type = $exception->get_block_type();
        
        // Determine block reason text
        $block_reason_text = ($block_type === 'blocked_country_login') 
            ? 'Login access from your country is blocked' 
            : 'Site access from your country is blocked';
        
        echo '<!DOCTYPE html>
<html>
<head>
    <title>Access Denied - Country Blocked</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        body { 
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            margin: 0;
            padding: 0;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .container { 
            max-width: 600px; 
            width: 90%;
            background: white; 
            padding: 50px 40px; 
            border-radius: 10px; 
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
        }
        .error-code {
            font-size: 72px;
            font-weight: bold;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            text-align: center;
            margin: 0 0 20px 0;
            line-height: 1;
        }
        h1 { 
            color: #2d3748; 
            font-size: 28px;
            text-align: center;
            margin: 0 0 15px 0;
            font-weight: 600;
        }
        .subtitle {
            color: #718096;
            text-align: center;
            font-size: 16px;
            margin: 0 0 30px 0;
        }
        .shield-icon {
            text-align: center;
            font-size: 48px;
            margin-bottom: 20px;
        }
        .alert-box {
            background: #fff5f5;
            border-left: 4px solid #fc8181;
            padding: 20px;
            border-radius: 5px;
            margin: 25px 0;
        }
        .alert-title {
            color: #c53030;
            font-weight: 600;
            font-size: 16px;
            margin: 0 0 10px 0;
        }
        .alert-message {
            color: #742a2a;
            line-height: 1.6;
            margin: 0;
        }
        .info-box {
            background: #f7fafc;
            padding: 20px;
            border-radius: 8px;
            margin: 25px 0;
            border: 1px solid #e2e8f0;
        }
        .info-row {
            display: flex;
            justify-content: space-between;
            padding: 10px 0;
            border-bottom: 1px solid #e2e8f0;
        }
        .info-row:last-child {
            border-bottom: none;
        }
        .info-label {
            color: #4a5568;
            font-weight: 500;
        }
        .info-value {
            color: #2d3748;
            font-weight: 600;
        }
        .footer {
            font-size: 13px;
            color: #a0aec0;
            text-align: center;
            margin-top: 30px;
            padding-top: 20px;
            border-top: 1px solid #e2e8f0;
        }
        @media (max-width: 600px) {
            .container {
                padding: 30px 20px;
            }
            .error-code {
                font-size: 56px;
            }
            h1 {
                font-size: 24px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="shield-icon">🛡️</div>
        <div class="error-code">403</div>
        <h1>Access Denied</h1>
        <p class="subtitle">Your location has been restricted</p>
        
        <div class="alert-box">
            <div class="alert-title">🚫 Country Blocked</div>
            <div class="alert-message">
                ' . esc_html($block_reason_text) . '
            </div>
        </div>

        <div class="info-box">
            <div class="info-row">
                <span class="info-label">Country</span>
                <span class="info-value">' . esc_html($country_name) . ' (' . esc_html($country_code) . ')</span>
            </div>
            <div class="info-row">
                <span class="info-label">Your IP</span>
                <span class="info-value">' . esc_html($this->request['ip']) . '</span>
            </div>
            <div class="info-row">
                <span class="info-label">Status</span>
                <span class="info-value" style="color: #c53030;">Blocked</span>
            </div>
            <div class="info-row">
                <span class="info-label">Time</span>
                <span class="info-value">' . esc_html(current_time('mysql')) . '</span>
            </div>
        </div>

        <div class="footer">
            <p><strong>What happened?</strong></p>
            <p>This website has restricted access from your geographic location for security purposes.</p>
            <p style="margin-top: 15px;">If you believe this is an error, please contact the site administrator with the information above.</p>
            <p style="margin-top: 20px; font-size: 11px;">Protected by VMPFence Security • Incident logged for review</p>
        </div>
    </div>
</body>
</html>';
        exit;
    }
    
    private function log_event($action, $message, $exception) {
        global $wpdb;
        
        // Skip logging if exception is marked as silent (e.g., already-blocked IPs)
        if ($exception instanceof VMPFence_Block_Exception && $exception->is_silent()) {
            return;
        }
        
        $table_name = $wpdb->prefix . 'vmpfence_waf_log';
        
        // Convert IP to binary format safely
        $ip_binary = '';
        if (filter_var($this->request['ip'], FILTER_VALIDATE_IP)) {
            $ip_converted = @inet_pton($this->request['ip']);
            if ($ip_converted !== false) {
                $ip_binary = $ip_converted;
            }
        }
        $data = array(
            'attackLogTime' => microtime(true),
            'IP' => $ip_binary,
            'action' => sanitize_text_field($action),
            'actionDescription' => sanitize_text_field($message),
            'actionData' => wp_json_encode(array(
                'violated_rules' => $exception->get_violated_rules(),
                'score' => method_exists($exception, 'get_score') ? $exception->get_score() : 0
            )),
            'logMsg' => sanitize_text_field($exception->getMessage()),
            'request_uri' => sanitize_text_field($this->request['uri']),
            'http_referer' => sanitize_text_field($this->request['referer']),
            'http_user_agent' => sanitize_text_field($this->request['user_agent'])
        );
        
        $wpdb->insert($table_name, $data);
        
        // Send event to API via logging service
        if (class_exists('VMPFence_Logging_Service')) {
            $logging_service = VMPFence_Logging_Service::get_instance();
            
            // Map firewall actions to event types
            $event_type_map = array(
                'block' => 'firewall_block',        // Generic attacks (RCE, LFI, RFI, etc.)
                'block_xss' => 'xss_detected',      // XSS attacks specifically (FIXED: was 'firewall_block')
                'block_sqli' => 'sqli_detected',    // SQLi attacks specifically (FIXED: was 'firewall_block')
                'allow' => 'firewall_allow'
            );
            
            // Map to severity based on action
            $severity_map = array(
                'block' => 'error',
                'block_xss' => 'critical',
                'block_sqli' => 'critical',
                'allow' => 'info'
            );
            
            $event_type = $event_type_map[$action] ?? 'firewall_event';
            $severity = $severity_map[$action] ?? 'warning';
            
            $event_data = array(
                'ip_address' => $this->request['ip'],
                'action' => $action,
                'reason' => $message,
                'violated_rules' => $exception->get_violated_rules(),
                'score' => method_exists($exception, 'get_score') ? $exception->get_score() : 0,
                'request_uri' => $this->request['uri'],
                'request_method' => $this->request['method'],
                'user_agent' => $this->request['user_agent'],
                'referer' => $this->request['referer'],
                'plugin_version' => defined('VMPFENCE_VERSION') ? VMPFENCE_VERSION : '1.0.0',
                'wp_version' => get_bloginfo('version'),
                'php_version' => phpversion()
            );
            
            $logging_service->log_event($event_type, $severity, $event_data, 'firewall');
        }
    }
    
    /**
     * Block request due to custom pattern match
     * @since 2.2.0
     */
    public function block_pattern_request($exception) {
        status_header(403);
        nocache_headers();
        
        $pattern_type = $exception->get_pattern_type();
        $pattern_value = esc_html($exception->get_pattern_value());
        
        echo '<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Access Denied</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            margin: 0;
            padding: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
        }
        .container {
            background: white;
            border-radius: 10px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.2);
            padding: 40px;
            max-width: 500px;
            text-align: center;
        }
        h1 { color: #e74c3c; margin-top: 0; font-size: 2em; }
        p { color: #555; line-height: 1.6; }
        .pattern-info { background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0; font-family: monospace; word-break: break-all; }
        .footer { margin-top: 30px; font-size: 0.85em; color: #999; }
    </style>
</head>
<body>
    <div class="container">
        <h1>🚫 Access Denied</h1>
        <p>Your request has been blocked by our security system.</p>
        <div class="pattern-info">
            <strong>Blocked Pattern:</strong><br>
            Type: ' . esc_html(ucfirst(str_replace('_', ' ', $pattern_type))) . '<br>
            Pattern: ' . $pattern_value . '
        </div>
        <p>If you believe this is an error, please contact the site administrator.</p>
        <div class="footer">Protected by VMP Security</div>
    </div>
</body>
</html>';
        exit;
    }
}
