<?php
defined('ABSPATH') || exit;

/**
 * Direct scan resume handler
 * 
 * Provides a direct, non-blocking way to resume stalled scans
 * This file is responsible for processing AJAX requests for scan resumption
 * and contains helper functions for non-blocking scan execution
 */

/**
 * Register the AJAX action for direct scan resumption
 */
add_action('wp_ajax_wpsec_direct_resume', 'wpsec_handle_direct_resume');
add_action('wp_ajax_nopriv_wpsec_direct_resume', 'wpsec_handle_direct_resume');

/**
 * Hooks to process resume requests on both admin and frontend page loads
 * This ensures scans can recover even without the API being called
 */
add_action('admin_init', 'wpsec_check_resume_on_pageload');
add_action('wp_loaded', 'wpsec_check_resume_on_pageload');

/**
 * Handle direct scan resume requests
 * Designed to be ultra-reliable and lightweight
 */
function wpsec_handle_direct_resume() {
    // Basic security checks
    if (!isset($_REQUEST['scan_id']) || !isset($_REQUEST['nonce'])) {
        wp_die('Invalid request');
    }
    
    // Check nonce
    if (!wp_verify_nonce($_REQUEST['nonce'], 'wpsec_direct_resume')) {
        wp_die('Security check failed');
    }
    
    // Get parameters
    $scan_id = sanitize_text_field($_REQUEST['scan_id']);
    $resume_from = isset($_REQUEST['resume_from']) ? intval($_REQUEST['resume_from']) : 0;
    $deep_scan = isset($_REQUEST['deep_scan']) && $_REQUEST['deep_scan'] == 1;
    $verbose = isset($_REQUEST['verbose']) && $_REQUEST['verbose'] == 1;
    
    // Send immediate response to avoid timeout and let the scan run
    // in the background
    if (function_exists('fastcgi_finish_request')) {
        // Non-blocking response for PHP-FPM
        header('Content-Type: application/json');
        echo json_encode([
            'status' => 'success',
            'message' => 'Resuming scan in background'
        ]);
        
        // This sends the response to the client immediately and 
        // lets the PHP script continue running in the background
        fastcgi_finish_request();
    } else {
        // Basic non-blocking approach for non-FPM setups
        header('Content-Type: application/json');
        header('Connection: close');
        ignore_user_abort(true);
        ob_start();
        echo json_encode([
            'status' => 'success',
            'message' => 'Resuming scan in background'
        ]);
        $size = ob_get_length();
        header("Content-Length: $size");
        ob_end_flush();
        flush();
    }
    
    // Log the resume attempt
    wpsec_debug_log("⚡ WPFort: Direct resume handler - Resuming scan $scan_id from position $resume_from", 'info');
    
    // Make sure the scan status is set to scanning
    update_option('wpsec_scan_' . $scan_id . '_status', 'scanning');
    update_option('wpsec_scan_' . $scan_id . '_last_update', time());
    
    // Run the scan function directly - this will now run in the background
    // after the HTTP response has been sent
    if (function_exists('wpsec_run_scan')) {
        wpsec_run_scan($scan_id, $deep_scan, $verbose, $resume_from);
    } else {
        wpsec_debug_log("❌ WPFort: Direct resume failed - wpsec_run_scan function not found", 'error');
        update_option('wpsec_scan_' . $scan_id . '_status', 'failed');
        update_option('wpsec_scan_' . $scan_id . '_last_update', time());
    }
    
    // This is technically unreachable code since the scan function should run to completion,
    // but we include it as a safety measure
    exit;
}

/**
 * Check for stalled scans on every page load and resume them if needed
 * This is a lightweight check that runs on both admin and frontend page loads
 */
function wpsec_check_resume_on_pageload() {
    // Don't run this check on AJAX requests to avoid duplication
    if (defined('DOING_AJAX') && DOING_AJAX) {
        return;
    }
    
    // Performance optimization: Only check for stalled scans once every 60 seconds
    // This prevents every page load from running the same expensive check
    $last_check = get_transient('wpsec_stalled_scan_last_check');
    if ($last_check !== false) {
        return; // Already checked recently
    }
    
    // Set a short-lived transient to prevent duplicate processing
    set_transient('wpsec_stalled_scan_last_check', time(), 60); // 60 seconds
    
    // Find stalled scans with prepared statement and limit
    global $wpdb;
    $stalled_scans = $wpdb->get_results(
        $wpdb->prepare(
            "SELECT option_name, option_value FROM {$wpdb->options} 
             WHERE option_name LIKE %s 
             AND (option_value = %s OR option_value = %s)
             LIMIT 5", // Only check a few at a time
            $wpdb->esc_like('wpsec_scan_') . '%_status',
            'scanning',
            'resuming'
        )
    );
    
    if (empty($stalled_scans)) {
        return;
    }
    
    // Keep track of how many scans we've processed
    $processed = 0;
    
    foreach ($stalled_scans as $option) {
        // Extract scan ID using a more efficient method 
        // (using str_replace instead of regex is faster)
        $scan_id = str_replace(['wpsec_scan_', '_status'], '', $option->option_name);
        
        if (empty($scan_id)) {
            continue;
        }
        
        $last_update = get_option('wpsec_scan_' . $scan_id . '_last_update', 0);
        $time_since_update = time() - $last_update;
        
        // Only process scans that haven't updated in at least 30 seconds
        if ($time_since_update < 30) {
            continue;
        }
        
        // Get scan info - more efficient by using fewer database calls
        $resume_from = intval(get_option('wpsec_scan_' . $scan_id . '_resume_position', 0));
        $deep_scan = (bool)get_option('wpsec_scan_' . $scan_id . '_deep', false);
        $verbose = (bool)get_option('wpsec_scan_' . $scan_id . '_verbose', false);
        
        // Update status and last update time
        update_option('wpsec_scan_' . $scan_id . '_status', 'scanning');
        update_option('wpsec_scan_' . $scan_id . '_last_update', time());
        
        // Log the resumption
        wpsec_debug_log("💤 WPFort: Auto-resuming stalled scan {$scan_id} (no updates for {$time_since_update}s)", 'info');
        
        // Only process one scan per page load
        register_shutdown_function('wpsec_run_scan_on_shutdown', $scan_id, $deep_scan, $verbose, $resume_from);
        
        // Only process one scan per page load
        $processed++;
        if ($processed > 0) {
            break;
        }
    }
}

/**
 * Run a scan in the background after the HTTP response has been sent
 * This is registered as a shutdown function for non-blocking scan execution
 * 
 * @param string $scan_id The scan ID
 * @param bool $deep_scan Whether to perform a deep scan
 * @param bool $verbose_logging Whether to log verbose details
 * @param int $resume_from Position to resume from
 */
function wpsec_run_scan_on_shutdown($scan_id, $deep_scan = false, $verbose_logging = false, $resume_from = 0) {
    // Make sure we're at the end of all output
    if (function_exists('fastcgi_finish_request')) {
        @fastcgi_finish_request();
    }
    
    // Close any database connections to prevent timeout issues
    global $wpdb;
    if ($wpdb) {
        $wpdb->close();
    }
    
    // Set unlimited execution time for scan process
    @set_time_limit(0);
    @ini_set('max_execution_time', 0);
    
    // Log the start of background scan
    wpsec_debug_log("🔥 WPFort: Running scan {$scan_id} in background (resume from {$resume_from})", 'info');
    
    // Load scan function if not already loaded
    if (!function_exists('wpsec_run_scan')) {
        require_once dirname(__FILE__) . '/scan.php';
    }
    
    // Run the scan directly
    wpsec_run_scan($scan_id, $deep_scan, $verbose_logging, $resume_from);
}
