<?php

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

/**
 * VMPFence Advanced Scan Engine
 * @since 1.0.0
 * @since 2.1.1 Enhanced with memory optimization, scan forking, and checkpointing
 */
class VMPFence_Advanced_Scan_Engine {
    
    private $memory_limit;
    private $max_execution_time;
    private $checkpoint_file;
    private $scan_state;
    private $performance_metrics;
    private $fork_enabled;
    private $max_forks;
    private $current_forks;
    
    public function __construct() {
        $this->memory_limit = $this->get_memory_limit();
        $this->max_execution_time = ini_get('max_execution_time') ?: 300;
        
        // Set checkpoint file path (WordPress-safe)
        if (function_exists('wp_upload_dir')) {
            $upload_dir = wp_upload_dir();
            $this->checkpoint_file = $upload_dir['basedir'] . '/vmpfence_scan_checkpoint.json';
        } else {
            // Fallback for testing or non-WordPress environments
            $this->checkpoint_file = sys_get_temp_dir() . '/vmpfence_scan_checkpoint.json';
        }
        $this->scan_state = array();
        $this->performance_metrics = array();
        $this->fork_enabled = function_exists('pcntl_fork') && function_exists('pcntl_wait');
        
        // Get CPU count (Windows compatible)
        if (PHP_OS_FAMILY === 'Windows') {
            $cpu_count = (int)getenv('NUMBER_OF_PROCESSORS') ?: 2;
        } else {
            // Check if shell_exec is available (often disabled on shared hosting)
            if (function_exists('shell_exec') && !in_array('shell_exec', array_map('trim', explode(',', ini_get('disable_functions'))))) {
                $cpu_count = (int)@shell_exec('nproc 2>/dev/null') ?: 2;
            } else {
                // Fallback when shell_exec is disabled - safe default for shared hosting
                $cpu_count = 2;
            }
        }
        $this->max_forks = min(4, max(1, $cpu_count));
        $this->current_forks = 0;
        
        $this->initialize_scan_state();
    }
    
    /**
     * Initialize scan state and performance metrics
     */
    private function initialize_scan_state() {
        $this->scan_state = array(
            'started' => false,
            'completed' => false,
            'paused' => false,
            'total_files' => 0,
            'scanned_files' => 0,
            'current_directory' => '',
            'last_checkpoint' => 0,
            'memory_usage' => array(),
            'scan_start_time' => 0,
            'estimated_completion' => 0
        );
        
        $this->performance_metrics = array(
            'files_per_second' => 0,
            'memory_peak' => 0,
            'memory_average' => 0,
            'checkpoint_frequency' => 100, // Save checkpoint every 100 files
            'fork_success_rate' => 0,
            'scan_efficiency' => 0
        );
    }
    
    /**
     * Optimize memory usage for large site scanning
     */
    public function optimize_memory_usage() {
        // Set memory limit if possible
        if (function_exists('ini_set')) {
            $current_limit = ini_get('memory_limit');
            $desired_limit = '512M';
            
            if ($this->parse_memory_limit($current_limit) < $this->parse_memory_limit($desired_limit)) {
                // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Needed for scan optimization
                @ini_set('memory_limit', $desired_limit);
            }
        }
        
        // Enable garbage collection
        if (function_exists('gc_enable')) {
            gc_enable();
        }
        
        // Set realistic execution time for large scans
        if (function_exists('set_time_limit')) {
            // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Required for long-running scans
            set_time_limit(0); // No time limit for CLI scans
        }
        
        // Optimize PHP settings for memory efficiency
        if (function_exists('ini_set')) {
            // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Necessary for regex optimization
            @ini_set('pcre.backtrack_limit', '100000');
            // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Necessary for regex optimization
            @ini_set('pcre.recursion_limit', '100000');
        }
        
        return true;
    }
    
    /**
     * Implement scan forking for parallel processing
     */
    public function implement_scan_forking($file_chunks) {
        if (!$this->fork_enabled || count($file_chunks) <= 1) {
            return false; // Fall back to sequential scanning
        }
        
        $child_pids = array();
        $results = array();
        
        foreach ($file_chunks as $chunk_id => $files) {
            if ($this->current_forks >= $this->max_forks) {
                // Wait for a child to complete
                $pid = pcntl_wait($status);
                if ($pid > 0) {
                    $this->current_forks--;
                    unset($child_pids[$pid]);
                }
            }
            
            $pid = pcntl_fork();
            
            if ($pid == -1) {
                // Fork failed
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Using error_log for debugging purposes
                // error_log("VMPFence: Fork failed for chunk $chunk_id");
                return false;
            } elseif ($pid == 0) {
                // Child process
                $this->scan_file_chunk($files, $chunk_id);
                exit(0);
            } else {
                // Parent process
                $child_pids[$pid] = $chunk_id;
                $this->current_forks++;
            }
        }
        
        // Wait for all children to complete
        while ($this->current_forks > 0) {
            $pid = pcntl_wait($status);
            if ($pid > 0) {
                $this->current_forks--;
                unset($child_pids[$pid]);
            }
        }
        
        return $this->collect_fork_results($file_chunks);
    }
    
    /**
     * Scan a chunk of files in a forked process
     */
    private function scan_file_chunk($files, $chunk_id) {
        $chunk_results = array();
        
        foreach ($files as $file) {
            try {
                // Simulate scanning logic here
                $file_result = $this->scan_single_file_fork($file);
                if ($file_result) {
                    $chunk_results[] = $file_result;
                }
                
                // Monitor memory in child process
                $this->monitor_child_memory();
                
            } catch (Exception $e) {
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Using error_log for debugging purposes
                // error_log("VMPFence Fork: Error scanning $file: " . $e->getMessage());
            }
        }
        
        // Save chunk results to temporary file
        if (function_exists('wp_upload_dir')) {
            $upload_dir = wp_upload_dir();
            $chunk_file = $upload_dir['basedir'] . "/vmpfence_chunk_{$chunk_id}.json";
        } else {
            $chunk_file = sys_get_temp_dir() . "/vmpfence_chunk_{$chunk_id}.json";
        }
        file_put_contents($chunk_file, json_encode($chunk_results));
    }
    
    /**
     * Scan a single file in forked process context
     */
    private function scan_single_file_fork($file_path) {
        if (!is_readable($file_path)) {
            return false;
        }
        
        // Basic file scanning logic for fork context
        $file_size = filesize($file_path);
        if ($file_size === false || $file_size > 50 * 1024 * 1024) { // Skip files > 50MB
            return false;
        }
        
        $content = file_get_contents($file_path);
        if ($content === false) {
            return false;
        }
        
        // Simple malware check (would integrate with main scanner)
        $suspicious_patterns = array(
            '/eval\s*\(\s*base64_decode/',
            '/system\s*\(\s*\$_/',
            '/exec\s*\(\s*\$_/'
        );
        
        foreach ($suspicious_patterns as $pattern) {
            if (preg_match($pattern, $content)) {
                return array(
                    'file' => $file_path,
                    'pattern' => $pattern,
                    'found_at' => time(),
                    'chunk_processed' => true
                );
            }
        }
        
        return false;
    }
    
    /**
     * Monitor memory usage in child processes
     */
    private function monitor_child_memory() {
        $memory_usage = memory_get_usage(true);
        $memory_peak = memory_get_peak_usage(true);
        
        // If memory usage is too high, force garbage collection
        if ($memory_usage > ($this->memory_limit * 0.8)) {
            if (function_exists('gc_collect_cycles')) {
                gc_collect_cycles();
            }
        }
        
        // Log critical memory usage
        // if ($memory_usage > ($this->memory_limit * 0.9)) {
        //     // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Using error_log for debugging purposes
        //     error_log("VMPFence Fork: High memory usage: " . number_format($memory_usage / 1024 / 1024, 2) . "MB");
        // }
    }
    
    /**
     * Collect results from all forked processes
     */
    private function collect_fork_results($file_chunks) {
        $all_results = array();
        
        if (function_exists('wp_upload_dir')) {
            $upload_dir = wp_upload_dir();
            $upload_base_dir = $upload_dir['basedir'];
        } else {
            $upload_base_dir = sys_get_temp_dir();
        }
        
        foreach ($file_chunks as $chunk_id => $files) {
            $chunk_file = $upload_base_dir . "/vmpfence_chunk_{$chunk_id}.json";
            
            if (file_exists($chunk_file)) {
                $chunk_results = json_decode(file_get_contents($chunk_file), true);
                if (is_array($chunk_results)) {
                    $all_results = array_merge($all_results, $chunk_results);
                }
                
                // Clean up chunk file
                wp_delete_file($chunk_file);
            }
        }
        
        return $all_results;
    }
    
    /**
     * Create scan checkpoint for resumption
     */
    public function create_scan_checkpoint($scan_progress) {
        $checkpoint_data = array(
            'timestamp' => time(),
            'scan_state' => $this->scan_state,
            'progress' => $scan_progress,
            'performance_metrics' => $this->performance_metrics,
            'memory_usage' => memory_get_usage(true),
            'memory_peak' => memory_get_peak_usage(true),
            'files_scanned' => $this->scan_state['scanned_files'],
            'total_files' => $this->scan_state['total_files'],
            'current_directory' => $this->scan_state['current_directory']
        );
        
        $result = file_put_contents($this->checkpoint_file, json_encode($checkpoint_data, JSON_PRETTY_PRINT));
        
        if ($result !== false) {
            $this->scan_state['last_checkpoint'] = time();
            return true;
        }
        
        return false;
    }
    
    /**
     * Load scan checkpoint for resumption
     */
    public function load_scan_checkpoint() {
        if (!file_exists($this->checkpoint_file)) {
            return false;
        }
        
        $checkpoint_data = json_decode(file_get_contents($this->checkpoint_file), true);
        
        if (!is_array($checkpoint_data)) {
            return false;
        }
        
        $this->scan_state = $checkpoint_data['scan_state'];
        $this->performance_metrics = $checkpoint_data['performance_metrics'];
        
        return $checkpoint_data;
    }
    
    /**
     * Check if scan can be resumed
     */
    public function can_resume_scan() {
        $checkpoint_data = $this->load_scan_checkpoint();
        
        if (!$checkpoint_data) {
            return false;
        }
        
        // Check if checkpoint is not too old (24 hours)
        $checkpoint_age = time() - $checkpoint_data['timestamp'];
        if ($checkpoint_age > 86400) {
            $this->clear_checkpoint();
            return false;
        }
        
        // Check if scan was completed
        if (isset($checkpoint_data['scan_state']['completed']) && $checkpoint_data['scan_state']['completed']) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Clear checkpoint file
     */
    public function clear_checkpoint() {
        if (file_exists($this->checkpoint_file)) {
            wp_delete_file($this->checkpoint_file);
        }
    }
    
    /**
     * Monitor overall memory usage during scan
     */
    public function monitor_memory_usage() {
        $current_memory = memory_get_usage(true);
        $peak_memory = memory_get_peak_usage(true);
        
        $this->scan_state['memory_usage'][] = array(
            'timestamp' => time(),
            'current' => $current_memory,
            'peak' => $peak_memory
        );
        
        // Update performance metrics
        $this->performance_metrics['memory_peak'] = max($this->performance_metrics['memory_peak'], $peak_memory);
        
        $memory_values = array_column($this->scan_state['memory_usage'], 'current');
        $this->performance_metrics['memory_average'] = array_sum($memory_values) / count($memory_values);
        
        // Check if memory usage is critical
        if ($current_memory > ($this->memory_limit * 0.85)) {
            $this->handle_memory_pressure();
            return 'critical';
        } elseif ($current_memory > ($this->memory_limit * 0.70)) {
            return 'high';
        }
        
        return 'normal';
    }
    
    /**
     * Handle memory pressure situations
     */
    private function handle_memory_pressure() {
        // Force garbage collection
        if (function_exists('gc_collect_cycles')) {
            gc_collect_cycles();
        }
        
        // Create checkpoint before potential crash
        $this->create_scan_checkpoint(array(
            'reason' => 'memory_pressure',
            'memory_usage' => memory_get_usage(true),
            'memory_limit' => $this->memory_limit
        ));
        
        // Log memory pressure
        // error_log("VMPFence: Memory pressure detected - " . number_format(memory_get_usage(true) / 1024 / 1024, 2) . "MB used");
    }
    
    /**
     * Get memory limit in bytes
     */
    private function get_memory_limit() {
        $memory_limit = ini_get('memory_limit');
        return $this->parse_memory_limit($memory_limit);
    }
    
    /**
     * Parse memory limit string to bytes
     */
    private function parse_memory_limit($limit) {
        if ($limit == -1) {
            return PHP_INT_MAX;
        }
        
        $limit = trim($limit);
        $last_char = strtolower($limit[strlen($limit) - 1]);
        $numeric_value = intval($limit);
        
        switch ($last_char) {
            case 'g':
                $numeric_value *= 1024 * 1024 * 1024;
                break;
            case 'm':
                $numeric_value *= 1024 * 1024;
                break;
            case 'k':
                $numeric_value *= 1024;
                break;
        }
        
        return $numeric_value;
    }
    
    /**
     * Update scan progress
     */
    public function update_scan_progress($files_scanned, $total_files, $current_directory) {
        $this->scan_state['scanned_files'] = $files_scanned;
        $this->scan_state['total_files'] = $total_files;
        $this->scan_state['current_directory'] = $current_directory;
        
        // Calculate performance metrics
        $elapsed_time = time() - $this->scan_state['scan_start_time'];
        if ($elapsed_time > 0) {
            $this->performance_metrics['files_per_second'] = $files_scanned / $elapsed_time;
        }
        
        // Estimate completion time
        if ($this->performance_metrics['files_per_second'] > 0) {
            $remaining_files = $total_files - $files_scanned;
            $estimated_seconds = $remaining_files / $this->performance_metrics['files_per_second'];
            $this->scan_state['estimated_completion'] = time() + $estimated_seconds;
        }
        
        // Create checkpoint if needed
        if ($files_scanned > 0 && $files_scanned % $this->performance_metrics['checkpoint_frequency'] == 0) {
            $this->create_scan_checkpoint(array(
                'files_scanned' => $files_scanned,
                'total_files' => $total_files,
                'progress_percent' => ($files_scanned / $total_files) * 100
            ));
        }
    }
    
    /**
     * Start scan with advanced engine
     */
    public function start_scan() {
        $this->scan_state['started'] = true;
        $this->scan_state['scan_start_time'] = time();
        $this->scan_state['completed'] = false;
        
        // Optimize memory settings
        $this->optimize_memory_usage();
        
        return true;
    }
    
    /**
     * Complete scan
     */
    public function complete_scan() {
        $this->scan_state['completed'] = true;
        $this->clear_checkpoint();
        
        return array(
            'scan_state' => $this->scan_state,
            'performance_metrics' => $this->performance_metrics,
            'total_time' => time() - $this->scan_state['scan_start_time']
        );
    }
    
    /**
     * Get current scan state
     */
    public function get_scan_state() {
        return $this->scan_state;
    }
    
    /**
     * Get performance metrics
     */
    public function get_performance_metrics() {
        return $this->performance_metrics;
    }
    
    /**
     * Check if forking is supported
     */
    public function is_fork_supported() {
        return $this->fork_enabled;
    }
}
