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

/**
 * Scan Recovery API Endpoint
 *
 * Simple, reliable REST API endpoint to check and recover stalled scans.
 * This is a streamlined version that does direct recovery with minimal overhead.
 *
 * Note: direct-resume.php is loaded before this file in the main plugin file
 * to ensure shared functions are available to all recovery mechanisms.
 */

/**
 * Register scan recovery REST API endpoints
 */
function wpsec_register_recovery_endpoints() {
    register_rest_route('wpsec/v1', '/scan-recovery', [
        'methods' => 'POST',
        'callback' => 'wpsec_api_scan_recovery',
        'permission_callback' => '__return_true' // Auth is handled in the callback
    ]);
}

add_action('rest_api_init', 'wpsec_register_recovery_endpoints');

/**
 * Ultra-simple recovery API endpoint that directly resumes stalled scans
 * 
 * @param object $request The WP REST request object
 * @return array Status information with recovery result
 */
function wpsec_api_scan_recovery($request) {
    // Check API key for authentication
    $api_key = $request->get_header('x-api-key');
    if ($api_key !== WPSEC_API_KEY) {
        return new WP_REST_Response([
            'status' => 'error',
            'message' => 'Invalid API key'
        ], 401);
    }
    
    // Get scan ID from request or find the active one
    $scan_id = $request->get_param('scan_id');
    $recovery_attempted = false;
    $recovery_needed = false;
    
    // If no specific scan ID, find the most recent active scan
    if (empty($scan_id)) {
        global $wpdb;
        $active_scan = $wpdb->get_row(
            "SELECT option_name FROM {$wpdb->options} 
             WHERE option_name LIKE 'wpsec_scan_%_status' 
             AND option_value != 'completed' AND option_value != 'failed'
             ORDER BY option_id DESC LIMIT 1"
        );
        
        if ($active_scan) {
            preg_match('/wpsec_scan_(.+)_status/', $active_scan->option_name, $matches);
            if (isset($matches[1])) {
                $scan_id = $matches[1];
            }
        }
    }
    
    // No active scan found
    if (empty($scan_id)) {
        return new WP_REST_Response([
            'status' => 'success',
            'message' => 'No active scans found',
            'scan' => null
        ]);
    }
    
    // Get scan status
    $status = get_option('wpsec_scan_' . $scan_id . '_status', '');
    $last_update = get_option('wpsec_scan_' . $scan_id . '_last_update', 0);
    $resume_position = get_option('wpsec_scan_' . $scan_id . '_resume_position', 0);
    $files_scanned = get_option('wpsec_scan_' . $scan_id . '_files_scanned', 0);
    $total_files = get_option('wpsec_scan_' . $scan_id . '_total_files', 0);
    $started_at = get_option('wpsec_scan_' . $scan_id . '_started_at', '');
    
    // Calculate time since last update
    $time_since_update = time() - $last_update;
    
    // Check if scan is stalled
    $stalled = false;
    
    // Case 1: Scan hasn't updated in 30 seconds
    if ($time_since_update > 30 && $status !== 'completed' && $status !== 'failed') {
        $stalled = true;
        $recovery_needed = true;
    }
    
    // Case 2: Scan is stuck in 'resuming' state for over 30 seconds
    if ($status === 'resuming' && $time_since_update > 30) {
        $stalled = true;
        $recovery_needed = true;
    }
    
    // Do direct recovery if needed - NO transients, NO cron, just direct resumption
    if ($recovery_needed) {
        // First update the status to make sure we're not in a 'resuming' loop
        update_option('wpsec_scan_' . $scan_id . '_status', 'scanning');
        update_option('wpsec_scan_' . $scan_id . '_last_update', time());
        
        // Get scan parameters
        $deep_scan = get_option('wpsec_scan_' . $scan_id . '_deep', false);
        $verbose = get_option('wpsec_scan_' . $scan_id . '_verbose', false);
        
        // Directly call the scan function in the background
        wpsec_debug_log("⚡ WPFort Recovery: Directly resuming scan {$scan_id} from position {$resume_position}", 'info');
        
        // Launch scan recovery via a non-blocking request to self
        $admin_url = admin_url('admin-ajax.php');
        $recovery_url = add_query_arg([
            'action' => 'wpsec_direct_resume',
            'scan_id' => $scan_id,
            'resume_from' => $resume_position,
            'deep_scan' => $deep_scan ? 1 : 0,
            'verbose' => $verbose ? 1 : 0,
            'nonce' => wp_create_nonce('wpsec_direct_resume')
        ], $admin_url);
        
        // Make a non-blocking request
        wp_remote_post($recovery_url, [
            'timeout' => 0.01,
            'blocking' => false,
            'sslverify' => false
        ]);
        
        $recovery_attempted = true;
    }
    
    // Server load information
    $server_env = wpsec_detect_server_environment();
    $cpu_load = isset($server_env['cpu_load']) ? $server_env['cpu_load'] : 0;
    
    // Calculate progress percentage
    $progress = $total_files > 0 ? round(($files_scanned / $total_files) * 100) : 0;
    
    // Return comprehensive status
    return new WP_REST_Response([
        'status' => 'success',
        'message' => $recovery_attempted ? 'Recovery initiated' : 'Scan status checked',
        'scan' => [
            'scan_id' => $scan_id,
            'status' => $status,
            'started_at' => $started_at,
            'last_update' => $last_update,
            'last_update_human' => human_time_diff($last_update) . ' ago',
            'files_scanned' => $files_scanned,
            'total_files' => $total_files,
            'progress' => $progress,
            'resume_position' => $resume_position,
            'stalled' => $stalled,
            'recovery_needed' => $recovery_needed,
            'recovery_attempted' => $recovery_attempted,
            'server_load' => $cpu_load
        ]
    ]);
}

/**
 * Check status of a scan and determine if recovery is needed
 * ULTRA-LIGHTWEIGHT: This function ONLY does detection, no recovery action
 *
 * @param string $scan_id The scan ID to check
 * @return array Status information
 */
function wpsec_check_scan_status($scan_id) {
    // Get current scan status - keep this super lightweight
    $status = get_option('wpsec_scan_' . $scan_id . '_status', '');
    $last_update = get_option('wpsec_scan_' . $scan_id . '_last_update', 0);
    $files_processed = get_option('wpsec_scan_' . $scan_id . '_files_processed', 0); 
    $resume_from = get_option('wpsec_scan_' . $scan_id . '_resume_position', 0);
    
    // Calculate time since last update
    $time_since_update = time() - $last_update;
    
    // Default values
    $stalled = false;
    $recovery_needed = false;
    $recovery_attempted = false;
    
    // Force recovery for these cases:
    
    // Case 1: Scan is paused - resume it immediately
    if ($status === 'paused') {
        $stalled = true;
        $recovery_needed = true;
    }
    
    // Case 2: Scan has been "resuming" for more than 60 seconds (stuck in resuming state)
    if ($status === 'resuming' && $time_since_update > 60) {
        $stalled = true;
        $recovery_needed = true;
        
        // Force status back to paused to break out of stuck "resuming" state
        update_option('wpsec_scan_' . $scan_id . '_status', 'paused');
    }
    
    // Case 3: Scan is scanning but hasn't updated in 30 seconds
    if ($status === 'scanning' && $time_since_update > 30) {
        $stalled = true;
        $recovery_needed = true;
    }
    
    // Just inform that recovery is needed - the actual recovery is done separately
    // This makes the API endpoint extremely fast
    if ($recovery_needed) {
        $recovery_attempted = true;
    }
    
    // Return status information
    return [
        'scan_id' => $scan_id,
        'status' => $status,
        'last_update' => $last_update,
        'last_update_human' => human_time_diff($last_update) . ' ago',
        'files_processed' => $files_processed,
        'resume_position' => $resume_from, 
        'stalled' => $stalled,
        'recovery_needed' => $recovery_needed,
        'recovery_attempted' => $recovery_attempted,
        'server_load' => wpsec_detect_server_environment()['cpu_load']
    ];
}

/**
 * Process any pending direct resume requests
 * This runs on EVERY page load in the admin area to ensure scans keep running
 */
function wpsec_process_direct_resume_requests() {
    global $wpdb;
    
    // Performance optimization: Only run this processing once every minute
    // This prevents multiple simultaneous API requests from all trying to process resume requests
    $last_check = get_transient('wpsec_direct_resume_last_check');
    if ($last_check !== false) {
        return; // Already checked recently, skip to avoid redundant processing
    }
    
    // Set a short-lived transient to prevent duplicate processing
    set_transient('wpsec_direct_resume_last_check', time(), 60); // 60 seconds
    
    // Only select option_name as that's all we need (performance)
    // Add LIMIT to prevent huge result sets (performance)
    // Use prepared statement for better security and performance
    $transients = $wpdb->get_col($wpdb->prepare(
        "SELECT option_name FROM {$wpdb->options} 
         WHERE option_name LIKE %s
         LIMIT 10",
        '_transient_wpsec_direct_resume_%'
    ));
    
    if (empty($transients)) {
        return;
    }
    
    foreach ($transients as $transient_name) {
        // Extract scan ID from transient name using a more efficient method
        $scan_id = str_replace('_transient_wpsec_direct_resume_', '', $transient_name);
        
        if (empty($scan_id)) {
            continue;
        }
        
        // Performance: Use a single query to get both status and resume position
        $status = get_option('wpsec_scan_' . $scan_id . '_status', '');
        $resume_from = get_option('wpsec_scan_' . $scan_id . '_resume_position', 0);
        
        // Delete the transient first to prevent repeated processing
        delete_option($transient_name); // Direct deletion is faster than delete_transient()
        
        // Only process if the scan exists and is either paused or stuck in resuming state
        if ($status === 'paused' || $status === 'resuming') {
            // Set status to ensure it's in the right state
            update_option('wpsec_scan_' . $scan_id . '_status', 'resuming');
            update_option('wpsec_scan_' . $scan_id . '_last_update', time());
            
            wpsec_debug_log("🔄 WPFort Direct Resume: Processing scan {$scan_id} from position {$resume_from}", 'info');
            
            // Get scan parameters
            $deep_scan = get_option('wpsec_scan_' . $scan_id . '_deep', false);
            $verbose = get_option('wpsec_scan_' . $scan_id . '_verbose', false);
            
            // Use shutdown function to run the scan after we send the HTTP response
            // This allows the API response to finish quickly while the scan runs in the background
            register_shutdown_function('wpsec_run_scan_on_shutdown', $scan_id, $deep_scan, $verbose, $resume_from);
        }
    }
}

// Hook into admin init to process direct resume requests
add_action('admin_init', 'wpsec_process_direct_resume_requests');
// Also hook into wp_loaded to catch non-admin requests
add_action('wp_loaded', 'wpsec_process_direct_resume_requests');

/**
 * Trigger WP Cron in a non-blocking way
 */
function wpsec_trigger_cron_nonblocking() {
    $cron_url = site_url('wp-cron.php?doing_wp_cron=1');
    
    // Use WordPress HTTP API for non-blocking request
    wp_remote_post(
        $cron_url,
        [
            'timeout'   => 0.01,   // Very short timeout - we don't need a response
            'blocking'  => false,  // Make the request non-blocking
            'sslverify' => false,  // Skip SSL verification for local requests
        ]
    );
}

/**
 * Handler for forced recovery action
 * 
 * @param string $scan_id The scan ID to recover
 */
function wpsec_handle_forced_recovery($scan_id) {
    // Check for recovery data
    $recovery_data = get_transient('wpsec_force_recovery_' . $scan_id);
    if (!$recovery_data) {
        return;
    }
    
    // Extract recovery parameters
    $resume_from = isset($recovery_data['resume_from']) ? $recovery_data['resume_from'] : 0;
    $deep_scan = isset($recovery_data['deep_scan']) ? $recovery_data['deep_scan'] : false;
    $verbose = isset($recovery_data['verbose']) ? $recovery_data['verbose'] : false;
    
    // Delete the transient to prevent duplicate recoveries
    delete_transient('wpsec_force_recovery_' . $scan_id);
    
    // Attempt to increase memory limit
    if (function_exists('wpsec_increase_memory_limit')) {
        wpsec_increase_memory_limit();
    }
    
    // Log the recovery attempt
    wpsec_debug_log("🔄 WPFort: Forced recovery of scan {$scan_id} from position {$resume_from}", 'info');
    
    // Run the scan
    if (function_exists('wpsec_run_scan')) {
        wpsec_run_scan($scan_id, $deep_scan, $verbose, $resume_from);
    }
}

// Register the recovery action
add_action('wpsec_forced_recovery', 'wpsec_handle_forced_recovery');
