<?php
/**
 * VMPFence AJAX Handlers
 *
 * @package VMPFence
 * @since 1.0.0
 * @since 2.2.0 Added diagnostic AJAX handlers
 * @since 2.2.1 Updated text-domain to vmpfence-security
 * @since 2.2.4 Malware signatures calculation refactored, pushed final results to server
 * @since 2.2.5 Added Enhanced Audit Log integration for all AJAX handlers
 * @since 2.2.6 Added Email Activity Report AJAX handler, debug log size limiting
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

class VMPFence_AJAX_Handler {
    
    /**
     * Constructor
     */
    public function __construct() {
        add_action('wp_ajax_vmpfence_start_scan', [$this, 'handle_start_scan']);
        add_action('wp_ajax_vmpfence_get_scan_progress', [$this, 'handle_get_scan_progress']);
        add_action('wp_ajax_vmpfence_get_scan_results', [$this, 'handle_get_scan_results']);
        add_action('wp_ajax_vmpfence_clear_scan_progress', [$this, 'handle_clear_scan_progress']);
        add_action('wp_ajax_vmpfence_get_debug_logs', [$this, 'handle_get_debug_logs']);
        add_action('wp_ajax_vmpfence_email_activity_log', [$this, 'handle_email_activity_log']);
        add_action('wp_ajax_vmpfence_ignore_issue', [$this, 'handle_ignore_issue']);
        add_action('wp_ajax_vmpfence_unignore_issue', [$this, 'handle_unignore_issue']);
        add_action('wp_ajax_vmpfence_mark_as_fixed', [$this, 'handle_mark_as_fixed']);
        add_action('wp_ajax_vmpfence_get_scan_status', [$this, 'handle_get_scan_status']);
        add_action('wp_ajax_vmpfence_store_scan_status', [$this, 'handle_store_scan_status']);
        add_action('wp_ajax_vmpfence_get_reputation_percentage', [$this, 'handle_get_reputation_percentage']);
        add_action('wp_ajax_vmpfence_get_security_scores', [$this, 'handle_get_security_scores']);
        add_action('wp_ajax_vmpfence_get_scan_type_percentage', [$this, 'handle_get_scan_type_percentage']);
        
        // Diagnostic test AJAX handler
        add_action('wp_ajax_vmpfence_testAjax', [$this, 'handle_test_ajax']);
        add_action('wp_ajax_nopriv_vmpfence_testAjax', [$this, 'handle_test_ajax']);
        
        // Async scan background processor
        add_action('vmpfence_async_scan_worker', [$this, 'run_async_scan_worker']);
        
        // File Repair AJAX handlers
        add_action('wp_ajax_vmpfence_repair_file', [$this, 'handle_repair_file']);
        add_action('wp_ajax_vmpfence_bulk_repair', [$this, 'handle_bulk_repair']);
        add_action('wp_ajax_vmpfence_check_repairability', [$this, 'handle_check_repairability']);
        add_action('wp_ajax_vmpfence_download_file', [$this, 'handle_download_file']);
        add_action('wp_ajax_vmpfence_serve_file', [$this, 'handle_serve_file']);
        add_action('wp_ajax_nopriv_vmpfence_serve_file', [$this, 'handle_serve_file']); // Allow non-logged users with valid token
        add_action('wp_ajax_vmpfence_get_file_view_token', [$this, 'handle_get_file_view_token']);
        add_action('wp_ajax_vmpfence_get_view_file_nonce', [$this, 'handle_get_view_file_nonce']);
        
        // Real-time monitoring AJAX handlers
        add_action('wp_ajax_vmpfence_get_security_events', [$this, 'handle_get_security_events']);
        add_action('wp_ajax_vmpfence_get_login_attempts', [$this, 'handle_get_login_attempts']);
        add_action('wp_ajax_vmpfence_block_ip', [$this, 'handle_block_ip']);
        add_action('wp_ajax_vmpfence_unblock_ip', [$this, 'handle_unblock_ip']);
        add_action('wp_ajax_vmpfence_get_blocked_ips', [$this, 'handle_get_blocked_ips']);
        
        // Code injection handling AJAX handlers
        add_action('wp_ajax_vmpfence_clean_option', [$this, 'handle_clean_option']);
        add_action('wp_ajax_vmpfence_view_option_code', [$this, 'handle_view_option_code']);
        
        // File differences AJAX handler
        add_action('wp_ajax_vmpfence_get_file_differences', [$this, 'handle_get_file_differences']);
        
        // File deletion AJAX handlers
        add_action('wp_ajax_vmpfence_delete_file', [$this, 'handle_delete_file']);
        add_action('wp_ajax_vmpfence_bulk_delete_files', [$this, 'handle_bulk_delete_files']);
        
        // Log file download AJAX handler
        add_action('wp_ajax_vmpfence_download_log', [$this, 'handle_download_log']);
        
        // Test email AJAX handlers
        add_action('wp_ajax_vmpfence_send_test_email', [$this, 'handle_send_test_email']);
        add_action('wp_ajax_vmpfence_send_activity_report', [$this, 'handle_send_activity_report']);
    }
    
    /**
     * Handle start scan AJAX request - ASYNC VERSION
     * Returns immediately and schedules background scan
     * @since 1.0.0
     * @since 2.1.1 Critical fix to prevent concurrent scans
     * @since 2.1.2 Browser close handling fix, concurrent scan lock fix
     */
    public function handle_start_scan() {
        try {
            // Check if initiated by monitor (bypass security checks)
            $initiated_by_monitor = VMPFence_Config::get('scan_initiated_by_monitor', false);
            
            if (!$initiated_by_monitor) {
                // Verify nonce
                $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
                if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
                    wp_send_json_error(['message' => 'Security check failed']);
                    return;
                }
                
                // Check user capabilities
                if (!current_user_can('manage_options')) {
                    wp_send_json_error(['message' => 'Insufficient permissions']);
                    return;
                }
            }
            
            // CRITICAL FIX: Ensure scan continues even if user closes browser during init
            ignore_user_abort(true);
            
            // CRITICAL FIX: Prevent concurrent scans with lock
            $scan_lock = get_transient('vmpfence_scan_lock');
            if ($scan_lock) {
                // Check if scan is actually running by checking progress update time
                $current_progress = get_option('vmpfence_scan_progress', []);
                $last_update = isset($current_progress['last_update']) ? $current_progress['last_update'] : 0;
                
                // If last update was more than 5 minutes ago, consider scan stale
                if ($last_update > 0 && (time() - $last_update) > 300) {
                    // Stale scan detected - clear lock and allow new scan
                    delete_transient('vmpfence_scan_lock');
                    delete_option('vmpfence_scan_progress');
                } else {
                    // Active scan in progress
                    wp_send_json_error([
                        'message' => 'A scan is already in progress. Please wait for it to complete.',
                        'scan_in_progress' => true
                    ]);
                    return;
                }
            }
            
            // Set scan lock (expires in 4 hours - more than enough for any scan)
            set_transient('vmpfence_scan_lock', time(), 4 * HOUR_IN_SECONDS);

            // Get scan type
            $scan_type = isset($_POST['scan_type']) ? sanitize_text_field(wp_unslash($_POST['scan_type'])) : 'standard';
            
            // CRITICAL FIX: Atomic progress replacement (no delete, just update)
            // This eliminates the race condition window entirely
            // Using update_option replaces the value atomically
            update_option('vmpfence_scan_progress', [
                'status' => 'initializing',
                'files_scanned' => 0,
                'total_files' => 0,
                'current_section' => 'Preparing to start scan...',
                'last_update' => time(),
                'scan_start' => time(),
                'scan_progress' => 0,
                'total_scans' => 11,
                'async_mode' => true
            ]);

            // Push initializing status to server immediately so the portal
            // shows progress within seconds instead of waiting for the 5-min cron.
            try {
                (new VmpFence_Scan_Sync())->push_immediate_status('initializing', 'Preparing to start scan...');
            } catch (\Throwable $e) {
                // Non-fatal — worst case the next cron cycle will push progress.
            }
            
            // Clear any pending async scans
            wp_clear_scheduled_hook('vmpfence_async_scan_worker');
            
            // CRITICAL FIX: Capture timestamp once to avoid race condition
            // Using time() twice could cause second-drift between schedule and verify
            $event_timestamp = time();
            $event_args = [
                'scan_type' => $scan_type,
                'user_id' => get_current_user_id(),
                'timestamp' => $event_timestamp
            ];
            
            // Schedule immediate async scan
            $scheduled = wp_schedule_single_event($event_timestamp, 'vmpfence_async_scan_worker', $event_args);
            
            // CRITICAL FIX: Verify event was actually scheduled (using same args/timestamp)
            $event_queued = wp_get_scheduled_event('vmpfence_async_scan_worker', $event_args);
            
            if (!$event_queued && !$scheduled) {
                // WP-Cron scheduling failed - log error
                // error_log('VMPFence: WP-Cron scheduling failed - event not queued');
                
                // CRITICAL FIX: Release lock on scheduling failure
                delete_transient('vmpfence_scan_lock');
                
                // Fall back to direct execution with warning
                wp_send_json_error([
                    'message' => 'WP-Cron unavailable - scan may be delayed. Please check WP-Cron status.',
                    'cron_failed' => true,
                    'fallback' => 'Please try running scan again or contact support if issue persists'
                ]);
                return;
            }
            
            // Trigger WP Cron immediately
            spawn_cron();
            
            do_action('vmpfence_scan_started', 'manual');
            
            // Return success immediately - scan runs in background
            wp_send_json_success([
                'message' => 'Scan initiated in background',
                'async' => true,
                'scan_type' => $scan_type,
                'started_at' => time(),
                'cron_verified' => ($event_queued !== false)
            ]);
            
        } catch (Exception $e) {
            // CRITICAL FIX: Release lock on exception
            delete_transient('vmpfence_scan_lock');
            
            // error_log('VMPFence: Failed to start async scan - ' . $e->getMessage());
            wp_send_json_error([
                'message' => 'Failed to start scan: ' . $e->getMessage(),
                'file' => basename($e->getFile()),
                'line' => $e->getLine()
            ]);
        }
    }
    
    /**
     * Run async scan worker - executed by WP Cron
     * This is where the actual scan happens in background
     * @since 2.1.0
     * @since 2.1.2 Async update option, scan lock fix
     */
    public function run_async_scan_worker($scan_type = 'standard', $user_id = 0, $timestamp = 0) {
        // CRITICAL FIX: Check scan lock to prevent duplicate execution
        // This prevents race condition if WP-Cron fires multiple times
        $lock = get_transient('vmpfence_scan_lock');
        if (!$lock) {
            // error_log('VMPFence Background Execution: Scan lock not found - another scan already completed or lock expired. Aborting duplicate execution.');
            return; // Exit silently - lock already released by completed scan
        }
        
        // Set up environment for background processing
        @set_time_limit(0);
        @ini_set('memory_limit', '512M');
        ignore_user_abort(true);
        
        try {
            // Load all required dependencies
            if (!class_exists('VMPFence_Scan_Config')) {
                require_once VMPFENCE_PLUGIN_DIR . 'lib/class-vmpfence-scan-config.php';
            }
            $scan_options = VMPFence_Scan_Config::getScanOptions();
            
            // Initialize IPv4-only HTTP interceptor if enabled
            $force_ipv4 = VMPFence_Config::get('scan_force_ipv4_start', false);
            $ipv4_interceptor = null;
            if ($force_ipv4) {
                if (!class_exists('VMPFence_Curl_Interceptor')) {
                    require_once VMPFENCE_PLUGIN_DIR . 'includes/class-curl-interceptor.php';
                }
                $ipv4_interceptor = new VMPFence_Curl_Interceptor($force_ipv4);
                $ipv4_interceptor->setOption(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
            }
            
            // Initialize scan monitoring
            if (!class_exists('VMPFence_Scan_Monitor')) {
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-scan-monitor.php';
            }
            VMPFence_Scan_Monitor::handleScanStart($scan_type);
            VMPFence_Scan_Monitor::logLastAttempt(false);
            
            // GLOBAL DATABASE CLEANUP
            $this->clear_all_previous_scan_data();
            
            // AUTOMATIC BACKUP CLEANUP
            $this->cleanup_old_backup_files();
            
            // Now run all the scanners
            // Initialize performance management
            if (!class_exists('VMPFence_Performance_Manager')) {
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-performance-manager.php';
            }
            VMPFence_Performance_Manager::init();
            
            // Initialize scan statistics collector
            require_once VMPFENCE_PLUGIN_DIR . 'includes/class-scan-statistics-collector.php';
            $stats_collector = VMPFence_Scan_Statistics_Collector::get_instance();
            $stats_collector->start_scan('Comprehensive Scan');
            $stats_collector->collect_wordpress_counts();
            
            // Set initial progress state
            update_option('vmpfence_scan_progress', [
                'status' => 'starting',
                'files_scanned' => 0,
                'total_files' => 0,
                'current_section' => 'Initializing comprehensive scan...',
                'last_update' => time(),
                'scan_start' => time(),
                'scan_progress' => 0,
                'total_scans' => 11,
                'async_mode' => true
            ]);
            
            // Use performance-managed time limits instead of unlimited
            // The Performance Manager will handle time limits based on configuration
            ignore_user_abort(true); // Continue even if user closes browser
            
            $scan_results = [];
            $total_issues = 0;
            
            // SIEM: Instantiate scan sync once for per-stage progress pushes
            $scan_sync = class_exists('VmpFence_Scan_Sync') ? new VmpFence_Scan_Sync() : null;
            
            // Initialize debug logs array
            $debug_logs = get_option('vmpfence_last_scan_debug_logs', []);
            
            // 1. Server State Scan - CONDITIONAL EXECUTION
            if ($scan_options['scansEnabled_checkHowGetIPs'] || 
                $scan_options['scansEnabled_wafStatus'] || 
                $scan_options['scansEnabled_diskSpace'] || 
                $scan_options['scansEnabled_checkReadableConfig'] ||
                $scan_options['scansEnabled_oldVersions']) {
                
                // Performance: Check time limits and handle stage transition
                VMPFence_Performance_Manager::stage_transition('server_state', false);
                
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Checking server state...',
                    'scan_progress' => 1,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-server-state-scanner.php';
                $server_scanner = new VMPFence_Server_State_Scanner();
                
                // Apply IPv4 interceptor to server scanner if enabled
                if ($force_ipv4 && isset($ipv4_interceptor)) {
                    $server_scanner->set_ipv4_interceptor($ipv4_interceptor);
                }
                
                $server_results = $server_scanner->run_scan($scan_options); // Pass scan options
                $scan_results['server_state'] = $server_results;
                $total_issues += $server_results['results']['issues_found'];
                
                // Store debug logs for later retrieval (append to existing logs)
                $debug_logs['server_state'] = $this->limit_debug_log_size($server_results['debug_log'] ?? []);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            } else {
                // Skip server state scan
            }
            
            // 2. File Changes Scan - CONDITIONAL EXECUTION (FIXED: Added other_scanOutside support)
            if ($scan_options['scansEnabled_core'] || 
                $scan_options['scansEnabled_plugins'] || 
                $scan_options['scansEnabled_themes'] || 
                $scan_options['scansEnabled_coreUnknown'] ||
                $scan_options['other_scanOutside']) {
                
                // Performance: Check time limits and handle stage transition
                VMPFence_Performance_Manager::stage_transition('file_changes', false);
                
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Scanning file changes...',
                    'scan_progress' => 2,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-file-scanner.php';
                $file_scanner = new VMPFence_File_Scanner();
                $file_results = $file_scanner->start_scan($scan_options); // Pass scan options
                $scan_results['file_changes'] = $file_results;
                $total_issues += $file_results['issues_found'];
                
                // Update debug logs with file scanner logs (limit size to prevent database errors)
                $debug_logs['file_changes'] = $this->limit_debug_log_size($file_results['debug_log'] ?? []);
                $debug_logs['timestamp'] = current_time('mysql');
                update_option('vmpfence_last_scan_debug_logs', $debug_logs);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            } else {
                // Skip file scanner
            }
            
            // 3. User Security Scan - CONDITIONAL EXECUTION
            if ($scan_options['scansEnabled_passwds'] || 
                $scan_options['scansEnabled_suspiciousAdminUsers']) {
                
                // Performance: Check time limits and handle stage transition
                VMPFence_Performance_Manager::stage_transition('user_security', false);
                
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Analyzing user account security...',
                    'scan_progress' => 3,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-user-security-scanner.php';
                $user_security_scanner = new VMPFence_User_Security_Scanner();
                $user_security_results = $user_security_scanner->run_scan($scan_options); // Pass scan options
                $scan_results['user_security'] = $user_security_results;
                $total_issues += $user_security_results['issues_found'];
                
                // Update debug logs with user security scanner logs (limit size to prevent database errors)
                $debug_logs['user_security'] = $this->limit_debug_log_size($user_security_results['debug_log'] ?? []);
                update_option('vmpfence_last_scan_debug_logs', $debug_logs);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            } else {
                // Skip user security scan
            }
            
            // 4. Content Safety Scan - CONDITIONAL EXECUTION
            if ($scan_options['scansEnabled_posts'] || 
                $scan_options['scansEnabled_comments']) {
                
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Analyzing content safety violations...',
                    'scan_progress' => 4,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-content-safety-scanner.php';
                $content_safety_scanner = new VMPFence_Content_Safety_Scanner();
                $content_safety_results = $content_safety_scanner->run_scan($scan_options); // Pass scan options
                $scan_results['content_safety'] = $content_safety_results;
                $total_issues += $content_safety_results['issues_found'];
                
                // Update debug logs with content safety scanner logs (limit size to prevent database errors)
                $debug_logs['content_safety'] = $this->limit_debug_log_size($content_safety_results['debug_log'] ?? []);
                update_option('vmpfence_last_scan_debug_logs', $debug_logs);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            } else {
                // Skip content safety scan
            }
            
            // 5. Public Files Scan - CONDITIONAL EXECUTION
            if ($scan_options['scansEnabled_checkReadableConfig'] || 
                $scan_options['scansEnabled_suspectedFiles']) {
                
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Checking for publicly accessible files...',
                    'scan_progress' => 5,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-public-files-scanner.php';
                $public_files_scanner = new VMPFence_Public_Files_Scanner();
                $public_files_results = $public_files_scanner->scan($scan_options); // Pass scan options
                $scan_results['public_files'] = [
                    'issues_found' => count($public_files_results['issues']),
                    'issues' => $public_files_results['issues'],
                    'scanned_count' => $public_files_results['scanned_count']
                ];
                $total_issues += count($public_files_results['issues']);
                // Update debug logs with public files scanner logs (limit size to prevent database errors)
                $debug_logs['public_files'] = $this->limit_debug_log_size($public_files_results['debug_log'] ?? []);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            } else {
                // Skip public files scan
            }
            
            // 6. User & Option Audit Scan - CONDITIONAL EXECUTION (Options Only)
            if ($scan_options['scansEnabled_suspiciousOptions']) {
                
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Auditing suspicious options...',
                    'scan_progress' => 6,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-user-option-audit-scanner.php';
                $user_audit_scanner = new VMPFence_User_Option_Audit_Scanner();
                $user_audit_results = $user_audit_scanner->scan($scan_options); // Pass scan options
                $scan_results['user_audit'] = [
                    'issues_found' => count($user_audit_results['issues']),
                    'issues' => $user_audit_results['issues'],
                    'scanned_count' => $user_audit_results['scanned_count']
                ];
                $total_issues += count($user_audit_results['issues']);
                
                // Update debug logs with user audit scanner logs (limit size to prevent database errors)
                $debug_logs['user_audit'] = $this->limit_debug_log_size($user_audit_results['debug_log'] ?? []);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            } else {
                // Skip user option audit scan
            }
            
            // 7. Vulnerability Scan - CONDITIONAL EXECUTION
            if ($scan_options['scansEnabled_oldVersions']) {
                
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Scanning for known vulnerabilities...',
                    'scan_progress' => 7,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-vulnerability-scanner.php';
                $vulnerability_scanner = new VMPFence_Vulnerability_Scanner();
                $vulnerability_results = $vulnerability_scanner->run_scan($scan_options); // Pass scan options
                $scan_results['vulnerability'] = $vulnerability_results;
                $total_issues += $vulnerability_results['issues_found'];
                
                // Update debug logs with vulnerability scanner logs
                $debug_logs['vulnerability'] = $this->limit_debug_log_size($vulnerability_results['debug_log'] ?? []);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            } else {
                // Skip vulnerability scan
            }
            
            // 8. Malware Scan - CONDITIONAL EXECUTION
            if ($scan_options['scansEnabled_malware'] || 
                $scan_options['scansEnabled_fileContents'] || 
                $scan_options['scansEnabled_fileContentsGSB'] ||
                $scan_options['scansEnabled_scanImages']) {
                
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Scanning for malware and suspicious code...',
                    'scan_progress' => 8,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-malware-scanner.php';
                $malware_scanner = new VMPFence_Malware_Scanner();
                $malware_results = $malware_scanner->scan($scan_options); // Pass scan options
                $scan_results['malware'] = $malware_results;
                $total_issues += $malware_results['issues_found'];
                
                // Update debug logs with malware scanner logs
                $debug_logs['malware'] = $this->limit_debug_log_size($malware_results['debug_log'] ?? []);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            } else {
                // Skip malware scan
            }
            
            // 9. Domain Blocklist Check - CONDITIONAL EXECUTION
            if ($scan_options['scansEnabled_checkGSB']) {
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Checking domain blocklist status...',
                    'scan_progress' => 9,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                require_once VMPFENCE_PLUGIN_DIR . 'includes/class-reputation-scanner.php';
                $reputation_scanner = new VMPFence_Reputation_Scanner();
                $domain_results = $reputation_scanner->check_domain_blocklist();
                $scan_results['domain_blocklist'] = $domain_results;
                $total_issues += $domain_results['issues_found'];
                
                $debug_logs['domain_blocklist'] = $this->limit_debug_log_size($domain_results['debug_log'] ?? []);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            }
            
            // 10. Spamvertising Check - CONDITIONAL EXECUTION
            if ($scan_options['spamvertizeCheck']) {
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Checking for spamvertising activity...',
                    'scan_progress' => 10,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                if (!isset($reputation_scanner)) {
                    require_once VMPFENCE_PLUGIN_DIR . 'includes/class-reputation-scanner.php';
                    $reputation_scanner = new VMPFence_Reputation_Scanner();
                }
                $spamvertising_results = $reputation_scanner->check_spamvertising();
                $scan_results['spamvertising'] = $spamvertising_results;
                $total_issues += $spamvertising_results['issues_found'];
                
                $debug_logs['spamvertising'] = $this->limit_debug_log_size($spamvertising_results['debug_log'] ?? []);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            }
            
            // 11. IP Spam Check - CONDITIONAL EXECUTION
            if ($scan_options['checkSpamIP']) {
                update_option('vmpfence_scan_progress', [
                    'status' => 'scanning',
                    'current_section' => 'Checking IP spam reputation...',
                    'scan_progress' => 11,
                    'total_scans' => 11,
                    'last_update' => time(),
                    'async_mode' => true
                ]);
                
                if (!isset($reputation_scanner)) {
                    require_once VMPFENCE_PLUGIN_DIR . 'includes/class-reputation-scanner.php';
                    $reputation_scanner = new VMPFence_Reputation_Scanner();
                }
                $ip_spam_results = $reputation_scanner->check_ip_spam();
                $scan_results['ip_spam'] = $ip_spam_results;
                $total_issues += $ip_spam_results['issues_found'];
                
                $debug_logs['ip_spam'] = $this->limit_debug_log_size($ip_spam_results['debug_log'] ?? []);
                if ($scan_sync) { $scan_sync->force_push_progress(); }
            }
            
            // Calculate total size to debug the issue
            $total_entries = 0;
            foreach ($debug_logs as $key => $logs) {
                if (is_array($logs)) {
                    $total_entries += count($logs);
                }
            }
            
            // Save debug logs separately to avoid size limits
            foreach ($debug_logs as $scan_type => $logs) {
                if ($scan_type !== 'timestamp' && is_array($logs)) {
                    
                    // Add timestamp to logs to ensure they're unique each scan
                    $timestamped_logs = array_map(function($log) {
                        return '[' . current_time('H:i:s') . '] ' . $log;
                    }, $logs);
                    
                    $save_result = update_option("vmpfence_debug_logs_{$scan_type}", $timestamped_logs);
                    
                    // Note: update_option returns false if the value hasn't changed, which is normal
                    $status = $save_result ? 'SUCCESS' : 'NO_CHANGE';
                }
            }
            
            // Save a lightweight index of available debug logs
            $debug_index = [
                'timestamp' => current_time('mysql'),
                'available_logs' => array_keys(array_filter($debug_logs, function($key) { 
                    return $key !== 'timestamp'; 
                }, ARRAY_FILTER_USE_KEY))
            ];
            $update_result = update_option('vmpfence_last_scan_debug_logs', $debug_index);
            
            // Verify the save worked
            $verification = get_option('vmpfence_last_scan_debug_logs', []);
            
            // Remove debug logs from scan results before storing to prevent database packet size errors
            // Debug logs are already stored separately in vmpfence_last_scan_debug_logs
            $scan_results_without_debug = $scan_results;
            foreach ($scan_results_without_debug as $scan_type => $results) {
                if (isset($scan_results_without_debug[$scan_type]['debug_log'])) {
                    unset($scan_results_without_debug[$scan_type]['debug_log']);
                }
            }
            
            // Get WordPress update counts for notifications
            $plugin_updates = get_site_transient('update_plugins');
            $theme_updates = get_site_transient('update_themes');
            $core_updates = get_site_transient('update_core');
            
            $plugin_update_count = isset($plugin_updates->response) ? count($plugin_updates->response) : 0;
            $theme_update_count = isset($theme_updates->response) ? count($theme_updates->response) : 0;
            $core_update_count = (isset($core_updates->updates) && !empty($core_updates->updates)) ? 1 : 0;
            $total_updates = $plugin_update_count + $theme_update_count + $core_update_count;
            
            // Store scan completion data including user statistics
            $scan_completion_data = [
                'completed_time' => current_time('mysql'),
                'total_issues' => $total_issues,
                'scans_completed' => count($scan_results), // Dynamic count of actually executed scanners
                'users_checked' => $user_security_results['users_checked'] ?? 0,
                'content_items_checked' => $content_safety_results['content_items_checked'] ?? 0,
                'scan_results' => $scan_results_without_debug
            ];
            
            update_option('vmpfence_last_scan_data', $scan_completion_data);
            
            // Get accurate issue count from database for notifications
            global $wpdb;
            $table_name = $wpdb->prefix . 'vmpfence_scan_issues';
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is prefixed, no user input
            $actual_issue_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE STATUS != 'ignored'");
            // Store update counts for dashboard notifications
            update_option('vmpfence_recent_scan_issues', $actual_issue_count);
            update_option('vmpfence_recent_update_count', [
                'total' => $total_updates,
                'plugins' => $plugin_update_count,
                'themes' => $theme_update_count,
                'core' => $core_update_count,
                'last_check' => current_time('mysql')
            ]);
            
            // Complete scan statistics collection
            $final_statistics = $stats_collector->end_scan();
            
            // Log performance summary
            VMPFence_Performance_Manager::log_performance_summary();
            $performance_metrics = VMPFence_Performance_Manager::get_performance_metrics();
            
            // Update progress to completed
            update_option('vmpfence_scan_progress', [
                'status' => 'completed',
                'current_section' => 'Scan completed successfully',
                'scan_progress' => 11,
                'total_scans' => 11,
                'last_update' => time(),
                'async_mode' => true
            ]);
            
            // Log scan success for monitoring
            if (class_exists('VMPFence_Scan_Monitor')) {
                VMPFence_Scan_Monitor::logLastSuccess();
            }
            
            // SIEM: Push final scan results (issues + statistics) to server
            if (class_exists('VmpFence_Scan_Sync')) {
                try {
                    $scan_sync_final = new VmpFence_Scan_Sync();
                    $push_ok = $scan_sync_final->push_scan_results();
                    if ($push_ok) {
                        // Clear any leftover retry flag on success
                        delete_option('vmpfence_scan_results_pending_push');
                    } else {
                        // FIX-5 (v6.6): API returned WP_Error — set retry flag
                        update_option('vmpfence_scan_results_pending_push', true, false);
                    }
                } catch (\Exception $e) {
                    // Set retry flag so the next cron cycle retries the push
                    update_option('vmpfence_scan_results_pending_push', true, false);
                    if (class_exists('VMPFence_Secure_Logger')) {
                        VMPFence_Secure_Logger::log('error', 'SIEM scan results push failed after scan completion — will retry on next cron', array(
                            'error' => $e->getMessage(),
                        ));
                    }
                }
            }
            
            // CRITICAL FIX: Release scan lock on completion
            delete_transient('vmpfence_scan_lock');
            
            // error_log('VMPFence Async: Scan completed successfully - ' . $total_issues . ' issues found');
            
        } catch (VMPFence_Scan_Duration_Exception $e) {
            // Handle scan duration limit exceeded
            // error_log("VMPFence Async: Scan terminated due to duration limit - " . $e->getMessage());
            
            VMPFence_Performance_Manager::log_performance_summary();
            
            // CRITICAL FIX: Release scan lock on timeout
            delete_transient('vmpfence_scan_lock');
            
            // Update progress with timeout status
            update_option('vmpfence_scan_progress', [
                'status' => 'completed_with_timeout',
                'current_section' => 'Scan completed (time limit reached)',
                'scan_progress' => count($scan_results ?? []),
                'total_scans' => 11,
                'last_update' => time(),
                'time_limit_reached' => true,
                'async_mode' => true
            ]);
            
        } catch (Exception $e) {
            // Clear progress state on error
            // error_log('VMPFence Async: Scan failed - ' . $e->getMessage());
            // error_log('VMPFence Async: Stack trace - ' . $e->getTraceAsString());
            
            // CRITICAL FIX: Release scan lock on error
            delete_transient('vmpfence_scan_lock');
            
            update_option('vmpfence_scan_progress', [
                'status' => 'error',
                'current_section' => 'Scan failed: ' . $e->getMessage(),
                'scan_progress' => count($scan_results ?? []),
                'total_scans' => 11,
                'last_update' => time(),
                'error' => $e->getMessage(),
                'async_mode' => true
            ]);
        }
    }
    
    /**
     * Handle get scan progress AJAX request
     */
    public function handle_get_scan_progress() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Get scan progress from option
        $progress = get_option('vmpfence_scan_progress', [
            'status' => 'idle',
            'progress' => 0,
            'current_file' => '',
            'files_scanned' => 0,
            'total_files' => 0
        ]);
        
        wp_send_json_success($progress);
    }
    
    /**
     * Handle clear scan progress AJAX request
     * Called after results are loaded to prevent re-detection
     */
    public function handle_clear_scan_progress() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Delete the completed progress to prevent duplicate loads
        delete_option('vmpfence_scan_progress');
        
        wp_send_json_success(['message' => 'Progress cleared']);
    }
    
    /**
     * Handle get scan results AJAX request
     */
    public function handle_get_scan_results() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        require_once VMPFENCE_PLUGIN_DIR . 'includes/class-file-scanner.php';
        $scanner = new VMPFence_File_Scanner();
        
        $latest_scan = $scanner->get_latest_scan_results();
        
        // Get file-related issues from vmpfence_scan_issues table with proper formatting
        $file_scan_issues = $this->get_file_changes_issues('new'); // Active issues (status = 'new')
        $file_ignored_issues = $this->get_file_changes_issues('ignored'); // Ignored issues
        
        // Get user security issues from vmpfence_scan_issues table
        $user_security_issues = $this->get_user_security_issues('new');
        $user_security_ignored = $this->get_user_security_issues('ignored');
        
        // Get content safety issues from vmpfence_scan_issues table
        $content_safety_issues = $this->get_content_safety_issues('new');
        $content_safety_ignored = $this->get_content_safety_issues('ignored');
        
        // Get public files issues from vmpfence_scan_issues table
        $public_files_issues = $this->get_public_files_issues('new');
        $public_files_ignored = $this->get_public_files_issues('ignored');
        
        // Get user audit issues from vmpfence_scan_issues table
        $user_audit_issues = $this->get_user_audit_issues('new');
        $user_audit_ignored = $this->get_user_audit_issues('ignored');
        
        // Get vulnerability scanner issues from vmpfence_scan_issues table
        $vulnerability_issues = $this->get_vulnerability_issues('new');
        $vulnerability_ignored = $this->get_vulnerability_issues('ignored');
        
        // Get malware scanner issues from vmpfence_scan_issues table  
        $malware_issues = $this->get_malware_issues('new');
        $malware_ignored = $this->get_malware_issues('ignored');
        
        // Get server state scanner issues from vmpfence_scan_issues table
        $server_state_issues = $this->get_server_state_issues('new');
        $server_state_ignored = $this->get_server_state_issues('ignored');
        
        // Get reputation scanner issues from vmpfence_scan_issues table
        $reputation_issues = $this->get_reputation_issues('new');
        $reputation_ignored = $this->get_reputation_issues('ignored');
        
        // Combine all issues
        $all_active_issues = array_merge($file_scan_issues, $user_security_issues, $content_safety_issues, $public_files_issues, $user_audit_issues, $vulnerability_issues, $malware_issues, $server_state_issues, $reputation_issues);
        $all_ignored_issues = array_merge($file_ignored_issues, $user_security_ignored, $content_safety_ignored, $public_files_ignored, $user_audit_ignored, $vulnerability_ignored, $malware_ignored, $server_state_ignored, $reputation_ignored);
        
        // Build scan completion data from LIVE database results (prioritize over cache)
        $scan_completion_data = $this->build_scan_completion_from_database($all_active_issues, $all_ignored_issues);
        
        // If no fresh database data, fall back to cached data
        if (empty($scan_completion_data) || $scan_completion_data['total_issues'] == 0) {
            $cached_data = get_option('vmpfence_last_scan_data', []);
            if (!empty($cached_data)) {
                $scan_completion_data = $cached_data;
            }
        } else {
            // error_log("VMPFence: Using FRESH database scan completion data: " . $scan_completion_data['total_issues'] . " issues");
        }
        
        // Merge scan completion data with latest scan results
        if (!empty($scan_completion_data) && is_array($latest_scan)) {
            $latest_scan = array_merge($latest_scan, $scan_completion_data);
        } elseif (!empty($scan_completion_data)) {
            $latest_scan = $scan_completion_data;
        }
        
        // Get user security statistics if scanner exists
        $user_security_stats = [];
        if (file_exists(VMPFENCE_PLUGIN_DIR . 'includes/class-user-security-scanner.php')) {
            require_once VMPFENCE_PLUGIN_DIR . 'includes/class-user-security-scanner.php';
            $user_security_scanner = new VMPFence_User_Security_Scanner();
            $user_security_stats = $user_security_scanner->get_user_security_stats();
        }
        
        // Get content safety statistics if scanner exists
        $content_safety_stats = [];
        if (file_exists(VMPFENCE_PLUGIN_DIR . 'includes/class-content-safety-scanner.php')) {
            require_once VMPFENCE_PLUGIN_DIR . 'includes/class-content-safety-scanner.php';
            $content_safety_scanner = new VMPFence_Content_Safety_Scanner();
            $content_safety_stats = $content_safety_scanner->get_content_safety_stats();
        }
        
        // Add user security stats to scan data if not already present
        if (!empty($user_security_stats) && is_array($latest_scan)) {
            if (!isset($latest_scan['users_checked'])) {
                $latest_scan['users_checked'] = $user_security_stats['total_users'];
            }
            $latest_scan['user_security_score'] = $user_security_stats['security_score'];
        }
        
        // Add content safety stats to scan data if not already present
        if (!empty($content_safety_stats) && is_array($latest_scan)) {
            if (!isset($latest_scan['content_items_checked'])) {
                $latest_scan['content_items_checked'] = $content_safety_stats['total_posts'] + $content_safety_stats['total_comments'];
            }
            $latest_scan['content_safety_score'] = $content_safety_stats['safety_score'];
        }
        
        // Get real-time security events (last 50 events)
        $security_events = $this->get_recent_security_events(50);
        $login_attempts = $this->get_recent_login_attempts(50);
        $blocked_ips = $this->get_active_blocked_ips(20);
        
        // Load comprehensive scan statistics
        $comprehensive_stats = array();

        
        // First require the file, then check if class exists
        require_once VMPFENCE_PLUGIN_DIR . 'includes/class-scan-statistics-collector.php';
        
        if (class_exists('VMPFence_Scan_Statistics_Collector')) {
            $stats_collector = VMPFence_Scan_Statistics_Collector::get_instance();
            $comprehensive_stats = $stats_collector->load_last_statistics();
        }
        
        wp_send_json_success([
            'scan_data' => $latest_scan,
            'comprehensive_statistics' => $comprehensive_stats,
            'issues' => $all_active_issues,
            'ignored_issues' => $all_ignored_issues,
            'active_count' => count($all_active_issues),
            'ignored_count' => count($all_ignored_issues),
            'user_security_stats' => $user_security_stats,
            'content_safety_stats' => $content_safety_stats,
            // Real-time monitoring data
            'security_events' => $security_events,
            'login_attempts' => $login_attempts,
            'blocked_ips' => $blocked_ips,
            'realtime_stats' => [
                'recent_events' => count($security_events),
                'failed_logins_24h' => $this->count_failed_logins_24h(),
                'blocked_ips_active' => count($blocked_ips)
            ]
        ]);
    }
    
    /**
     * Handle get debug logs AJAX request
     */
    public function handle_get_debug_logs() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        // Get the debug logs index
        $debug_index = get_option('vmpfence_last_scan_debug_logs', []);
        
        // Reconstruct the full debug logs from separate options
        $debug_logs = [];
        
        if (isset($debug_index['timestamp'])) {
            $debug_logs['timestamp'] = $debug_index['timestamp'];
        }
        
        if (isset($debug_index['available_logs']) && is_array($debug_index['available_logs'])) {
            foreach ($debug_index['available_logs'] as $scan_type) {
                $logs = get_option("vmpfence_debug_logs_{$scan_type}", []);
                if (!empty($logs)) {
                    $debug_logs[$scan_type] = $logs;
                }
            }
        }
        
        wp_send_json_success($debug_logs);
    }
    
    /**
     * Handle email activity log AJAX request
     */
    public function handle_email_activity_log() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        // Get and validate email address
        $email = isset($_POST['email']) ? sanitize_email(wp_unslash($_POST['email'])) : '';
        if (!is_email($email)) {
            wp_send_json_error(['message' => 'Invalid email address provided']);
            return;
        }
        
        // Collect debug logs per scan type (structured for server template)
        $debug_index = get_option('vmpfence_last_scan_debug_logs', []);
        $debug_logs = array();
        $log_content = '';

        if (!empty($debug_index) && !empty($debug_index['available_logs'])) {
            foreach ($debug_index['available_logs'] as $scan_type) {
                $logs = get_option("vmpfence_debug_logs_{$scan_type}", []);
                if (!empty($logs)) {
                    $label = ucwords(str_replace('_', ' ', $scan_type));
                    $entries = array();
                    foreach ($logs as $log_entry) {
                        if (is_array($log_entry)) {
                            $entries[] = array(
                                'timestamp' => isset($log_entry['timestamp']) ? $log_entry['timestamp'] : '',
                                'level'     => isset($log_entry['level']) ? $log_entry['level'] : 'INFO',
                                'message'   => isset($log_entry['message']) ? $log_entry['message'] : '',
                            );
                        } else {
                            $entries[] = array('message' => (string) $log_entry);
                        }
                    }
                    $debug_logs[$label] = $entries;
                }
            }
        }

        // Collect scan stats
        $scan_stats_raw = get_option('vmpfence_last_scan_stats', []);
        $scan_stats = array();
        if (!empty($scan_stats_raw)) {
            foreach ($scan_stats_raw as $key => $value) {
                $scan_stats[ucfirst(str_replace('_', ' ', $key))] = $value;
            }
        }
        if (isset($debug_index['timestamp'])) {
            $scan_stats['Last scan completed'] = $debug_index['timestamp'];
        }

        // Send via centralized Email Service (API → branded template)
        $context = array(
            'plugin_version' => defined('VMPFENCE_VERSION') ? VMPFENCE_VERSION : 'unknown',
            'wp_version'     => get_bloginfo('version'),
            'admin_email'    => get_option('admin_email'),
            'scan_timestamp' => current_time('Y-m-d H:i:s'),
            'debug_logs'     => $debug_logs,
            'scan_stats'     => $scan_stats,
        );

        $result = VMPFence_Email_Service::send_alert('activity_log', $context, array($email));

        if ($result['success']) {
            wp_send_json_success(['message' => sprintf('Activity log has been sent to %s', $email)]);
        } else {
            wp_send_json_error(['message' => sprintf('Failed to send activity log: %s', $result['message'])]);
        }
    }
    
    /**
     * Handle ignore issue AJAX request
     */
    public function handle_ignore_issue() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        $issue_id = isset($_POST['issue_id']) ? intval($_POST['issue_id']) : 0;
        
        if (!$issue_id) {
            wp_send_json_error(['message' => 'Invalid issue ID']);
            return;
        }
        
        // Try to find the issue in vmpfence_scan_issues table (all issues are now stored here)
        global $wpdb;
        $issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // Check if the issue exists
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $issue = $wpdb->get_row($wpdb->prepare(
            "SELECT id FROM {$issues_table} WHERE id = %d",
            $issue_id
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if ($issue) {
            // Update the issue status to ignored
            $result = $wpdb->update(
                $issues_table,
                [
                    'status' => 'ignored',
                    'resolved_time' => current_time('mysql')
                ],
                ['id' => $issue_id],
                ['%s', '%s'],
                ['%d']
            );
        } else {
            wp_send_json_error(['message' => 'Issue not found']);
            return;
        }
        
        if ($result === false) {
            wp_send_json_error(['message' => 'Failed to ignore issue']);
            return;
        }
        
        // Get updated counts from vmpfence_scan_issues table (all scanners)
        require_once VMPFENCE_PLUGIN_DIR . 'includes/class-file-scanner.php';
        $scanner = new VMPFence_File_Scanner();
        $file_active_count = $scanner->get_active_issues_count();
        $file_ignored_count = $scanner->get_ignored_issues_count();
        
        $user_active_count = count($this->get_user_security_issues('new'));
        $user_ignored_count = count($this->get_user_security_issues('ignored'));
        
        $content_active_count = count($this->get_content_safety_issues('new'));
        $content_ignored_count = count($this->get_content_safety_issues('ignored'));
        
        $public_files_active_count = count($this->get_public_files_issues('new'));
        $public_files_ignored_count = count($this->get_public_files_issues('ignored'));
        
        $user_audit_active_count = count($this->get_user_audit_issues('new'));
        $user_audit_ignored_count = count($this->get_user_audit_issues('ignored'));
        
        $vulnerability_active_count = count($this->get_vulnerability_issues('new'));
        $vulnerability_ignored_count = count($this->get_vulnerability_issues('ignored'));
        
        $malware_active_count = count($this->get_malware_issues('new'));
        $malware_ignored_count = count($this->get_malware_issues('ignored'));
        
        $server_state_active_count = count($this->get_server_state_issues('new'));
        $server_state_ignored_count = count($this->get_server_state_issues('ignored'));
        
        $reputation_active_count = count($this->get_reputation_issues('new'));
        $reputation_ignored_count = count($this->get_reputation_issues('ignored'));
        
        $total_active = $file_active_count + $user_active_count + $content_active_count + $public_files_active_count + $user_audit_active_count + $vulnerability_active_count + $malware_active_count + $server_state_active_count + $reputation_active_count;
        $total_ignored = $file_ignored_count + $user_ignored_count + $content_ignored_count + $public_files_ignored_count + $user_audit_ignored_count + $vulnerability_ignored_count + $malware_ignored_count + $server_state_ignored_count + $reputation_ignored_count;
        
        wp_send_json_success([
            'message' => 'Issue ignored successfully',
            'active_count' => $total_active,
            'ignored_count' => $total_ignored
        ]);
    }
    
    /**
     * Handle unignore issue AJAX request
     */
    public function handle_unignore_issue() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        $issue_id = isset($_POST['issue_id']) ? intval($_POST['issue_id']) : 0;
        
        if (!$issue_id) {
            wp_send_json_error(['message' => 'Invalid issue ID']);
            return;
        }
        
        // Try to find the issue in vmpfence_scan_issues table (all issues are now stored here)
        global $wpdb;
        $issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // Check if the issue exists
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $issue = $wpdb->get_row($wpdb->prepare(
            "SELECT id FROM {$issues_table} WHERE id = %d",
            $issue_id
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if ($issue) {
            // Update the issue status back to new (unignore)
            $result = $wpdb->update(
                $issues_table,
                [
                    'status' => 'new',
                    'resolved_time' => null
                ],
                ['id' => $issue_id],
                ['%s', '%s'],
                ['%d']
            );
        } else {
            wp_send_json_error(['message' => 'Issue not found']);
            return;
        }
        
        if ($result === false) {
            wp_send_json_error(['message' => 'Failed to unignore issue']);
            return;
        }
        
        // Get updated counts from vmpfence_scan_issues table (all scanners)
        require_once VMPFENCE_PLUGIN_DIR . 'includes/class-file-scanner.php';
        $scanner = new VMPFence_File_Scanner();
        $file_active_count = $scanner->get_active_issues_count();
        $file_ignored_count = $scanner->get_ignored_issues_count();
        
        $user_active_count = count($this->get_user_security_issues('new'));
        $user_ignored_count = count($this->get_user_security_issues('ignored'));
        
        $content_active_count = count($this->get_content_safety_issues('new'));
        $content_ignored_count = count($this->get_content_safety_issues('ignored'));
        
        $public_files_active_count = count($this->get_public_files_issues('new'));
        $public_files_ignored_count = count($this->get_public_files_issues('ignored'));
        
        $user_audit_active_count = count($this->get_user_audit_issues('new'));
        $user_audit_ignored_count = count($this->get_user_audit_issues('ignored'));
        
        $vulnerability_active_count = count($this->get_vulnerability_issues('new'));
        $vulnerability_ignored_count = count($this->get_vulnerability_issues('ignored'));
        
        $malware_active_count = count($this->get_malware_issues('new'));
        $malware_ignored_count = count($this->get_malware_issues('ignored'));
        
        $server_state_active_count = count($this->get_server_state_issues('new'));
        $server_state_ignored_count = count($this->get_server_state_issues('ignored'));
        
        $reputation_active_count = count($this->get_reputation_issues('new'));
        $reputation_ignored_count = count($this->get_reputation_issues('ignored'));
        
        $total_active = $file_active_count + $user_active_count + $content_active_count + $public_files_active_count + $user_audit_active_count + $vulnerability_active_count + $malware_active_count + $server_state_active_count + $reputation_active_count;
        $total_ignored = $file_ignored_count + $user_ignored_count + $content_ignored_count + $public_files_ignored_count + $user_audit_ignored_count + $vulnerability_ignored_count + $malware_ignored_count + $server_state_ignored_count + $reputation_ignored_count;
        
        wp_send_json_success([
            'message' => 'Issue unignored successfully',
            'active_count' => $total_active,
            'ignored_count' => $total_ignored
        ]);
    }
    
    /**
     * Get file changes issues from vmpfence_scan_issues table with proper headings
     *
     * @param string $status Issue status ('new', 'ignored')
     * @return array Array of file changes issues with proper formatting
     */
    private function get_file_changes_issues($status = 'new') {
        global $wpdb;
        
        $issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $issues = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$issues_table} WHERE issue_type = %s AND status = %s ORDER BY detected_time DESC",
            'file_changes',
            $status
        ), ARRAY_A);
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        // Format issues with proper headings based on type
        $formatted_issues = [];
        foreach ($issues as $issue) {
            $issue['issue_type'] = $issue['issue_type']; // Broad category from database
            $issue['type'] = $issue['type']; // Specific type from database
            $issue['violation_type'] = $issue['type']; // For compatibility
            $issue['heading'] = $this->get_file_changes_heading($issue['type']);
            $issue['type_display'] = $this->get_file_changes_type_display($issue['type']);
            $formatted_issues[] = $issue;
        }
        
        return $formatted_issues;
    }
    
    /**
     * Get proper heading for file changes issue based on type
     *
     * @param string $type The specific issue type (plugin_update, theme_update, etc.)
     * @return string User-friendly heading
     */
    private function get_file_changes_heading($type) {
        switch ($type) {
            case 'plugin_update':
                return 'Plugin Update Available';
            case 'theme_update':
                return 'Theme Update Available';
            case 'theme_suspicious':
            case 'theme_suspicious_file':
                return 'Theme Integrity Issue';
            case 'plugin_suspicious':
            case 'plugin_suspicious_file':
                return 'Plugin Integrity Issue';
            case 'modified':
                return 'Core File Modified';
            case 'unknown':
                return 'Unknown File Detected';
            case 'core_unknown':
                return 'Unknown Core File Detected';
            case 'skipped_path':
                return 'Scan Path Skipped';
            default:
                return 'File Changes Issue';
        }
    }
    
    /**
     * Get display name for file changes type
     *
     * @param string $type The specific issue type
     * @return string Display name for the type
     */
    private function get_file_changes_type_display($type) {
        switch ($type) {
            case 'plugin_update':
                return 'plugin_update';
            case 'theme_update':
                return 'theme_update';
            case 'theme_suspicious':
            case 'theme_suspicious_file':
                return 'theme_integrity';
            case 'plugin_suspicious':
            case 'plugin_suspicious_file':
                return 'plugin_integrity';
            case 'modified':
                return 'core_modified';
            case 'unknown':
                return 'core_unknown';
            case 'core_unknown':
                return 'core_unknown_file';
            case 'skipped_path':
                return 'skipped_path';
            default:
                return $type;
        }
    }
    
    /**
     * Get user security issues from vmpfence_scan_issues table
     *
     * @param string $status Issue status ('new', 'ignored')
     * @return array Array of user security issues
     */
    private function get_user_security_issues($status = 'new') {
        global $wpdb;
        
        $issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $issues = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$issues_table} WHERE issue_type = %s AND status = %s ORDER BY detected_time DESC",
            'user_security',
            $status
        ), ARRAY_A);
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if (!$issues) {
            return [];
        }
        
        // Convert to format expected by frontend
        $formatted_issues = [];
        foreach ($issues as $issue) {
            $formatted_issues[] = [
                'id' => $issue['id'],
                'issue_type' => $issue['issue_type'], // Broad category (user_security)
                'type' => $issue['type'] ?: 'user_security', // Specific type
                'violation_type' => $issue['type'] ?: 'user_security', // For compatibility
                'severity' => $issue['severity'], // Already text in new table
                'file_path' => $issue['file_path'] ?: 'User Account',
                'description' => $issue['description'],
                'detected_time' => $issue['detected_time'],
                'status' => $issue['status'],
                'resolved_time' => $status === 'ignored' ? $issue['resolved_time'] : null,
                'source_table' => 'vmpfence_scan_issues' // Mark source for ignore/unignore operations
            ];
        }
        
        return $formatted_issues;
    }
    
    /**
     * Get content safety issues from vmpfence_scan_issues table
     *
     * @param string $status Issue status ('new', 'ignored')
     * @return array Array of content safety issues
     */
    private function get_content_safety_issues($status = 'new') {
        global $wpdb;
        
        $issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $issues = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$issues_table} WHERE issue_type = %s AND status = %s ORDER BY detected_time DESC",
            'content_safety',
            $status
        ), ARRAY_A);
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if (!$issues) {
            return [];
        }
        
        // Convert to format expected by frontend
        $formatted_issues = [];
        foreach ($issues as $issue) {
            $formatted_issues[] = [
                'id' => $issue['id'],
                'issue_type' => $issue['issue_type'], // Broad category (content_safety)
                'type' => $issue['type'] ?: 'content_safety', // Specific violation type
                'violation_type' => $issue['type'] ?: 'content_safety', // For compatibility
                'severity' => $issue['severity'], // Already text in new table
                'file_path' => $issue['file_path'] ?: 'Content',
                'description' => $issue['description'],
                'detected_time' => $issue['detected_time'],
                'status' => $issue['status'],
                'resolved_time' => $status === 'ignored' ? $issue['resolved_time'] : null,
                'source_table' => 'vmpfence_scan_issues' // Mark source for ignore/unignore operations
            ];
        }
        
        return $formatted_issues;
    }
    
    /**
     * Map numeric severity to text
     *
     * @param int $severity Numeric severity
     * @return string Text severity
     */
    private function map_numeric_severity_to_text($severity) {
        switch ((int) $severity) {
            case 10:
                return 'critical';
            case 7:
                return 'high';
            case 5:
                return 'medium';
            case 3:
                return 'low';
            case 4:  // Legacy mapping for older file scanner
                return 'critical';
            case 2:  // Legacy mapping for older file scanner
                return 'medium';
            case 1:  // Legacy mapping for older file scanner
                return 'low';
            default:
                return 'medium';
        }
    }
    
    /**
     * Limit debug log size to prevent database packet size errors
     *
     * @param array $debug_log Debug log array
     * @return array Truncated debug log
     */
    private function limit_debug_log_size($debug_log) {
        if (!is_array($debug_log)) {
            return [];
        }
        
        // Since we're saving debug logs separately now, we can keep more entries
        // Limit to maximum 200 entries per scanner
        $max_entries = 200;
        
        if (count($debug_log) > $max_entries) {
            // Keep first 100 and last 100 entries
            $first_half = array_slice($debug_log, 0, 100);
            $last_half = array_slice($debug_log, -100);
            $truncated_log = array_merge(
                $first_half,
                ['... [' . (count($debug_log) - 200) . ' entries truncated] ...'],
                $last_half
            );
            return $truncated_log;
        }
        
        return $debug_log;
    }
    
    /**
     * Get public files issues from vmpfence_scan_issues table
     *
     * @param string $status Status filter ('new', 'ignored', or 'all')
     * @return array Formatted public files issues
     */
    private function get_public_files_issues($status = 'new') {
        global $wpdb;
        
        $issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $issues = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$issues_table} WHERE issue_type = %s AND status = %s ORDER BY detected_time DESC",
            'public_files',
            $status
        ), ARRAY_A);
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if (!$issues) {
            return [];
        }
        
        // Convert to format expected by frontend
        $formatted_issues = [];
        foreach ($issues as $issue) {
            $formatted_issues[] = [
                'id' => $issue['id'],
                'issue_type' => $issue['issue_type'], // Broad category (public_files)
                'type' => $issue['type'] ?: 'public_files', // Specific type
                'violation_type' => $issue['type'] ?: 'public_files', // For compatibility
                'severity' => $issue['severity'], // Already text in new table
                'file_path' => $issue['file_path'],
                'file_url' => '', // Not stored separately in new table
                'description' => $issue['description'],
                'detected_time' => $issue['detected_time'],
                'status' => $issue['status'],
                'resolved_time' => $status === 'ignored' ? $issue['resolved_time'] : null,
                'source_table' => 'vmpfence_scan_issues' // Mark source for ignore/unignore operations
            ];
        }
        
        return $formatted_issues;
    }
    
    /**
     * Get user audit issues from vmpfence_scan_issues table
     *
     * @param string $status Status filter ('new', 'ignored', or 'all')
     * @return array Formatted user audit issues
     */
    private function get_user_audit_issues($status = 'new') {
        global $wpdb;
        
        $issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $issues = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$issues_table} WHERE issue_type = %s AND status = %s ORDER BY detected_time DESC",
            'user_audit',
            $status
        ), ARRAY_A);
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if (!$issues) {
            return [];
        }
        
        // Convert to format expected by frontend
        $formatted_issues = [];
        foreach ($issues as $issue) {
            $formatted_issues[] = [
                'id' => $issue['id'],
                'issue_type' => $issue['issue_type'], // Broad category (user_audit)
                'type' => $issue['type'] ?: 'user_audit', // Specific type
                'violation_type' => $issue['type'] ?: 'user_audit', // For compatibility
                'severity' => $issue['severity'], // Already text in new table
                'username' => '', // Extracted from file_path if needed
                'user_id' => '',
                'user_email' => '',
                'option_name' => '',
                'suspicious_url' => '',
                'file_path' => $issue['file_path'],
                'description' => $issue['description'],
                'detected_time' => $issue['detected_time'],
                'status' => $issue['status'],
                'resolved_time' => $status === 'ignored' ? $issue['resolved_time'] : null,
                'source_table' => 'vmpfence_scan_issues' // Mark source for ignore/unignore operations
            ];
        }
        
        return $formatted_issues;
    }
    
    private function get_vulnerability_issues($status) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_scan_issues';
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $sql = $wpdb->prepare("SELECT * FROM $table_name WHERE issue_type = %s AND status = %s ORDER BY detected_time DESC", 'vulnerability', $status);
        $results = $wpdb->get_results($sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        // Transform to expected format
        $formatted_results = array();
        foreach ($results as $row) {
            $formatted_results[] = array(
                'id' => $row['id'],
                'message' => $row['description'],
                'severity' => $row['severity'],
                'type' => $row['type'] ?: 'vulnerability', // Specific vulnerability type
                'issue_type' => $row['issue_type'], // Broad category (vulnerability)
                'violation_type' => $row['type'] ?: 'vulnerability', // For compatibility
                'file_path' => $row['file_path'],
                'line_number' => null, // Not stored in this table
                'created_at' => $row['detected_time'],
                'detected_time' => $row['detected_time'], // For JavaScript compatibility
                'resolved_time' => $row['resolved_time'], // For ignored issues
                'source_table' => 'vmpfence_scan_issues'
            );
        }
        return $formatted_results;
    }
    
    private function get_malware_issues($status) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_scan_issues';
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $sql = $wpdb->prepare("SELECT * FROM $table_name WHERE issue_type = %s AND status = %s ORDER BY detected_time DESC", 'malware', $status);
        $results = $wpdb->get_results($sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        // Transform to expected format
        $formatted_results = array();
        foreach ($results as $row) {
            $formatted_results[] = array(
                'id' => $row['id'],
                'message' => $row['description'],
                'description' => $row['description'], // JavaScript expects this field
                'severity' => $row['severity'],
                'type' => $row['type'] ?: 'malware', // Specific malware type
                'issue_type' => $row['issue_type'], // Broad category (malware)
                'violation_type' => $row['type'] ?: 'malware', // For compatibility
                'file_path' => $row['file_path'],
                'line_number' => null, // Not stored in this table
                'created_at' => $row['detected_time'],
                'detected_time' => $row['detected_time'], // For JavaScript compatibility
                'resolved_time' => $row['resolved_time'], // For ignored issues
                'source_table' => 'vmpfence_scan_issues'
            );
        }
        return $formatted_results;
    }
    
    private function get_reputation_issues($status) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_scan_issues';
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $sql = $wpdb->prepare("SELECT * FROM $table_name WHERE issue_type = %s AND status = %s ORDER BY detected_time DESC", 'reputation', $status);
        $results = $wpdb->get_results($sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        // Transform to expected format
        $formatted_results = array();
        foreach ($results as $row) {
            $formatted_results[] = array(
                'id' => $row['id'],
                'message' => $row['description'],
                'description' => $row['description'], // JavaScript expects this field
                'severity' => $row['severity'],
                'type' => $row['type'] ?: 'reputation', // Specific reputation type
                'issue_type' => $row['issue_type'], // Broad category (reputation)
                'violation_type' => $row['type'] ?: 'reputation', // For compatibility
                'file_path' => $row['file_path'],
                'line_number' => null, // Not stored in this table
                'created_at' => $row['detected_time'],
                'detected_time' => $row['detected_time'], // For JavaScript compatibility
                'resolved_time' => $row['resolved_time'], // For ignored issues
                'source_table' => 'vmpfence_scan_issues'
            );
        }
        return $formatted_results;
    }
    
    private function get_server_state_issues($status) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $sql = $wpdb->prepare("SELECT * FROM $table_name WHERE issue_type = %s AND status = %s ORDER BY detected_time DESC", 'server_state', $status);
        
        $results = $wpdb->get_results($sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        // if ($wpdb->last_error) {
        //     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
        //     error_log("VMPFence AJAX: Database error: " . $wpdb->last_error);
        // }
        
        // Transform to expected format
        $formatted_results = array();
        foreach ($results as $row) {
            $formatted_results[] = array(
                'id' => $row['id'],
                'message' => $row['description'],
                'description' => $row['description'], // Add description field for JavaScript
                'severity' => $row['severity'],
                'type' => $row['type'] ?: 'server_state', // Specific server state type
                'issue_type' => $row['issue_type'], // Broad category (server_state)
                'violation_type' => $row['type'] ?: 'server_state', // For compatibility
                'file_path' => $row['file_path'],
                'line_number' => null, // Not stored in this table
                'created_at' => $row['detected_time'],
                'detected_time' => $row['detected_time'], // For JavaScript compatibility
                'resolved_time' => $row['resolved_time'], // For ignored issues
                'source_table' => 'vmpfence_scan_issues'
            );
        }
        
        return $formatted_results;
    }
    
    /**
     * Build scan completion data from live database results
     * This prioritizes fresh database data over cached results
     */
    private function build_scan_completion_from_database($all_active_issues, $all_ignored_issues) {
        global $wpdb;
        
        // If no active issues in database, return empty
        if (empty($all_active_issues)) {
            return [];
        }
        
        // Get the latest scan results from vmpfence_scan_results table
        $scan_results_table = $wpdb->prefix . 'vmpfence_scan_results';
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $latest_scan_results = $wpdb->get_results(
            "SELECT * FROM $scan_results_table ORDER BY scan_time DESC LIMIT 20",
            ARRAY_A
        );
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        // Group by scan_type to get the latest result for each scanner
        $grouped_results = [];
        foreach ($latest_scan_results as $result) {
            $scan_type = $result['scan_type'];
            if (!isset($grouped_results[$scan_type])) {
                $grouped_results[$scan_type] = $result;
            }
        }
        
        $scan_results = [];
        $total_issues = count($all_active_issues);
        $scans_completed = 0;
        
        // Process each scanner's results
        foreach ($grouped_results as $scan_type => $result) {
            $scans_completed++;
            
            // Get issues for this scan type
            $scanner_issues = array_filter($all_active_issues, function($issue) use ($scan_type) {
                // Handle both 'type' and 'issue_type' keys for compatibility
                $issue_type = isset($issue['issue_type']) ? $issue['issue_type'] : 
                             (isset($issue['type']) ? $issue['type'] : '');
                return $issue_type === $scan_type || 
                       (isset($issue['scan_type']) && $issue['scan_type'] === $scan_type);
            });
            
            $issues_found = count($scanner_issues);
            
            // Build scan result structure based on scan type
            switch ($scan_type) {
                case 'server_state':
                    // Read checks_performed from scan_data JSON
                    $scan_data = isset($result['scan_data']) ? json_decode($result['scan_data'], true) : [];
                    $checks_performed = isset($scan_data['checks_performed']) ? $scan_data['checks_performed'] : 0;
                    
                    $scan_results[$scan_type] = [
                        'results' => [
                            'scan_type' => 'server_state',
                            'scan_time' => $result['scan_time'],
                            'issues_found' => $issues_found,
                            'checks_performed' => $checks_performed
                        ],
                        'issues' => array_map(function($issue) {
                            return [
                                'severity' => $issue['severity'],
                                'title' => $this->extract_title_from_message($issue['message']),
                                'description' => $this->extract_description_from_message($issue['message']),
                                'solution' => $this->extract_solution_from_message($issue['message']),
                                'scan_type' => 'server_state'
                            ];
                        }, array_values($scanner_issues))
                    ];
                    break;
                    
                case 'file_changes':
                    $scan_data = json_decode($result['scan_data'], true);
                    $scan_results[$scan_type] = [
                        'scan_id' => $result['id'],
                        'core_files' => $scan_data['stats']['core_files_checked'] ?? 0,
                        'plugins' => $scan_data['stats']['plugins_checked'] ?? 0,
                        'themes' => $scan_data['stats']['themes_checked'] ?? 0,
                        'unknown_files' => 0,
                        'modified_files' => 0,
                        'new_files' => 0,
                        'deleted_files' => 0,
                        'scan_time' => $result['scan_time'],
                        'total_files_checked' => $result['total_files'],
                        'issues_found' => $issues_found,
                        'scan_status' => 'completed'
                    ];
                    break;
                    
                case 'user_security':
                    $scan_data = json_decode($result['scan_data'], true);
                    // The scanner data seems to be storing issues count instead of users scanned
                    // Use actual database count for accuracy
                    $actual_users_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->users}");
                    $scan_results[$scan_type] = [
                        'success' => 1,
                        'issues_found' => $issues_found,
                        'scan_time' => $result['scan_time'],
                        'scan_type' => $scan_type,
                        'users_checked' => intval($actual_users_count)  // Use actual user count from database
                    ];
                    break;
                    
                case 'content_safety':
                    $scan_data = json_decode($result['scan_data'], true);
                    // Use actual database count for content items checked
                    $content_items = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status = 'publish'") + 
                                   $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_approved = '1'");
                    $scan_results[$scan_type] = [
                        'success' => 1,
                        'issues_found' => $issues_found,
                        'scan_time' => $result['scan_time'],
                        'scan_type' => $scan_type,
                        'content_items_checked' => intval($content_items)  // Use actual database count
                    ];
                    break;
                    
                default:
                    // For other scanners, use a generic structure
                    $scan_results[$scan_type] = [
                        'success' => 1,
                        'issues_found' => $issues_found,
                        'scan_time' => $result['scan_time'],
                        'scan_type' => $scan_type
                    ];
                    break;
            }
        }
        
        // Get actual user and content statistics from scan results or database
        $users_checked = 0;
        $content_items_checked = 0;
        
        // Try to get from scan results first
        if (isset($scan_results['user_security']['users_checked'])) {
            $users_checked = $scan_results['user_security']['users_checked'];
        } else {
            // Fallback: count actual users in database
            $users_checked = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->users}");
        }
        
        if (isset($scan_results['content_safety']['content_items_checked'])) {
            $content_items_checked = $scan_results['content_safety']['content_items_checked'];
        } else {
            // Fallback: count actual posts and comments in database
            $posts_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status = 'publish'");
            $comments_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_approved = '1'");
            $content_items_checked = intval($posts_count) + intval($comments_count);
        }
        
        $completion_data = [
            'completed_time' => current_time('mysql'),
            'total_issues' => $total_issues,
            'scans_completed' => $scans_completed,
            'users_checked' => $users_checked,
            'content_items_checked' => $content_items_checked,
            'scan_results' => $scan_results
        ];
        return $completion_data;
    }
    
    /**
     * Extract title from combined message (before first colon)
     */
    private function extract_title_from_message($message) {
        $parts = explode(':', $message, 2);
        return trim($parts[0]);
    }
    
    /**
     * Extract description from combined message (between first colon and "Solution:")
     */
    private function extract_description_from_message($message) {
        if (strpos($message, 'Solution:') !== false) {
            $parts = explode('Solution:', $message, 2);
            $before_solution = $parts[0];
            $desc_parts = explode(':', $before_solution, 2);
            return isset($desc_parts[1]) ? trim($desc_parts[1]) : $message;
        }
        
        $parts = explode(':', $message, 2);
        return isset($parts[1]) ? trim($parts[1]) : $message;
    }
    
    /**
     * Extract solution from combined message (after "Solution:")
     */
    private function extract_solution_from_message($message) {
        if (strpos($message, 'Solution:') !== false) {
            $parts = explode('Solution:', $message, 2);
            return isset($parts[1]) ? trim($parts[1]) : 'Review and resolve this issue.';
        }
        return 'Review and resolve this issue.';
    }
    
    /**
     * Handle mark as fixed AJAX request
     */
    public function handle_mark_as_fixed() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        $issue_id = isset($_POST['issue_id']) ? intval($_POST['issue_id']) : 0;
        
        if (!$issue_id) {
            wp_send_json_error(['message' => 'Invalid issue ID']);
            return;
        }
        
        global $wpdb;
        $issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        $scan_results_table = $wpdb->prefix . 'vmpfence_scan_results';
        
        // First, get the issue details before deletion to know which scan type to update
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $issue = $wpdb->get_row($wpdb->prepare(
            "SELECT issue_type, scan_id FROM {$issues_table} WHERE id = %d",
            $issue_id
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if (!$issue) {
            wp_send_json_error(['message' => 'Issue not found']);
            return;
        }
        
        // Get the scan type from the scan results table
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $scan_result = $wpdb->get_row($wpdb->prepare(
            "SELECT scan_type, issues_found FROM {$scan_results_table} WHERE id = %d",
            $issue->scan_id
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if (!$scan_result) {
            wp_send_json_error(['message' => 'Scan result not found']);
            return;
        }
        
        // Delete the issue from database
        $result = $wpdb->delete(
            $issues_table,
            ['id' => $issue_id],
            ['%d']
        );
        
        if ($result === false) {
            wp_send_json_error(['message' => 'Failed to mark issue as fixed']);
            return;
        }
        
        // Update the issues_found count in scan_results table (decrement by 1)
        $new_issues_count = max(0, intval($scan_result->issues_found) - 1); // Prevent negative counts
        
        $update_result = $wpdb->update(
            $scan_results_table,
            ['issues_found' => $new_issues_count],
            ['id' => $issue->scan_id],
            ['%d'],
            ['%d']
        );
        
        // Recalculate issue counts after deletion - use direct queries
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $active_count = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$issues_table} WHERE status = %s",
            'new'
        ));
        
        $ignored_count = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$issues_table} WHERE status = %s",
            'ignored'
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        wp_send_json_success([
            'message' => 'Issue marked as fixed successfully',
            'active_count' => intval($active_count),
            'ignored_count' => intval($ignored_count),
            'updated_scan_type' => $scan_result->scan_type,
            'new_issues_count' => $new_issues_count
        ]);
    }

    /**
     * Handle get scan status AJAX request
     */
    public function handle_get_scan_status() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        global $wpdb;
        
        try {
            // Get scan results from database
            $scan_results_table = $wpdb->prefix . 'vmpfence_scan_results';
            
            // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
            // Check if table exists
            $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$scan_results_table'") === $scan_results_table;
            // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            
            if (!$table_exists) {
                // No table means fresh installation - all grey
                wp_send_json_success(['has_data' => false, 'status_data' => []]);
                return;
            }
            
            // Get the latest scan result for each scan_type where scan_status = 'completed'
            // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
            $results = $wpdb->get_results(
                "SELECT sr1.scan_type, sr1.issues_found, sr1.scan_status 
                FROM $scan_results_table sr1
                INNER JOIN (
                    SELECT scan_type, MAX(id) as max_id
                    FROM $scan_results_table 
                    WHERE scan_status = 'completed'
                    GROUP BY scan_type
                ) sr2 ON sr1.scan_type = sr2.scan_type AND sr1.id = sr2.max_id
                WHERE sr1.scan_status = 'completed'",
                ARRAY_A
            );
            // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            
            if (empty($results)) {
                // No completed scans - all grey
                wp_send_json_success(['has_data' => false, 'status_data' => []]);
                return;
            }
            
            // Build status data based on actual database results
            $status_data = [];
            
            // Mapping from database scan_type to UI data-scan-type attributes
            $scan_type_mapping = [
                'malware' => 'malware_scan',
                'vulnerability' => 'vulnerability_scan', 
                'user_audit' => 'user_option_audit',
                // Reputation scan types (direct mappings to UI elements)
                'blocklist_check' => 'blocklist_check',
                'spamvertising_checks' => 'spamvertising_checks',
                'spam_check' => 'spam_check',
                // Direct mappings (no change needed)
                'server_state' => 'server_state',
                'file_changes' => 'file_changes',
                'user_security' => 'user_security',
                'content_safety' => 'content_safety',
                'public_files' => 'public_files'
            ];
            
            foreach ($results as $result) {
                $db_scan_type = $result['scan_type'];
                $issues_found = intval($result['issues_found']);
                
                // Map database scan type to UI scan type
                $ui_scan_type = isset($scan_type_mapping[$db_scan_type]) 
                    ? $scan_type_mapping[$db_scan_type] 
                    : $db_scan_type;
                
                $status_data[$ui_scan_type] = $issues_found;
            }
            wp_send_json_success(['has_data' => true, 'status_data' => $status_data]);
            
        } catch (Exception $e) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
            // error_log('VMPFence: Error getting scan status: ' . $e->getMessage());
            wp_send_json_error(['message' => 'Failed to get scan status']);
        }
    }

    /**
     * Handle store scan status AJAX request
     */
    public function handle_store_scan_status() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        // Get and decode the status data
        $status_data_raw = isset($_POST['status_data']) ? sanitize_text_field(wp_unslash($_POST['status_data'])) : '';
        $status_data = json_decode(stripslashes($status_data_raw), true);
        
        if (!$status_data) {
            wp_send_json_error(['message' => 'Invalid status data']);
            return;
        }
        
        // Store the scan status
        $result = update_option('vmpfence_scan_status', $status_data);
        
        if ($result) {
            wp_send_json_success(['message' => 'Scan status stored successfully']);
        } else {
            wp_send_json_error(['message' => 'Failed to store scan status']);
        }
    }
    
    /**
     * Handle get security events AJAX request
     */
    public function handle_get_security_events() {
        // Verify nonce
        $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        try {
            global $wpdb;
            
            $limit = intval($_REQUEST['limit'] ?? 50);
            $offset = intval($_REQUEST['offset'] ?? 0);
            $severity = isset($_REQUEST['severity']) ? sanitize_text_field(wp_unslash($_REQUEST['severity'])) : '';
            $event_type = isset($_REQUEST['event_type']) ? sanitize_text_field(wp_unslash($_REQUEST['event_type'])) : '';
            
            $table = $wpdb->prefix . 'vmpfence_events';
            $where_conditions = ['1=1'];
            $where_values = [];
            
            if (!empty($severity)) {
                $severity_num = $this->map_severity_text_to_number($severity);
                $where_conditions[] = 'severity = %d';
                $where_values[] = $severity_num;
            }
            
            if (!empty($event_type)) {
                $where_conditions[] = 'event_type = %s';
                $where_values[] = $event_type;
            }
            
            $where_clause = implode(' AND ', $where_conditions);
            
            // Get total count
            $count_sql = "SELECT COUNT(*) FROM $table WHERE $where_clause";
            if (!empty($where_values)) {
                $count_sql = $wpdb->prepare($count_sql, ...$where_values); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            }
            $total_events = $wpdb->get_var($count_sql); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
            
            // Get events with pagination
            $events_sql = "SELECT * FROM $table WHERE $where_clause ORDER BY time DESC LIMIT %d OFFSET %d";
            $all_values = array_merge($where_values, [$limit, $offset]);
            $events_sql = $wpdb->prepare($events_sql, ...$all_values); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $events = $wpdb->get_results($events_sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
            
            // Format events data
            foreach ($events as &$event) {
                $event['formatted_time'] = gmdate('F j, Y g:i A', (int)$event['time']);
                $event['event_time'] = gmdate('Y-m-d H:i:s', (int)$event['time']);
                $event['severity_text'] = $this->map_severity_number_to_text($event['severity']);
                
                // Convert IP from binary
                if (!empty($event['source_ip'])) {
                    $event['ip_address'] = inet_ntop($event['source_ip']);
                }
                
                // Parse data JSON
                if (!empty($event['data'])) {
                    $event['event_data'] = json_decode($event['data'], true) ?: $event['data'];
                } else {
                    $event['event_data'] = [];
                }
            }
            
            wp_send_json_success([
                'events' => $events,
                'total' => $total_events,
                'has_more' => ($offset + $limit) < $total_events
            ]);
            
        } catch (Exception $e) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
            // error_log('VMPFence Security Events Error: ' . $e->getMessage());
            wp_send_json_error(['message' => 'Failed to retrieve security events: ' . $e->getMessage()]);
        }
    }
    
    /**
     * Handle get login attempts AJAX request
     * @since 2.0.0 -- Decrepited `vmpfence_failed_logins` table replaced with `vmpfence_security_attempts`
     */
    public function handle_get_login_attempts() {
        // Verify nonce
        $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        try {
            global $wpdb;
            
            $limit = intval($_REQUEST['limit'] ?? 50);
            $offset = intval($_REQUEST['offset'] ?? 0);
            $hours = intval($_REQUEST['hours'] ?? 24); // Default to last 24 hours
            
            $table = $wpdb->prefix . 'vmpfence_security_attempts';
            $where_clause = 'attempt_time > DATE_SUB(NOW(), INTERVAL %d HOUR)';
            $where_values = [$hours];
            
            // Get total count
            $count_sql = "SELECT COUNT(*) FROM $table WHERE $where_clause";
            $count_sql = $wpdb->prepare($count_sql, ...$where_values); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $total_attempts = $wpdb->get_var($count_sql); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
            
            // Get attempts with pagination
            $attempts_sql = "SELECT * FROM $table WHERE $where_clause ORDER BY attempt_time DESC LIMIT %d OFFSET %d";
            $all_values = array_merge($where_values, [$limit, $offset]);
            $attempts_sql = $wpdb->prepare($attempts_sql, ...$all_values); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $attempts = $wpdb->get_results($attempts_sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
            
            // Format timestamps and add status
            foreach ($attempts as &$attempt) {
                $attempt['formatted_time'] = mysql2date('F j, Y g:i A', $attempt['attempt_time']);
                $attempt['status'] = 'failed';
                
                // Check if currently blocked
                $attempt['currently_blocked'] = $attempt['blocked_until'] && strtotime($attempt['blocked_until']) > time();
                if ($attempt['currently_blocked']) {
                    $attempt['formatted_blocked_until'] = mysql2date('F j, Y g:i A', $attempt['blocked_until']);
                }
            }
            
            // Get summary statistics
            $stats_sql = "SELECT 
                COUNT(*) as failed_count,
                COUNT(CASE WHEN blocked_until > NOW() THEN 1 END) as currently_blocked
                FROM $table 
                WHERE attempt_time > DATE_SUB(NOW(), INTERVAL %d HOUR)";
            $stats_sql = $wpdb->prepare($stats_sql, $hours); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $stats_result = $wpdb->get_row($stats_sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
            
            $stats = [
                'failed' => intval($stats_result['failed_count']),
                'blocked' => intval($stats_result['currently_blocked'])
            ];
            
            wp_send_json_success([
                'attempts' => $attempts,
                'total' => $total_attempts,
                'stats' => $stats,
                'has_more' => ($offset + $limit) < $total_attempts
            ]);
            
        } catch (Exception $e) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
            // error_log('VMPFence Login Attempts Error: ' . $e->getMessage());
            wp_send_json_error(['message' => 'Failed to retrieve login attempts: ' . $e->getMessage()]);
        }
    }
    
    /**
     * Handle block IP AJAX request
     */
    public function handle_block_ip() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        try {
            $ip_address = isset($_POST['ip_address']) ? sanitize_text_field(wp_unslash($_POST['ip_address'])) : '';
            $reason = isset($_POST['reason']) ? sanitize_text_field(wp_unslash($_POST['reason'])) : 'Manual block';
            $duration = intval($_POST['duration'] ?? 24); // hours
            $block_type = (isset($_POST['block_type']) && $_POST['block_type'] === 'permanent') ? 'permanent' : 'temporary';
            
            if (empty($ip_address) || !filter_var($ip_address, FILTER_VALIDATE_IP)) {
                wp_send_json_error(['message' => 'Invalid IP address']);
                return;
            }
            
            global $wpdb;
            $table = $wpdb->prefix . 'vmpfence_blocked_ips';
            
            $blocked_until = null;
            if ($block_type === 'temporary') {
                $blocked_until = gmdate('Y-m-d H:i:s', time() + ($duration * 3600));
            }
            
            $result = $wpdb->replace($table, [
                'ip' => inet_pton($ip_address), // Store as binary
                'reason' => $reason,
                'blocked_at' => current_time('mysql'),
                'blocked_until' => $blocked_until,
                'blocked_by' => get_current_user_id()
            ]);
            
            if ($result !== false) {
                // Log the blocking event
                $this->log_security_event('ip_blocked', 'high', $ip_address, [
                    'block_type' => $block_type,
                    'blocked_until' => $blocked_until,
                    'reason' => $reason,
                    'blocked_by' => get_current_user_id()
                ]);
                do_action('vmpfence_ip_blocked', $ip_address, $reason, $block_type);
                
                wp_send_json_success([
                    'message' => "IP $ip_address has been blocked successfully",
                    'ip_address' => $ip_address,
                    'block_type' => $block_type,
                    'blocked_until' => $blocked_until
                ]);
            } else {
                wp_send_json_error(['message' => 'Failed to block IP address']);
            }
            
        } catch (Exception $e) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
            // error_log('VMPFence Block IP Error: ' . $e->getMessage());
            wp_send_json_error(['message' => 'Failed to block IP: ' . $e->getMessage()]);
        }
    }
    
    /**
     * Handle unblock IP AJAX request
     */
    public function handle_unblock_ip() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        try {
            $ip_address = isset($_POST['ip_address']) ? sanitize_text_field(wp_unslash($_POST['ip_address'])) : '';
            
            if (empty($ip_address) || !filter_var($ip_address, FILTER_VALIDATE_IP)) {
                wp_send_json_error(['message' => 'Invalid IP address']);
                return;
            }
            
            global $wpdb;
            $table = $wpdb->prefix . 'vmpfence_blocked_ips';
            
            // Set blocked_until to past time instead of deleting
            $result = $wpdb->update($table, [
                'blocked_until' => gmdate('Y-m-d H:i:s', time() - 1)
            ], ['ip' => inet_pton($ip_address)], ['%s'], ['%s']);
            
            if ($result !== false) {
                // Log the unblocking event
                $this->log_security_event('ip_unblocked', 'medium', $ip_address, [
                    'unblocked_by' => get_current_user_id()
                ]);
                do_action('vmpfence_ip_unblocked', $ip_address);
                
                wp_send_json_success([
                    'message' => "IP $ip_address has been unblocked successfully",
                    'ip_address' => $ip_address
                ]);
            } else {
                wp_send_json_error(['message' => 'Failed to unblock IP address']);
            }
            
        } catch (Exception $e) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
            // error_log('VMPFence Unblock IP Error: ' . $e->getMessage());
            wp_send_json_error(['message' => 'Failed to unblock IP: ' . $e->getMessage()]);
        }
    }
    
    /**
     * Handle get blocked IPs AJAX request
     */
    public function handle_get_blocked_ips() {
        // Verify nonce
        $nonce = isset($_REQUEST['nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        try {
            global $wpdb;
            
            $limit = intval($_REQUEST['limit'] ?? 50);
            $offset = intval($_REQUEST['offset'] ?? 0);
            $show_expired = (isset($_REQUEST['show_expired']) && $_REQUEST['show_expired'] === 'true');
            
            $table = $wpdb->prefix . 'vmpfence_blocked_ips';
            $where_conditions = ['1=1'];
            $where_values = [];
            
            if (!$show_expired) {
                // Only show active blocks (not expired)
                $where_conditions[] = '(blocked_until IS NULL OR blocked_until > NOW())';
            }
            
            $where_clause = implode(' AND ', $where_conditions);
            
            // Get total count
            $count_sql = "SELECT COUNT(*) FROM $table WHERE $where_clause";
            if (!empty($where_values)) {
                $count_sql = $wpdb->prepare($count_sql, ...$where_values); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            }
            $total_blocked = $wpdb->get_var($count_sql); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
            
            // Get blocked IPs with pagination
            $blocked_sql = "SELECT * FROM $table WHERE $where_clause ORDER BY blocked_at DESC LIMIT %d OFFSET %d";
            $all_values = array_merge($where_values, [$limit, $offset]);
            $blocked_sql = $wpdb->prepare($blocked_sql, ...$all_values); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
            $blocked_ips = $wpdb->get_results($blocked_sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
            
            // Format timestamps and add status
            foreach ($blocked_ips as &$blocked) {
                // Convert IP from binary to readable format
                if (!empty($blocked['ip'])) {
                    $blocked['ip_address'] = inet_ntop($blocked['ip']);
                }
                
                $blocked['formatted_blocked_at'] = mysql2date('F j, Y g:i A', $blocked['blocked_at']);
                $blocked['formatted_blocked_until'] = $blocked['blocked_until'] ? 
                    mysql2date('F j, Y g:i A', $blocked['blocked_until']) : 'Never';
                $blocked['is_active'] = !$blocked['blocked_until'] || strtotime($blocked['blocked_until']) > time();
                $blocked['is_expired'] = $blocked['blocked_until'] && strtotime($blocked['blocked_until']) < time();
                
                // Get blocked user info if available
                if (!empty($blocked['blocked_by'])) {
                    $user = get_user_by('ID', $blocked['blocked_by']);
                    $blocked['blocked_by_name'] = $user ? $user->display_name : 'Unknown';
                } else {
                    $blocked['blocked_by_name'] = 'System';
                }
            }
            
            // Get summary statistics
            $stats_sql = "SELECT 
                COUNT(*) as total,
                COUNT(CASE WHEN blocked_until IS NULL OR blocked_until > NOW() THEN 1 END) as active,
                COUNT(CASE WHEN blocked_until IS NOT NULL AND blocked_until <= NOW() THEN 1 END) as expired
                FROM $table";
            $stats = $wpdb->get_row($stats_sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- No variables to prepare
            
            wp_send_json_success([
                'blocked_ips' => $blocked_ips,
                'total' => $total_blocked,
                'stats' => [
                    'total' => intval($stats['total']),
                    'active' => intval($stats['active']),
                    'expired' => intval($stats['expired'])
                ],
                'has_more' => ($offset + $limit) < $total_blocked
            ]);
            
        } catch (Exception $e) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
            // error_log('VMPFence Blocked IPs Error: ' . $e->getMessage());
            wp_send_json_error(['message' => 'Failed to retrieve blocked IPs: ' . $e->getMessage()]);
        }
    }
    
    /**
     * Get recent security events for dashboard display
     */
    private function get_recent_security_events($limit = 50) {
        global $wpdb;
        $table = $wpdb->prefix . 'vmpfence_events';
        
        // Ensure table exists before querying (auto-create if missing)
        if (class_exists('VMPFence_Database') && !VMPFence_Database::events_table_exists()) {
            VMPFence_Database::ensure_events_table_exists();
        }
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $sql = $wpdb->prepare(
            "SELECT id, time, event_type, severity, source_ip, user_id, username, message, data
             FROM $table 
             WHERE time > UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 24 HOUR))
             ORDER BY time DESC 
             LIMIT %d",
            $limit
        );
        
        $events = $wpdb->get_results($sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        // Format data for display
        foreach ($events as &$event) {
            // Convert timestamp to readable format
            $event['formatted_time'] = gmdate('F j, Y g:i A', (int)$event['time']);
            $event['event_time'] = gmdate('Y-m-d H:i:s', (int)$event['time']);
            
            // Convert IP from binary to string
            if (!empty($event['source_ip'])) {
                $event['ip_address'] = inet_ntop($event['source_ip']);
            }
            
            // Parse data JSON if exists
            if (!empty($event['data'])) {
                $event['event_data'] = json_decode($event['data'], true) ?: $event['data'];
            } else {
                $event['event_data'] = [];
            }
            
            // Map severity number to text
            $event['severity_text'] = $this->map_severity_number_to_text($event['severity']);
        }
        
        return $events ?: [];
    }
    
    /**
     * Get recent login attempts for dashboard display
     * @since 2.0.0 -- Decrepited `vmpfence_failed_logins` table replaced with `vmpfence_security_attempts`
     */
    private function get_recent_login_attempts($limit = 50) {
        global $wpdb;
        $table = $wpdb->prefix . 'vmpfence_security_attempts';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $sql = $wpdb->prepare(
            "SELECT id, ip_address, username, event_type, user_agent, attempt_time, blocked_until, attempt_count, geo_location
             FROM $table 
             WHERE attempt_time > DATE_SUB(NOW(), INTERVAL 24 HOUR) 
             ORDER BY attempt_time DESC 
             LIMIT %d",
            $limit
        );
        
        $attempts = $wpdb->get_results($sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        // Format times and add status
        foreach ($attempts as &$attempt) {
            $attempt['formatted_time'] = mysql2date('F j, Y g:i A', $attempt['attempt_time']);
            $attempt['status'] = 'failed'; // All entries in this table are failed attempts
            
            // Check if currently blocked
            if ($attempt['blocked_until'] && strtotime($attempt['blocked_until']) > time()) {
                $attempt['currently_blocked'] = true;
                $attempt['formatted_blocked_until'] = mysql2date('F j, Y g:i A', $attempt['blocked_until']);
            } else {
                $attempt['currently_blocked'] = false;
                $attempt['formatted_blocked_until'] = null;
            }
        }
        
        return $attempts ?: [];
    }
    
    /**
     * Get active blocked IPs
     */
    private function get_active_blocked_ips($limit = 20) {
        global $wpdb;
        $table = $wpdb->prefix . 'vmpfence_blocked_ips';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $sql = $wpdb->prepare(
            "SELECT id, ip_address, reason, blocked_at, blocked_until, block_count, last_attempt
             FROM $table 
             WHERE blocked_until > NOW()
             ORDER BY blocked_at DESC 
             LIMIT %d",
            $limit
        );
        
        $blocked_ips = $wpdb->get_results($sql, ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        // Format times
        foreach ($blocked_ips as &$blocked) {
            $blocked['formatted_blocked_at'] = mysql2date('F j, Y g:i A', $blocked['blocked_at']);
            $blocked['formatted_blocked_until'] = mysql2date('F j, Y g:i A', $blocked['blocked_until']);
            $blocked['is_permanent'] = false; // This table uses blocked_until, so all are temporary
            $blocked['block_type'] = 'temporary';
        }
        
        return $blocked_ips ?: [];
    }
    
    /**
     * Count failed logins in last 24 hours
     * @since 2.0.0 -- Decrepited `vmpfence_failed_logins` table replaced with `vmpfence_security_attempts`
     */
    private function count_failed_logins_24h() {
        global $wpdb;
        $table = $wpdb->prefix . 'vmpfence_security_attempts';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $count = $wpdb->get_var( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Simple COUNT query without user input
            "SELECT COUNT(*) FROM $table 
             WHERE attempt_time > DATE_SUB(NOW(), INTERVAL 24 HOUR)"
        );
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        return intval($count);
    }
    
    /**
     * Map numeric severity to text
     */
    private function map_severity_number_to_text($severity) {
        switch (intval($severity)) {
            case 0: return 'info';
            case 1: return 'low';
            case 2: return 'medium';
            case 3: return 'high';
            case 4: return 'critical';
            default: return 'info';
        }
    }
    
    /**
     * Map severity text to number for database storage
     */
    private function map_severity_text_to_number($severity_text) {
        switch (strtolower($severity_text)) {
            case 'info': return 0;
            case 'low': return 1;
            case 'medium': return 2;
            case 'high': return 3;
            case 'critical': return 4;
            default: return 0; // Default to info
        }
    }
    
    /**
     * Clear all previous scan data from database before starting new scan
     * This ensures that only results from currently selected scanners appear
     */
    private function clear_all_previous_scan_data() {
        global $wpdb;
        
        $scan_issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        $scan_results_table = $wpdb->prefix . 'vmpfence_scan_results';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names from WordPress prefix
        // Check if tables exist
        $scan_issues_exists = $wpdb->get_var("SHOW TABLES LIKE '$scan_issues_table'");
        $scan_results_exists = $wpdb->get_var("SHOW TABLES LIKE '$scan_results_table'");
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if (!$scan_issues_exists || !$scan_results_exists) {
            return;
        }
        
        // Clear all previous scan issues (non-ignored only) from all scanners
         // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names from WordPress prefix
        $deleted_issues = $wpdb->query("DELETE FROM {$scan_issues_table} WHERE status != 'ignored'");
        
        // Clear all previous scan results from all scanners
        $deleted_results = $wpdb->query("DELETE FROM {$scan_results_table}");
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    }
    
    /**
     * Handle repair file AJAX request
     */
    public function handle_repair_file() {
        // Include the repair engine
        if (!class_exists('VMPFence_File_Repair_Engine')) {
            require_once VMPFENCE_PLUGIN_DIR . 'includes/class-file-repair-engine.php';
        }
        
        if (!class_exists('VMPFence_File_Repair_Engine')) {
            wp_send_json_error('Repair engine not available');
            return;
        }
        
        try {
            $repair_engine = new VMPFence_File_Repair_Engine();
            $repair_engine->handle_repair_file();
        } catch (Exception $e) {
            wp_send_json_error('Repair failed: ' . $e->getMessage());
        }
    }
    
    /**
     * Handle bulk repair AJAX request
     */
    public function handle_bulk_repair() {
        // Include the repair engine
        if (!class_exists('VMPFence_File_Repair_Engine')) {
            require_once VMPFENCE_PLUGIN_DIR . 'includes/class-file-repair-engine.php';
        }
        
        $repair_engine = new VMPFence_File_Repair_Engine();
        $repair_engine->handle_bulk_repair();
    }
    
    /**
     * Handle check repairability AJAX request
     */
    public function handle_check_repairability() {
        // Include the repair engine
        if (!class_exists('VMPFence_File_Repair_Engine')) {
            require_once VMPFENCE_PLUGIN_DIR . 'includes/class-file-repair-engine.php';
        }
        
        $repair_engine = new VMPFence_File_Repair_Engine();
        $repair_engine->handle_check_repairability();
    }
    
    /**
     * Handle file download request before repair
     */
    public function handle_download_file() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        $issue_id = isset($_POST['issue_id']) ? intval($_POST['issue_id']) : 0;
        
        if (!$issue_id) {
            wp_send_json_error(['message' => 'Invalid issue ID']);
            return;
        }
        
        global $wpdb;
        $issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // Get issue details
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $issue = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$issues_table} WHERE id = %d",
            $issue_id
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if (!$issue) {
            wp_send_json_error(['message' => 'Issue not found']);
            return;
        }
        
        // Build file path
        $file_path = ABSPATH . $issue->file_path;
        
        if (!file_exists($file_path) || !is_readable($file_path)) {
            wp_send_json_error(['message' => 'File not found or not readable']);
            return;
        }
        
        // Get clean filename for download
        $original_filename = basename($issue->file_path);
        $safe_filename = sanitize_file_name($original_filename);
        
        // Generate secure download token that expires in 10 minutes
        $download_token = wp_create_nonce('vmpfence_download_' . $issue_id . '_' . time());
        $expiry = time() + 600; // 10 minutes
        
        // Store download info temporarily in database
        $this->store_download_token($download_token, $issue_id, $file_path, $safe_filename, $expiry);
        
        // Generate download URL with token
        $download_url = admin_url('admin-ajax.php') . '?action=vmpfence_serve_file&token=' . urlencode($download_token);
        
        wp_send_json_success([
            'download_url' => $download_url,
            'filename' => $safe_filename,
            'message' => 'File prepared for download'
        ]);
    }

    /**
     * Handle get file view token request (for viewing malicious code files)
     */
    public function handle_get_file_view_token() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_send_json_error('Security check failed');
        }

        // Verify user capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
        }

        $issue_id = isset($_POST['issue_id']) ? intval($_POST['issue_id']) : 0;
        $file_path = isset($_POST['file_path']) ? sanitize_text_field(wp_unslash($_POST['file_path'])) : '';

        if (!$issue_id) {
            wp_send_json_error('Invalid issue ID');
        }

        // Get issue from database to verify it exists and get file path
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_scan_issues';
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $issue = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $issue_id), ARRAY_A);
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

        if (!$issue) {
            wp_send_json_error('Issue not found');
        }

        // Use file path from database if not provided
        if (empty($file_path)) {
            $file_path = $issue['file_path'];
        }

        if (empty($file_path)) {
            wp_send_json_error('File path not available');
        }

        // Security check: ensure file path is within WordPress directory
        $wp_path = ABSPATH;
        $full_file_path = realpath($file_path);
        $wp_real_path = realpath($wp_path);

        if (!$full_file_path || strpos($full_file_path, $wp_real_path) !== 0) {
            wp_send_json_error('File access denied for security reasons');
        }

        if (!file_exists($full_file_path) || !is_readable($full_file_path)) {
            wp_send_json_error('File not found or not accessible');
        }

        // Generate secure token for file access
        $token = wp_generate_password(32, false);
        $expiry = time() + 300; // 5 minutes expiry
        $filename = basename($full_file_path);

        // Store token in database
        $this->store_download_token($token, $issue_id, $full_file_path, $filename, $expiry);

        wp_send_json_success([
            'token' => $token,
            'filename' => $filename,
            'message' => 'File access token generated'
        ]);
    }

    /**
     * Handle get view file nonce request (for simple URL-based file viewing)
     */
    public function handle_get_view_file_nonce() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_send_json_error('Security check failed');
        }

        // Verify user capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
        }

        $issue_id = isset($_POST['issue_id']) ? intval($_POST['issue_id']) : 0;

        if (!$issue_id) {
            wp_send_json_error('Invalid issue ID');
        }

        // Generate nonce for viewing file
        $view_nonce = wp_create_nonce('vmpfence_view_file_nonce');
        
        // Create the view URL
        $base_url = home_url('/');
        $view_url = $base_url . '?_vmpfence=viewMaliciousFile&issue_id=' . $issue_id . '&nonce=' . $view_nonce;

        wp_send_json_success([
            'view_url' => $view_url,
            'message' => 'View URL generated'
        ]);
    }
    
    /**
     * Handle serve file request (streams file directly to browser)
     */
    public function handle_serve_file() {
        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Token-based authentication used instead
        if (!isset($_GET['token'])) {
            wp_die('Invalid download token', 'Download Error', array('response' => 403));
        }
        
        $token = sanitize_text_field(wp_unslash($_GET['token']));
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        $download_info = $this->get_download_token($token);
        
        if (!$download_info) {
            wp_die('Download token expired or invalid', 'Download Error', array('response' => 403));
        }
        
        // Verify file still exists
        if (!file_exists($download_info['file_path']) || !is_readable($download_info['file_path'])) {
            wp_die('File not found or not accessible', 'Download Error', array('response' => 404));
        }
        
        // Clean up the token (one-time use)
        $this->cleanup_download_token($token);
        
        // Determine if this is a text file that can be displayed
        $file_extension = strtolower(pathinfo($download_info['file_path'], PATHINFO_EXTENSION));
        $text_extensions = ['php', 'js', 'html', 'htm', 'css', 'txt', 'log', 'json', 'xml', 'sql', 'py', 'pl', 'sh'];
        
        if (in_array($file_extension, $text_extensions)) {
            // Display file content in browser with syntax highlighting
            $file_content = file_get_contents($download_info['file_path']);
            $safe_content = esc_html($file_content);
            
            // Load CSS from external file for standalone HTML document
            $css_file = plugin_dir_path(dirname(__FILE__)) . 'admin/assets/css/file-viewer.css';
            $css_content = file_exists($css_file) ? file_get_contents($css_file) : '';
            
            echo '<!DOCTYPE html>
<html>
<head>
    <title>VMP Fence - View File: ' . esc_html($download_info['filename']) . '</title>
    <style>' . esc_html($css_content) . '</style>
</head>
<body>
    <div class="header">
        <h2>VMP Fence File Viewer</h2>
        <p>File: ' . esc_html($download_info['file_path']) . '</p>
        <p>Size: ' . number_format(filesize($download_info['file_path'])) . ' bytes</p>
    </div>
    <div class="content">
        <pre>' . esc_html($safe_content) . '</pre>
    </div>
</body>
</html>';
        } else {
            // For binary files, force download
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename="' . $download_info['filename'] . '"');
            header('Content-Length: ' . filesize($download_info['file_path']));
            header('Cache-Control: no-cache, must-revalidate');
            header('Pragma: no-cache');
            
            // Output file content directly
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Direct file streaming needed for download
            readfile($download_info['file_path']);
        }
        exit;
    }
    
    /**
     * Store download token temporarily in database
     */
    private function store_download_token($token, $issue_id, $file_path, $filename, $expiry) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_download_tokens';
        
        // Create table if it doesn't exist
        $this->create_download_tokens_table();
        
        // Insert token
        $wpdb->insert(
            $table_name,
            array(
                'token' => $token,
                'issue_id' => $issue_id,
                'file_path' => $file_path,
                'filename' => $filename,
                'expiry' => $expiry,
                'created_at' => time()
            ),
            array('%s', '%d', '%s', '%s', '%d', '%d')
        );
    }
    
    /**
     * Get download token info and validate expiry
     */
    private function get_download_token($token) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_download_tokens';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $download_info = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$table_name} WHERE token = %s AND expiry > %d",
            $token,
            time()
        ), ARRAY_A);
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        return $download_info;
    }
    
    /**
     * Clean up used download token
     */
    private function cleanup_download_token($token) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_download_tokens';
        
        $wpdb->delete(
            $table_name,
            array('token' => $token),
            array('%s')
        );
    }
    
    /**
     * Create download tokens table
     */
    private function create_download_tokens_table() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_download_tokens';
        
        $charset_collate = $wpdb->get_charset_collate();
        
        $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
            id int(11) NOT NULL AUTO_INCREMENT,
            token varchar(255) NOT NULL,
            issue_id int(11) NOT NULL,
            file_path text NOT NULL,
            filename varchar(255) NOT NULL,
            expiry int(11) NOT NULL,
            created_at int(11) NOT NULL,
            PRIMARY KEY (id),
            UNIQUE KEY token (token),
            KEY expiry (expiry)
        ) {$charset_collate};";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
    }
    
    /**
     * Clean up old backup files (older than 30 days)
     */
    private function cleanup_old_backup_files() {
        $upload_dir = wp_upload_dir();
        $backup_dir = $upload_dir['basedir'] . '/vmpfence-backups';
        
        if (!is_dir($backup_dir)) {
            return;
        }
        
        // Define cutoff time (30 days ago)
        $cutoff_time = time() - (30 * 24 * 60 * 60); // 30 days in seconds
        try {
            // Get all files in the backup directory
            $files = scandir($backup_dir);
            
            if ($files === false) {
                return;
            }
            
            $deleted_count = 0;
            $total_size_deleted = 0;
            
            foreach ($files as $file) {
                // Skip . and .. directory entries
                if ($file === '.' || $file === '..') {
                    continue;
                }
                
                $file_path = $backup_dir . '/' . $file;
                
                // Skip if it's not a file
                if (!is_file($file_path)) {
                    continue;
                }
                
                // Check file modification time
                $file_mtime = filemtime($file_path);
                
                if ($file_mtime !== false && $file_mtime < $cutoff_time) {
                    // File is older than 1 month, delete it
                    $file_size = filesize($file_path);
                    
                    if (wp_delete_file($file_path)) {
                        $deleted_count++;
                        $total_size_deleted += $file_size;
                    }
                }
            }
            
            // Log summary if any files were deleted
            // if ($deleted_count > 0) {
            //     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
            //     error_log("VMPFence: Backup cleanup completed - Deleted {$deleted_count} old backup files, freed " . size_format($total_size_deleted) . " of space");
            // }
            
        } catch (Exception $e) {
            // Log error but don't fail the main operation
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
           // error_log("VMPFence: Error during backup files cleanup: " . $e->getMessage());
        }
        
        // Also clean up expired download tokens
        $this->cleanup_expired_download_tokens();
    }
    
    /**
     * Clean up expired download tokens from database
     */
    private function cleanup_expired_download_tokens() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_download_tokens';
        
        // Delete expired tokens
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $deleted = $wpdb->query($wpdb->prepare(
            "DELETE FROM {$table_name} WHERE expiry < %d",
            time()
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        if ($deleted > 0) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
            // error_log("VMPFence: Cleaned up {$deleted} expired download tokens");
        }
    }

    /**
     * Handle clean option AJAX request - removes code injection from WordPress options
     */
    public function handle_clean_option() {
        // Verify nonce and permissions
        if (!check_ajax_referer('vmpfence_scan_nonce', 'nonce', false) || !current_user_can('manage_options')) {
            wp_send_json_error('Invalid request or insufficient permissions');
            return;
        }

        $issue_id = intval($_POST['issue_id'] ?? 0);
        if (!$issue_id) {
            wp_send_json_error('Invalid issue ID');
            return;
        }

        // Get the issue details from database
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $issue = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$table_name} WHERE id = %d AND type = 'code_injection_in_option'",
            $issue_id
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

        if (!$issue) {
            wp_send_json_error('Issue not found or not a code injection issue');
            return;
        }

        // Parse the issue description to get the option name
        $description = $issue->description;
        preg_match("/Option '([^']+)'/", $description, $matches);
        
        if (!$matches || !isset($matches[1])) {
            wp_send_json_error('Could not determine option name from issue description');
            return;
        }

        $option_name = $matches[1];
        
        try {
            // Get current option value
            $option_value = get_option($option_name);
            
            if ($option_value === false) {
                wp_send_json_error('Option not found in database');
                return;
            }

            // Convert to string for processing if it's an array/object
            $current_value = $option_value;
            $is_serialized = false;
            
            if (!is_string($current_value)) {
                $current_value = serialize($current_value);
                $is_serialized = true;
            }

            // Clean the option by removing common injection patterns
            $cleaned_value = $this->clean_injected_code($current_value);
            
            // Debug: Log what we're working with
            $original_length = strlen($current_value);
            $cleaned_length = strlen($cleaned_value);
            
            if ($cleaned_value === $current_value) {
                // Try a more aggressive cleaning approach
                $cleaned_value = $this->aggressive_clean_injected_code($current_value);
                
                if ($cleaned_value === $current_value) {
                    wp_send_json_error("No malicious code patterns detected in current option value. Original length: {$original_length} chars, Type: " . gettype($option_value));
                    return;
                }
            }

            // Convert back to original format if it was serialized
            if ($is_serialized) {
                $unserialized = @unserialize($cleaned_value);
                if ($unserialized !== false) {
                    $final_value = $unserialized;
                } else {
                    // If unserialize fails, we may have broken the data structure
                    wp_send_json_error('Cleaning process corrupted the data structure. Manual review required.');
                    return;
                }
            } else {
                $final_value = $cleaned_value;
            }

            // Update the option with cleaned value
            $update_result = update_option($option_name, $final_value);
            
            if (!$update_result) {
                wp_send_json_error('Failed to update option in database');
                return;
            }

            // Mark the issue as resolved
            $update_result = $wpdb->update(
                $table_name,
                ['status' => 'resolved', 'resolved_time' => current_time('mysql')],
                ['id' => $issue_id],
                ['%s', '%s'],
                ['%d']
            );

            // Verify the update worked
            if ($update_result === false) {
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
                // error_log("VMPFence: Failed to update issue status in database. Error: " . $wpdb->last_error);
                wp_send_json_error('Failed to mark issue as resolved in database');
                return;
            }

            // Double-check the issue was updated
            // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
            $updated_issue = $wpdb->get_row($wpdb->prepare(
                "SELECT status, resolved_time FROM {$table_name} WHERE id = %d",
                $issue_id
            ));
            // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            
            // Calculate lengths safely for response
            $original_length = is_string($current_value) ? strlen($current_value) : strlen(serialize($current_value));
            $cleaned_length = is_string($cleaned_value) ? strlen($cleaned_value) : strlen(serialize($cleaned_value));
            
            wp_send_json_success([
                'message' => 'WordPress option cleaned successfully',
                'option_name' => $option_name,
                'original_length' => $original_length,
                'cleaned_length' => $cleaned_length
            ]);

        } catch (Exception $e) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
            // error_log("VMPFence: Error cleaning option '{$option_name}': " . $e->getMessage());
            wp_send_json_error('Error occurred while cleaning option: ' . $e->getMessage());
        }
    }

    /**
     * Clean injected code from option value
     */
    private function clean_injected_code($value) {
        if (!is_string($value)) {
            return $value;
        }

        $original_value = $value;
        
        // Remove common malicious patterns
        $malicious_patterns = [
            // Advanced PHP code execution patterns
            '/<\?php\s+(?:eval|system|exec|shell_exec|passthru|file_get_contents|fopen|fwrite).*?\?>/is',
            '/eval\s*\(\s*(?:\$_(?:GET|POST|REQUEST|COOKIE)|base64_decode|gzinflate|str_rot13)[^)]*\)/is',
            '/(?:system|exec|shell_exec|passthru)\s*\(\s*\$_(?:GET|POST|REQUEST)[^)]*\)/is',
            '/file_(?:get_contents|put_contents)\s*\(\s*(?:\$_(?:GET|POST)|["\'][^"\']*\.php["\'])[^)]*\)/is',
            '/(?:include|require)(?:_once)?\s*\(\s*\$_(?:GET|POST|REQUEST)[^)]*\)/is',
            '/base64_decode\s*\(\s*["\'][A-Za-z0-9+\/]{20,}={0,2}["\'](?:\s*\)\s*;\s*eval)/is',
            '/preg_replace\s*\(\s*["\'].*\/e["\'].*\$_(?:GET|POST)[^)]*\)/is',
            
            // Advanced JavaScript injection patterns
            '/<script[^>]*>.*(?:eval|document\.write|window\.location|xhr|fetch).*<\/script>/is',
            '/javascript\s*:\s*(?:eval|alert|document\.write)[^;"\'\s]*/is',
            '/eval\s*\(\s*(?:atob|unescape|String\.fromCharCode)[^)]*\)/is',
            '/document\.write\s*\(\s*["\']<script[^)]*\)/is',
            '/window\.location\s*=\s*["\'](?:javascript|data):[^"\']*["\']/',
            '/innerHTML\s*=\s*["\'].*<script[^"\']*["\']/',
            '/on(?:click|load|error|focus|blur)\s*=\s*["\'].*(?:eval|window\.location|document\.write)[^"\']*["\']/',
            
            // Advanced SQL injection patterns  
            '/UNION\s+(?:ALL\s+)?SELECT.*(?:concat|group_concat|load_file|into\s+outfile)/is',
            '/(?:DROP|ALTER|CREATE)\s+(?:TABLE|DATABASE|USER)[^;]*/is',
            '/(?:DELETE|UPDATE)\s+.*WHERE.*(?:1\s*=\s*1|["\'].*["\'])/is',
            '/INSERT\s+INTO.*VALUES.*(?:char\(|0x[0-9a-f]+)/is',
            '/SELECT\s+.*FROM\s+.*WHERE.*(?:benchmark|sleep|waitfor)/is',
            
            // Obfuscation and encoding patterns
            '/\\\\x[0-9a-f]{2}/is',
            '/%[0-9a-f]{2}/is',
            '/\\\\[0-9]{3}/is',
            
            // Known backdoor signatures and shells
            '/c99|r57|sh3ll|b374k|wso|r00t|shell|backdoor/is',
        ];

        foreach ($malicious_patterns as $pattern) {
            $value = preg_replace($pattern, '', $value);
        }

        // Remove excessive whitespace that might be left after cleaning
        $value = preg_replace('/\s+/', ' ', $value);
        $value = trim($value);

        return $value;
    }

    /**
     * More aggressive cleaning for persistent injections
     */
    private function aggressive_clean_injected_code($value) {
        if (!is_string($value)) {
            return $value;
        }

        $original_value = $value;
        
        // More comprehensive malicious patterns
        $aggressive_patterns = [
            // Obfuscated PHP
            '/\$[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*["\'][^"\']*["\'];\s*eval\s*\(/is',
            '/\$[a-zA-Z_][a-zA-Z0-9_]*\s*\.\s*\$[a-zA-Z_][a-zA-Z0-9_]*\s*\.\s*\$[a-zA-Z_][a-zA-Z0-9_]*/is',
            
            // Base64 and other encodings
            '/[a-zA-Z0-9+\/]{50,}={0,2}/is', // Long base64 strings
            '/chr\s*\(\s*\d+\s*\)/is',
            '/pack\s*\([^)]+\)/is',
            '/gzinflate\s*\([^)]+\)/is',
            '/str_rot13\s*\([^)]+\)/is',
            '/strrev\s*\([^)]+\)/is',
            
            // Suspicious function calls
            '/preg_replace\s*\([^)]*\/e[^)]*\)/is',
            '/create_function\s*\([^)]+\)/is',
            '/call_user_func\s*\([^)]+\)/is',
            '/call_user_func_array\s*\([^)]+\)/is',
            '/assert\s*\([^)]+\)/is',
            
            // Concatenated strings that might hide malicious code
            '/["\'][^"\']*["\'][\s]*\.[\s]*["\'][^"\']*["\'][\s]*\.[\s]*["\'][^"\']*["\']/is',
            
            // Hex encoded content
            '/\\\\x[0-9a-fA-F]{2}[\\\\x0-9a-fA-F]*/is',
            
            // Suspicious variable patterns
            '/\$[a-zA-Z_][a-zA-Z0-9_]*\[["\'][^"\']*["\']\]\s*=\s*["\'][^"\']*["\']/is',
            
            // Remove any obviously suspicious content
            '/valueskljhsdfjkhadsf/is', // From the specific injection we saw
        ];

        foreach ($aggressive_patterns as $pattern) {
            $cleaned = preg_replace($pattern, '', $value);
            if ($cleaned !== $value) {
                $value = $cleaned;
            }
        }

        // Clean up any remaining whitespace and empty structures
        $value = preg_replace('/\s+/', ' ', $value);
        $value = preg_replace('/[;,]\s*[;,]+/', ';', $value); // Remove multiple consecutive semicolons
        $value = trim($value);

        return $value;
    }

    /**
     * Handle view option code AJAX request - shows injected code in modal
     */
    public function handle_view_option_code() {
        // Verify nonce and permissions
        if (!check_ajax_referer('vmpfence_scan_nonce', 'nonce', false) || !current_user_can('manage_options')) {
            wp_send_json_error('Invalid request or insufficient permissions');
            return;
        }

        $issue_id = intval($_POST['issue_id'] ?? 0);
        if (!$issue_id) {
            wp_send_json_error('Invalid issue ID');
            return;
        }

        // Get the issue details from database
        global $wpdb;
        $table_name = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
        $issue = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$table_name} WHERE id = %d AND type = 'code_injection_in_option'",
            $issue_id
        ));
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

        if (!$issue) {
            wp_send_json_error('Issue not found or not a code injection issue');
            return;
        }

        // Parse the issue description to get the option name
        $description = $issue->description;
        preg_match("/Option '([^']+)'/", $description, $matches);
        
        if (!$matches || !isset($matches[1])) {
            wp_send_json_error('Could not determine option name from issue description');
            return;
        }

        $option_name = $matches[1];
        
        try {
            // Get current option value
            $option_value = get_option($option_name);
            
            if ($option_value === false) {
                wp_send_json_error('Option not found in database');
                return;
            }

            // If it's not a string, serialize it for viewing
            if (!is_string($option_value)) {
                $option_value = serialize($option_value);
            }

            wp_send_json_success([
                'option_name' => $option_name,
                'option_value' => $option_value,
                'issue_details' => $description,
                'value_length' => strlen($option_value)
            ]);

        } catch (Exception $e) {
            wp_send_json_error('Error occurred while retrieving option: ' . $e->getMessage());
        }
    }

    /**
     * Handle file differences request
     */
    public function handle_get_file_differences() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_send_json_error('Invalid nonce');
            return;
        }

        // Verify user capability
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $issue_id = isset($_POST['issue_id']) ? intval($_POST['issue_id']) : 0;
        
        if (!$issue_id) {
            wp_send_json_error('Invalid issue ID');
            return;
        }

        try {
            global $wpdb;
            $table_name = $wpdb->prefix . 'vmpfence_scan_issues';

            // Get the issue details
            // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from WordPress prefix
            $issue = $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$table_name} WHERE id = %d",
                $issue_id
            ));
            // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
            if (!$issue) {
                wp_send_json_error('Issue not found');
                return;
            }

            // Extract file path from the issue data and normalize it
            $file_path = $issue->file_path;
            
            // Normalize path separators for Windows compatibility
            $file_path = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $file_path);
            
            // If path is relative, make it absolute from WordPress root
            if (!$this->is_absolute_path($file_path)) {
                $file_path = ABSPATH . ltrim($file_path, DIRECTORY_SEPARATOR);
            }
            
            if (!$file_path || !file_exists($file_path)) {
                wp_send_json_error('File not found: ' . $file_path);
                return;
            }

            // Read current file content
            $current_content = file_get_contents($file_path);
            if ($current_content === false) {
                wp_send_json_error('Unable to read current file');
                return;
            }

            // Try to get the original WordPress file for comparison
            $original_content = $this->get_original_wordpress_file_content($file_path);
            
            // Generate formatted differences
            $formatted_diff = '';
            $changes_summary = '';
            
            if ($original_content !== false) {
                $formatted_diff = $this->generate_file_diff($original_content, $current_content);
                $changes_summary = $this->get_changes_summary($original_content, $current_content);
            } else {
                $changes_summary = 'Original file not available for comparison - showing current file content only';
            }

            // Get file info
            $file_name = basename($file_path);
            $relative_path = str_replace(ABSPATH, '', $file_path);

            wp_send_json_success([
                'file_name' => $file_name,
                'file_path' => $relative_path,
                'current_content' => $current_content,
                'original_content' => $original_content,
                'formatted_diff' => $formatted_diff,
                'changes_summary' => $changes_summary
            ]);

        } catch (Exception $e) {
            wp_send_json_error('Error occurred while getting file differences: ' . $e->getMessage());
        }
    }

    /**
     * Get original WordPress file content for comparison
     */
    private function get_original_wordpress_file_content($file_path) {
        // Determine if this is a core WordPress file
        $wp_content_path = WP_CONTENT_DIR;
        $wp_includes_path = ABSPATH . 'wp-includes/';
        $wp_admin_path = ABSPATH . 'wp-admin/';

        // Skip if it's in wp-content (themes/plugins)
        if (strpos($file_path, $wp_content_path) === 0) {
            return false; // We don't have original versions of theme/plugin files
        }

        // For core WordPress files, try to get from WordPress.org
        global $wp_version;
        
        // Get the relative path from WordPress root
        $relative_path = str_replace(ABSPATH, '', $file_path);
        $relative_path = str_replace('\\', '/', $relative_path); // Normalize to forward slashes for URLs
        
        /**
         * External Service: GitHub WordPress Repository
         * 
         * PURPOSE: Downloads original WordPress core files to show file differences/comparisons.
         * 
         * WHEN CALLED: Only when user clicks "View Differences" on a scan result (not during scans).
         * 
         * DATA SENT:
         * - WordPress version number (e.g., "6.8")
         * - File path relative to WordPress root (e.g., "wp-admin/index.php")
         * - User-Agent: "VMPFence Security Scanner"
         * 
         * DATA NOT SENT:
         * - No personal data, user information, site content, or configuration
         * 
         * PRIVACY:
         * - GitHub may log standard access logs (IP, user agent, timestamp)
         * - All accessed files are public WordPress core files
         * - No authentication or tracking involved
         * 
         * DOCUMENTATION: See readme.txt "External Services" section for full details.
         * PRIVACY POLICY: https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement
         * 
         * @link https://raw.githubusercontent.com/WordPress/
         */
        $wp_org_url = "https://raw.githubusercontent.com/WordPress/WordPress/{$wp_version}/{$relative_path}";
        
        // Try to fetch the original file
        $response = wp_remote_get($wp_org_url, [
            'timeout' => 30,
            'user-agent' => 'VMPFence Security Scanner'
        ]);
        
        if (is_wp_error($response)) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional for debugging
            // error_log("VMPFENCE: GitHub fetch failed: " . $response->get_error_message());
        } else {
            $response_code = wp_remote_retrieve_response_code($response);
        }
        
        if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
            
            // Try alternative URL structure for some files
            $alt_url = "https://core.svn.wordpress.org/tags/{$wp_version}/{$relative_path}";
            $alt_response = wp_remote_get($alt_url, [
                'timeout' => 30,
                'user-agent' => 'VMPFence Security Scanner'
            ]);
            
            if (is_wp_error($alt_response)) {
                return false;
            }
            
            $alt_response_code = wp_remote_retrieve_response_code($alt_response);
            
            if ($alt_response_code !== 200) {
                return false;
            }
            return wp_remote_retrieve_body($alt_response);
        }
        
        return wp_remote_retrieve_body($response);
    }

    /**
     * Generate formatted diff between two file contents
     */
    private function generate_file_diff($original, $current) {
        // Normalize line endings first
        $original = str_replace(["\r\n", "\r"], "\n", $original);
        $current = str_replace(["\r\n", "\r"], "\n", $current);
        
        $original_lines = explode("\n", $original);
        $current_lines = explode("\n", $current);
        
        // Use a simple but effective diff algorithm
        $diff_result = $this->simple_diff($original_lines, $current_lines);
        
        $diff = [];
        $old_line_num = 1;
        $new_line_num = 1;
        
        foreach ($diff_result as $change) {
            switch ($change[0]) {
                case '=': // Unchanged
                    $diff[] = sprintf('<div><span class="diff-line-number">%d</span><span class="diff-unchanged">%s</span></div>', 
                        $old_line_num, htmlspecialchars($change[1]));
                    $old_line_num++;
                    $new_line_num++;
                    break;
                    
                case '+': // Added
                    $diff[] = sprintf('<div><span class="diff-line-number">%d</span><span class="diff-added">+ %s</span></div>', 
                        $new_line_num, htmlspecialchars($change[1]));
                    $new_line_num++;
                    break;
                    
                case '-': // Removed
                    $diff[] = sprintf('<div><span class="diff-line-number">%d</span><span class="diff-removed">- %s</span></div>', 
                        $old_line_num, htmlspecialchars($change[1]));
                    $old_line_num++;
                    break;
            }
        }
        
        return implode("\n", $diff);
    }

    /**
     * Simple diff algorithm that actually works - uses exact line matching
     */
    private function simple_diff($old_lines, $new_lines) {
        $result = [];
        $old_count = count($old_lines);
        $new_count = count($new_lines);
        
        // Find the longest common subsequence to identify what actually changed
        $lcs = $this->find_lcs($old_lines, $new_lines);
        
        $old_i = 0;
        $new_i = 0;
        $lcs_i = 0;
        
        while ($old_i < $old_count || $new_i < $new_count || $lcs_i < count($lcs)) {
            if ($lcs_i < count($lcs)) {
                $common_line = $lcs[$lcs_i];
                
                // Add any deleted lines before this common line
                while ($old_i < $old_count && $old_lines[$old_i] !== $common_line) {
                    $result[] = ['-', $old_lines[$old_i]];
                    $old_i++;
                }
                
                // Add any inserted lines before this common line
                while ($new_i < $new_count && $new_lines[$new_i] !== $common_line) {
                    $result[] = ['+', $new_lines[$new_i]];
                    $new_i++;
                }
                
                // Add the common line
                if ($old_i < $old_count && $new_i < $new_count && 
                    $old_lines[$old_i] === $common_line && $new_lines[$new_i] === $common_line) {
                    $result[] = ['=', $common_line];
                    $old_i++;
                    $new_i++;
                }
                
                $lcs_i++;
            } else {
                // No more common lines, add remaining as deletions/insertions
                while ($old_i < $old_count) {
                    $result[] = ['-', $old_lines[$old_i]];
                    $old_i++;
                }
                while ($new_i < $new_count) {
                    $result[] = ['+', $new_lines[$new_i]];
                    $new_i++;
                }
                break;
            }
        }
        
        return $result;
    }
    
    /**
     * Find Longest Common Subsequence between two arrays
     */
    private function find_lcs($old_lines, $new_lines) {
        $old_count = count($old_lines);
        $new_count = count($new_lines);
        
        // Create a matrix to store LCS lengths
        $matrix = array_fill(0, $old_count + 1, array_fill(0, $new_count + 1, 0));
        
        // Fill the matrix
        for ($i = 1; $i <= $old_count; $i++) {
            for ($j = 1; $j <= $new_count; $j++) {
                if ($old_lines[$i-1] === $new_lines[$j-1]) {
                    $matrix[$i][$j] = $matrix[$i-1][$j-1] + 1;
                } else {
                    $matrix[$i][$j] = max($matrix[$i-1][$j], $matrix[$i][$j-1]);
                }
            }
        }
        
        // Backtrack to find the actual LCS
        $lcs = [];
        $i = $old_count;
        $j = $new_count;
        
        while ($i > 0 && $j > 0) {
            if ($old_lines[$i-1] === $new_lines[$j-1]) {
                array_unshift($lcs, $old_lines[$i-1]);
                $i--;
                $j--;
            } elseif ($matrix[$i-1][$j] > $matrix[$i][$j-1]) {
                $i--;
            } else {
                $j--;
            }
        }
        
        return $lcs;
    }

    /**
     * Get summary of changes between files
     */
    private function get_changes_summary($original, $current) {
        if ($original === false) {
            return 'Original file not available for comparison';
        }

        // Normalize line endings first
        $original = str_replace(["\r\n", "\r"], "\n", $original);
        $current = str_replace(["\r\n", "\r"], "\n", $current);
        
        $original_lines = explode("\n", $original);
        $current_lines = explode("\n", $current);
        
        // Use the same diff algorithm to get accurate counts
        $diff_result = $this->simple_diff($original_lines, $current_lines);
        
        $added = 0;
        $removed = 0;
        $unchanged = 0;
        
        foreach ($diff_result as $change) {
            switch ($change[0]) {
                case '+':
                    $added++;
                    break;
                case '-':
                    $removed++;
                    break;
                case '=':
                    $unchanged++;
                    break;
            }
        }
        
        $summary = [];
        if ($added > 0) $summary[] = "+{$added} lines added";
        if ($removed > 0) $summary[] = "-{$removed} lines removed";
        
        return !empty($summary) ? implode(', ', $summary) : 'No changes detected';
    }

    /**
     * Check if a path is absolute
     */
    private function is_absolute_path($path) {
        // Windows: Check for drive letter (C:) or UNC path (\\)
        if (PHP_OS_FAMILY === 'Windows') {
            return preg_match('/^[a-zA-Z]:\\\\|^\\\\\\\\/', $path);
        }
        // Unix/Linux: Check for leading slash
        return strpos($path, '/') === 0;
    }

    /**
     * Handle delete file AJAX request
     */
    public function handle_delete_file() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_send_json_error('Security check failed');
            return;
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $issue_id = isset($_POST['issue_id']) ? intval($_POST['issue_id']) : 0;
        
        if (!$issue_id) {
            wp_send_json_error('Invalid issue ID');
            return;
        }

        global $wpdb;
        $scan_issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // Get issue information
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $query = $wpdb->prepare(
            "SELECT * FROM {$scan_issues_table} WHERE id = %d",
            $issue_id
        );
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        
        $issue = $wpdb->get_row($query); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Already prepared above
        
        if (!$issue) {
            wp_send_json_error('Issue not found');
            return;
        }

        // Check if this is a deletable file type
        $deletable_types = [
            'malware_detected',
            'malware',
            'unknown_threat',
            'malicious_content',
            'suspicious_external_file',
            'obfuscation',
            'base64_malware',
            'malicious_url'
        ];
        
        // Use issue_type if type is empty (database schema inconsistency)
        $actual_type = !empty($issue->type) ? $issue->type : $issue->issue_type;
        
        if (!in_array($actual_type, $deletable_types)) {
            wp_send_json_error('This file type cannot be automatically deleted');
            return;
        }

        $file_path = $issue->file_path;
        
        // Convert relative path to absolute path if needed
        if (!$this->is_absolute_path($file_path)) {
            $file_path = ABSPATH . $file_path;
        }
        
        // Security: Ensure file path is within allowed directories
        if (!$this->is_safe_to_delete($file_path)) {
            wp_send_json_error('File is not safe to delete (outside allowed directories)');
            return;
        }

        // Check if file exists
        if (!file_exists($file_path)) {
            // File already gone, just remove from database
            $result = $wpdb->delete($scan_issues_table, ['id' => $issue_id], ['%d']);
            
            if ($result) {
                wp_send_json_success(['message' => 'File was already deleted, issue removed from list']);
            } else {
                wp_send_json_error('Could not remove issue from database');
            }
            return;
        }

        // Create backup before deletion
        $backup_success = $this->create_file_backup($file_path);
        
        // Attempt to delete the file
        if (wp_delete_file($file_path)) {
            // Remove the issue from database
            $result = $wpdb->delete($scan_issues_table, ['id' => $issue_id], ['%d']);
            
            if ($result) {
                wp_send_json_success([
                    'message' => 'File deleted successfully' . 
                                ($backup_success ? ' (backup created)' : ''),
                    'backup_created' => $backup_success
                ]);
            } else {
                wp_send_json_error('File deleted but could not remove issue from database');
            }
        } else {
            wp_send_json_error('Could not delete file: Permission denied or file in use');
        }
    }

    /**
     * Handle bulk delete files AJAX request
     */
    public function handle_bulk_delete_files() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_send_json_error('Security check failed');
            return;
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $issue_ids = isset($_POST['issue_ids']) ? array_map('sanitize_text_field', wp_unslash($_POST['issue_ids'])) : [];
        if (!is_array($issue_ids) || empty($issue_ids)) {
            wp_send_json_error('No issue IDs provided');
            return;
        }

        // Sanitize issue IDs
        $issue_ids = array_map('intval', $issue_ids);
        $issue_ids = array_filter($issue_ids);
        
        if (empty($issue_ids)) {
            wp_send_json_error('No valid issue IDs provided');
            return;
        }

        global $wpdb;
        $scan_issues_table = $wpdb->prefix . 'vmpfence_scan_issues';
        
        // Get all issues
        $issue_ids_string = implode(',', $issue_ids);
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $issues = $wpdb->get_results(
            "SELECT * FROM {$scan_issues_table} WHERE id IN ({$issue_ids_string})"
        );
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        if (empty($issues)) {
            wp_send_json_error('No issues found');
            return;
        }

        $deleted_count = 0;
        $backup_count = 0;
        $error_files = [];
        $deletable_types = [
            'malware_detected',
            'malware',
            'unknown_threat', 
            'malicious_content',
            'suspicious_external_file',
            'obfuscation',
            'base64_malware',
            'malicious_url'
        ];

        foreach ($issues as $issue) {
            // Use issue_type if type is empty (database schema inconsistency)
            $actual_type = !empty($issue->type) ? $issue->type : $issue->issue_type;
            
            // Check if this is a deletable file type
            if (!in_array($actual_type, $deletable_types)) {
                continue;
            }

            $file_path = $issue->file_path;
            // Convert relative path to absolute path if needed
            if (!$this->is_absolute_path($file_path)) {
                $file_path = ABSPATH . $file_path;
            }
            
            // Security: Ensure file path is within allowed directories
            if (!$this->is_safe_to_delete($issue->file_path)) {
                $error_files[] = basename($file_path) . ' (unsafe location)';
                continue;
            }

            // Check if file exists
            if (!file_exists($file_path)) {
                // File already gone, just remove from database
                $wpdb->delete($scan_issues_table, ['id' => $issue->id], ['%d']);
                $deleted_count++;
                continue;
            }

            // Create backup before deletion
            if ($this->create_file_backup($file_path)) {
                $backup_count++;
            }
            
            // Attempt to delete the file
            if (wp_delete_file($file_path)) {
                // Remove the issue from database
                $result = $wpdb->delete($scan_issues_table, ['id' => $issue->id], ['%d']);
                
                if ($result) {
                    $deleted_count++;
                } else {
                    $error_files[] = basename($file_path) . ' (database error)';
                }
            } else {
                $error_files[] = basename($file_path) . ' (permission denied)';
            }
        }

        // Log the bulk deletion
        // error_log("VMPFence: Bulk file deletion completed: {$deleted_count} files deleted, {$backup_count} backups created");

        $message = "Successfully deleted {$deleted_count} files";
        if ($backup_count > 0) {
            $message .= " ({$backup_count} backups created)";
        }
        if (!empty($error_files)) {
            $message .= ". Errors: " . implode(', ', $error_files);
        }

        wp_send_json_success([
            'message' => $message,
            'deleted_count' => $deleted_count,
            'backup_count' => $backup_count,
            'error_count' => count($error_files),
            'errors' => $error_files
        ]);
    }

    /**
     * Check if a file is safe to delete
     */
    private function is_safe_to_delete($file_path) {
        
        // Convert relative path to absolute path if needed (but caller should already do this)
        if (!$this->is_absolute_path($file_path)) {
            $file_path = ABSPATH . $file_path;
        }
        
        // Convert to canonical absolute path
        $realpath = realpath($file_path);
        
        if (!$realpath) {
            // If realpath fails but file might exist, try the original path
            if (file_exists($file_path)) {
                $realpath = $file_path;
            } else {
                return false; // File doesn't exist or path invalid
            }
        }

        // Define safe directories (places where malware might be found)
        $wp_root = realpath(ABSPATH);
        $wp_content = realpath(WP_CONTENT_DIR);
        $wp_uploads = realpath(wp_upload_dir()['basedir']);
        
        // Allow deletion in:
        // 1. WordPress uploads directory (common malware location)
        // 2. WordPress content directory (themes/plugins with malware)
        // 3. WordPress root (but be careful with core files)
        $safe_directories = [
            $wp_uploads,
            $wp_content,
            $wp_root
        ];

        // Check if file is within any safe directory
        foreach ($safe_directories as $safe_dir) {
            if ($safe_dir && strpos($realpath, $safe_dir . DIRECTORY_SEPARATOR) === 0) {
                // Additional check: Don't delete WordPress core files
                if ($this->is_wordpress_core_file($realpath)) {
                    return false;
                }
                return true;
            }
        }
        return false; // File is outside safe directories
    }

    /**
     * Check if a file is a WordPress core file (should not be deleted)
     */
    private function is_wordpress_core_file($file_path) {
        $wp_root = realpath(ABSPATH);
        $relative_path = str_replace($wp_root . DIRECTORY_SEPARATOR, '', $file_path);
        
        // Convert backslashes to forward slashes for consistent checking
        $relative_path = str_replace('\\', '/', $relative_path);
        
        // Core files that should never be deleted
        $core_files = [
            'index.php',
            'wp-config.php',
            'wp-load.php',
            'wp-blog-header.php',
            'wp-settings.php',
            '.htaccess'
        ];
        
        // Known WordPress core files in wp-admin directory
        $wp_admin_core_files = [
            'wp-admin/admin.php',
            'wp-admin/admin-ajax.php',
            'wp-admin/admin-footer.php',
            'wp-admin/admin-header.php',
            'wp-admin/admin-post.php',
            'wp-admin/index.php',
            'wp-admin/menu.php',
            'wp-admin/options.php',
            'wp-admin/plugins.php',
            'wp-admin/themes.php',
            'wp-admin/users.php',
            'wp-admin/tools.php',
            'wp-admin/edit.php',
            'wp-admin/post.php',
            'wp-admin/post-new.php',
            'wp-admin/upload.php',
            'wp-admin/media.php'
        ];
        
        // Check if it's a core file in root
        if (in_array($relative_path, $core_files)) {
            return true;
        }
        
        // Check if it's a known core file in wp-admin
        if (in_array($relative_path, $wp_admin_core_files)) {
            return true;
        }
        
        // Block all files in wp-includes (this is strictly core)
        if (strpos($relative_path, 'wp-includes/') === 0) {
            return true;
        }
        
        // For wp-admin, only block known core files and core subdirectories
        if (strpos($relative_path, 'wp-admin/') === 0) {
            // Block specific core subdirectories in wp-admin
            $core_admin_dirs = [
                'wp-admin/css/',
                'wp-admin/js/',
                'wp-admin/images/',
                'wp-admin/includes/',
                'wp-admin/maint/',
                'wp-admin/network/',
                'wp-admin/user/'
            ];
            
            foreach ($core_admin_dirs as $core_dir) {
                if (strpos($relative_path, $core_dir) === 0) {
                    return true;
                }
            }
            return false;
        }
        
        return false;
    }

    /**
     * Create a backup of a file before deletion
     */
    private function create_file_backup($file_path) {
        if (!file_exists($file_path)) {
            return false;
        }

        // Create backup directory if it doesn't exist
        $backup_dir = WP_CONTENT_DIR . '/vmpfence-backups/deleted-files';
        if (!file_exists($backup_dir)) {
            wp_mkdir_p($backup_dir);
        }

        // Generate backup filename with timestamp
        $filename = basename($file_path);
        $timestamp = gmdate('Y-m-d_H-i-s');
        $backup_filename = $timestamp . '_' . $filename;
        $backup_path = $backup_dir . '/' . $backup_filename;

        // Copy file to backup location
        if (copy($file_path, $backup_path)) {
            // Create info file with original path
            $info_file = $backup_path . '.info';
            file_put_contents($info_file, json_encode([
                'original_path' => $file_path,
                'backup_time' => current_time('mysql'),
                'file_size' => filesize($file_path),
                'file_hash' => md5_file($file_path)
            ]));
            
            return true;
        }

        return false;
    }
    
    /**
     * Handle get reputation percentage AJAX request
     */
    public function handle_get_reputation_percentage() {
        // Verify nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        // Include the reputation scanner class
        require_once VMPFENCE_PLUGIN_DIR . 'includes/class-reputation-scanner.php';
        
        // Calculate reputation percentage
        $percentage = VMPFence_Reputation_Scanner::calculate_reputation_percentage();
        $status_list = VMPFence_Reputation_Scanner::get_reputation_status_list();
        
        wp_send_json_success([
            'percentage' => $percentage,
            'status_list' => $status_list
        ]);
    }
    
    /**
     * Handle security scores AJAX request
     */
    public function handle_get_security_scores() {
        // Security checks
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        // 1. SCAN TYPE FROM DATABASE - Just use the stored value
        $scan_type = VMPFence_Config::get('scanType', 'standard');
        
        // Get scan options for calculations
        $scan_options = VMPFence_Scan_Config::getScanOptions();
        
        switch($scan_type) {
            case 'limited': 
                $scan_type_percentage = 40; 
                $scan_type_name = 'Limited'; 
                break;
            case 'standard': 
                $scan_type_percentage = 60; 
                $scan_type_name = 'Standard'; 
                break;
            case 'high_sensitivity': 
                $scan_type_percentage = 85; 
                $scan_type_name = 'High Sensitivity'; 
                break;
            case 'custom':
                $enabled_modules = 0;
                $key_modules = ['scansEnabled_core', 'scansEnabled_themes', 'scansEnabled_plugins', 
                               'scansEnabled_malware', 'scansEnabled_fileContents', 'scansEnabled_posts',
                               'scansEnabled_comments', 'scansEnabled_suspiciousOptions', 'scansEnabled_checkGSB',
                               'scansEnabled_checkReadableConfig', 'scansEnabled_suspectedFiles'];
                foreach($key_modules as $module) {
                    if (!empty($scan_options[$module])) $enabled_modules++;
                }
                $scan_type_percentage = round(($enabled_modules / 11) * 100);
                $scan_type_name = 'Custom';
                break;
            default: 
                $scan_type_percentage = 60; 
                $scan_type_name = 'Standard';
        }
        
        // 2. MALWARE SIGNATURES CALCULATION
        // Use lightweight metadata instead of instantiating full Malware_Scanner + Reflection
        // (Conflict #8 fix: avoids loading 130 MB for a count query)
        require_once VMPFENCE_PLUGIN_DIR . 'includes/class-signature-loader.php';
        $total_patterns = VMPFence_Signature_Loader::get_count();
        
        // Get actual signature counts from stored options
        $local_signature_count = get_option('vmpfence_signature_count', 0);
        $server_total_count = get_option('vmpfence_server_signature_total', 0);
        $license_tier = VMPFence_Config::get('scanner_license_tier', 'free');
        
        // Add user custom signatures
        $user_signatures = VMPFence_Config::get('scan_include_extra', '');
        $user_count = empty($user_signatures) ? 0 : count(explode("\n", trim($user_signatures)));
        
        // Calculate percentage based on actual signature counts
        if ($server_total_count > 0 && ($local_signature_count > 0 || $user_count > 0)) {
            // Use real counts: (local + user custom) vs (server total + user custom)
            // User custom signatures are local-only additions, so they're added to both numerator and denominator
            $total_local = $local_signature_count + $user_count;
            $total_available = $server_total_count + $user_count;
            $malware_percentage = min(100, round(($total_local / $total_available) * 100));
        } elseif ($local_signature_count > 0 && $server_total_count == 0) {
            // Edge case: We have local signatures but no server total recorded
            // This can happen on fresh install or failed sync
            // Assume local count IS the server total (they're in sync)
            $total_local = $local_signature_count + $user_count;
            $malware_percentage = 100; // Assume fully synced if we have signatures but no server record
        } else {
            // Fallback for installations without stored counts (legacy or first-time setup)
            $final_count = $total_patterns + $user_count;
            $target_count = max(30, $final_count); // Avoid division by zero, use at least 30
            $malware_percentage = min(100, round(($final_count / $target_count) * 100));
        }
        
        // Status description based on tier and percentage
        if ($license_tier === 'premium') {
            // Premium users get all signatures immediately
            if ($malware_percentage >= 95) {
                $status = 'All latest signatures available';
            } elseif ($malware_percentage >= 85) {
                $status = 'Signatures updating...';
            } else {
                $status = 'Signature update recommended';
            }
        } else {
            // Free tier - signatures are delayed by 30 days
            if ($malware_percentage >= 90) {
                $status = 'Community signatures active (30-day delay)';
            } elseif ($malware_percentage >= 70) {
                $days_behind = 30 + round((100 - $malware_percentage) / 100 * 30);
                $status = 'Signatures delayed by ~' . $days_behind . ' days (free tier)';
            } else {
                $status = 'Signature update needed';
            }
        }
        
        wp_send_json_success([
            'scan_types' => $scan_type_percentage,
            'scan_type_name' => $scan_type_name,
            'malware_signatures' => $malware_percentage,
            'malware_status' => $status
        ]);
    }
    
    /**
     * Handle scan type percentage AJAX request (only returns scan type info)
     */
    public function handle_get_scan_type_percentage() {
        // Security checks
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        if (!wp_verify_nonce($nonce, 'vmpfence_scan_nonce')) {
            wp_die('Security check failed');
        }
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions');
        }
        
        // Get current scan type from form data if available, otherwise from database
        $selected_scan_type = '';
        
        // Check if scan type is provided in the request
        if (isset($_POST['selected_scan_type']) && !empty($_POST['selected_scan_type'])) {
            $selected_scan_type = sanitize_text_field(wp_unslash($_POST['selected_scan_type']));
        }
        
        // If no scan type from request, get from database
        if (empty($selected_scan_type)) {
            $scan_options = VMPFence_Scan_Config::getScanOptions();
            $selected_scan_type = $scan_options['scanType'] ?? 'standard';
        }
        
        switch($selected_scan_type) {
            case 'limited': 
                $scan_type_percentage = 40; 
                $scan_type_name = 'Limited';
                $scan_type_description = 'Limited detection capability with very low resource utilization';
                break;
            case 'standard': 
                $scan_type_percentage = 60; 
                $scan_type_name = 'Standard';
                $scan_type_description = 'Our recommendation for all websites. Best detection capability in the industry';
                break;
            case 'high_sensitivity': 
                $scan_type_percentage = 85; 
                $scan_type_name = 'High Sensitivity';
                $scan_type_description = 'More thorough but may produce false positives';
                break;
            case 'custom':
                $scan_options = VMPFence_Scan_Config::getScanOptions();
                $enabled_modules = 0;
                $key_modules = ['scansEnabled_core', 'scansEnabled_themes', 'scansEnabled_plugins', 
                               'scansEnabled_malware', 'scansEnabled_fileContents', 'scansEnabled_posts',
                               'scansEnabled_comments', 'scansEnabled_suspiciousOptions', 'scansEnabled_checkGSB',
                               'scansEnabled_checkReadableConfig', 'scansEnabled_suspectedFiles'];
                foreach($key_modules as $module) {
                    if (!empty($scan_options[$module])) $enabled_modules++;
                }
                $scan_type_percentage = round(($enabled_modules / 11) * 100);
                $scan_type_name = 'Custom';
                $scan_type_description = 'Selected automatically when General Options have been customized';
                break;
            default: 
                $scan_type_percentage = 60; 
                $scan_type_name = 'Standard';
                $scan_type_description = 'Our recommendation for all websites. Best detection capability in the industry';
        }
        
        wp_send_json_success([
            'scan_types' => $scan_type_percentage,
            'scan_type_name' => $scan_type_name,
            'scan_type_description' => $scan_type_description
        ]);
    }
    
    /**
     * Handle test AJAX request for diagnostics
     * Used by connectivity diagnostic tests
     *
     * @since 2.2.0
     */
    public function handle_test_ajax() {
        die("VMPWORDPRESSSECURITYSCANTESTOK");
    }
    
    /**
     * Handle log file download
     *
     * @since 2.2.0
     */
    public function handle_download_log() {
        // Security check
        if (!current_user_can('manage_options')) {
            wp_die(__('You do not have permission to download log files.', 'vmpfence-security'));
        }
        
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'vmpfence_diagnostics')) {
            wp_die(__('Security check failed.', 'vmpfence-security'));
        }
        
        // Get log file path
        $log_path = isset($_POST['log_path']) ? sanitize_text_field($_POST['log_path']) : '';
        
        if (empty($log_path)) {
            wp_die(__('No log file specified.', 'vmpfence-security'));
        }
        
        // Security: Verify the file exists and is a valid log file
        if (!file_exists($log_path)) {
            wp_die(__('Log file not found.', 'vmpfence-security'));
        }
        
        // Normalize path for comparison (handle backslashes and forward slashes)
        $log_path_normalized = wp_normalize_path(realpath($log_path));
        
        // Security: Only allow download of specific log files
        $allowed_files = array(
            WP_CONTENT_DIR . '/debug.log',
            ini_get('error_log'),
            'C:/xampp/php/logs/php_error_log',
            'C:\\xampp\\php\\logs\\php_error_log',
            '/var/log/php_errors.log',
            '/var/log/php/error.log'
        );
        
        // Filter out empty paths and normalize them
        $allowed_files = array_filter($allowed_files);
        $allowed_files_normalized = array();
        foreach ($allowed_files as $file) {
            if (file_exists($file)) {
                $allowed_files_normalized[] = wp_normalize_path(realpath($file));
            }
        }
        
        if (!in_array($log_path_normalized, $allowed_files_normalized)) {
            wp_die(__('Access denied. Invalid log file path.', 'vmpfence-security'));
        }
        
        // Set headers for download
        header('Content-Type: text/plain');
        header('Content-Disposition: attachment; filename="' . basename($log_path) . '"');
        header('Content-Length: ' . filesize($log_path));
        header('Cache-Control: no-cache, must-revalidate');
        header('Expires: 0');
        
        // Output file contents
        readfile($log_path);
        exit;
    }
    
    /**
     * Handle test email sending
     *
     * @since 2.2.0
     */
    public function handle_send_test_email() {
        // Security check
        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('You do not have permission to send test emails.', 'vmpfence-security'));
        }
        
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'vmpfence_diagnostics')) {
            wp_send_json_error(__('Security check failed.', 'vmpfence-security'));
        }
        
        // Get email address
        $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
        
        if (empty($email) || !is_email($email)) {
            wp_send_json_error(__('Invalid email address.', 'vmpfence-security'));
        }
        
        // Send test email via centralized Email Service (API → branded template)
        $context = array(
            'requesting_ip' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : 'unknown',
            'sent_at'       => current_time('mysql'),
        );

        $result = VMPFence_Email_Service::send_alert('test_email', $context, array($email));

        if ($result['success']) {
            wp_send_json_success(sprintf(
                /* translators: %s: delivery method (api or fallback) */
                __('Test email sent successfully via %s.', 'vmpfence-security'),
                $result['method']
            ));
        } else {
            wp_send_json_error(sprintf(
                /* translators: %s: error detail */
                __('Failed to send test email: %s', 'vmpfence-security'),
                $result['message']
            ));
        }
    }
    
    /**
     * Handle test activity report sending
     *
     * @since 2.2.0
     */
    public function handle_send_activity_report() {
        // Security check
        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('You do not have permission to send activity reports.', 'vmpfence-security'));
        }
        
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'vmpfence_diagnostics')) {
            wp_send_json_error(__('Security check failed.', 'vmpfence-security'));
        }
        
        // Get email address
        $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
        
        if (empty($email) || !is_email($email)) {
            wp_send_json_error(__('Invalid email address.', 'vmpfence-security'));
        }
        
        // Send test activity report via centralized Email Service (API → branded template)
        $context = array(
            'is_test'        => true,
            'report_period'  => 'Test Report',
            'wp_version'     => get_bloginfo('version'),
            'php_version'    => PHP_VERSION,
            'plugin_version' => defined('VMPFENCE_VERSION') ? VMPFENCE_VERSION : 'unknown',
            'sent_at'        => current_time('Y-m-d H:i:s'),
        );

        $result = VMPFence_Email_Service::send_alert('activity_report', $context, array($email));

        if ($result['success']) {
            wp_send_json_success(sprintf(
                __('Test activity report sent successfully via %s.', 'vmpfence-security'),
                $result['method']
            ));
        } else {
            wp_send_json_error(sprintf(
                __('Failed to send activity report: %s', 'vmpfence-security'),
                $result['message']
            ));
        }
    }
}

// Initialize AJAX handler
new VMPFence_AJAX_Handler();
