<?php
/**
 * VMPFence Performance Manager
 * 
 * Handles performance controls for all VMPFence scanners including:
 * - Low resource scanning
 * - Memory management
 * - Time limits and duration controls
 * - Resource throttling
 *
 * @package VMPFence
 * @since 1.0.0
 */

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

class VMPFence_Performance_Manager {
    
    /**
     * Default values
     */
    const DEFAULT_MAX_SCAN_TIME = 10800; // 3 hours
    const DEFAULT_MAX_EXEC_TIME = 15;     // 15 seconds
    const DEFAULT_MEMORY_LIMIT = 256;     // 256MB
    const MIN_EXEC_TIME = 8;             // Minimum 8 seconds
    const MAX_INI_EXEC_TIME = 90;        // Max 90 seconds from ini
    
    /**
     * Performance tracking
     */
    private static $scan_start_time = null;
    private static $stage_start_time = null;
    private static $low_resource_wait_step = false;
    private static $performance_metrics = [];
    
    /**
     * Initialize performance management
     */
    public static function init() {
        // Only initialize once per scan to avoid multiple resets
        if (self::$scan_start_time !== null) {
            return; // Already initialized
        }
        
        self::$scan_start_time = time();
        self::$stage_start_time = time();
        self::$performance_metrics = [
            'start_time' => self::$scan_start_time,
            'memory_start' => memory_get_usage(true),
            'peak_memory' => 0,
            'stages_completed' => 0,
            'sleep_cycles' => 0,
            'time_limit_checks' => 0
        ];
        
        // Request maximum memory if configured
        self::request_max_memory();
        
        // Set execution time limit if configured
        self::set_execution_time_limit();
        
        // Log performance settings for debugging (only once)
        self::log_performance_settings();
    }
    
    /**
     * Log current performance settings (for debugging)
     */
    private static function log_performance_settings() {
        // Only log once per scan to avoid spam
        static $logged = false;
        if ($logged) {
            return;
        }
        $logged = true;
        
        $low_resource = self::use_low_resource_scanning();
        $max_duration = self::get_max_scan_duration();
        $max_exec = self::get_max_exec_time();
        $max_memory = self::get_max_memory();
        $max_issues = self::get_max_issues();
    }
    
    /**
     * Check if low resource scanning is enabled
     * 
     * @return bool
     */
    public static function use_low_resource_scanning() {
        // Try multiple option sources for compatibility
        $scan_options = get_option('vmpfence_scan_options', []);
        if (isset($scan_options['lowResourceScansEnabled'])) {
            return !empty($scan_options['lowResourceScansEnabled']);
        }
        
        // Fallback to VMPFence_Config
        return (bool) VMPFence_Config::get('lowResourceScansEnabled', false);
    }
    
    /**
     * Get maximum scan duration in seconds
     * 
     * @return int
     */
    public static function get_max_scan_duration() {
        // Try multiple option sources for compatibility
        $scan_options = get_option('vmpfence_scan_options', []);
        if (isset($scan_options['scan_maxDuration'])) {
            $duration = intval($scan_options['scan_maxDuration']);
        } else {
            // Fallback to VMPFence_Config
            $duration = intval(VMPFence_Config::get('scan_maxDuration', 0));
        }
        
        if ($duration <= 0) {
            $duration = self::DEFAULT_MAX_SCAN_TIME;
        }
        
        return $duration;
    }
    
    /**
     * Get maximum execution time per stage
     * 
     * @return int
     */
    public static function get_max_exec_time() {
        // Try multiple option sources for compatibility  
        $scan_options = get_option('vmpfence_scan_options', []);
        if (isset($scan_options['scan_maxExecTime'])) {
            $exec_time = intval($scan_options['scan_maxExecTime']);
        } else {
            // Fallback to VMPFence_Config
            $exec_time = intval(VMPFence_Config::get('scan_maxExecTime', 0));
        }
        
        // If 0 or empty
        if ($exec_time <= 0) {
            $exec_time = self::detect_max_execution_time();
        }
        
        // Ensure minimum execution time
        return max($exec_time, self::MIN_EXEC_TIME);
    }
    
    /**
     * Detect maximum execution time
     * 
     * @return int
     */
    private static function detect_max_execution_time() {
        // Get PHP's max_execution_time only once to avoid instability
        static $cached_exec_time = null;
        
        if ($cached_exec_time !== null) {
            return $cached_exec_time;
        }
        
        // Get PHP's max_execution_time
        $ini_time = @ini_get('max_execution_time');
        
        if (is_numeric($ini_time)) {
            $ini_time = (int) $ini_time;
            
            // If it's reasonable, use half of it
            if ($ini_time >= self::MIN_EXEC_TIME) {
                // (max 90 seconds from ini)
                if ($ini_time > self::MAX_INI_EXEC_TIME) {
                    $ini_time = self::MAX_INI_EXEC_TIME;
                }
                $cached_exec_time = floor($ini_time / 2);
                return $cached_exec_time;
            }
        }
        
        // Default fallback
        $cached_exec_time = self::DEFAULT_MAX_EXEC_TIME;
        return $cached_exec_time;
    }
    
    /**
     * Get maximum memory limit in MB
     * 
     * @return int
     */
    public static function get_max_memory() {
        // Try multiple option sources for compatibility
        $scan_options = get_option('vmpfence_scan_options', []);
        if (isset($scan_options['maxMem'])) {
            $memory = intval($scan_options['maxMem']);
        } else {
            // Fallback to VMPFence_Config
            $memory = intval(VMPFence_Config::get('maxMem', 0));
        }
        
        if ($memory <= 0) {
            $memory = self::DEFAULT_MEMORY_LIMIT;
        }
        
        return $memory;
    }
    
    /**
     * Get maximum issues to process/report
     * 
     * @return int (0 = unlimited)
     */
    public static function get_max_issues() {
        // Try multiple option sources for compatibility
        $scan_options = get_option('vmpfence_scan_options', []);
        if (isset($scan_options['scan_maxIssues'])) {
            return intval($scan_options['scan_maxIssues']);
        } else {
            // Fallback to VMPFence_Config
            return intval(VMPFence_Config::get('scan_maxIssues', 0));
        }
    }
    
    /**
     * Request maximum memory limit
     */
    private static function request_max_memory() {
        $max_mem = self::get_max_memory();
        
        if (function_exists('memory_get_usage') && self::get_memory_limit_bytes() < ($max_mem * 1024 * 1024)) {
            @ini_set('memory_limit', $max_mem . 'M');
        }
    }
    
    /**
     * Set execution time limit for current stage
     */
    private static function set_execution_time_limit() {
        $max_exec = self::get_max_exec_time();
        @set_time_limit($max_exec);
    }
    
    /**
     * Get current PHP memory limit in bytes
     * 
     * @return int
     */
    private static function get_memory_limit_bytes() {
        $limit = ini_get('memory_limit');
        if (is_numeric($limit)) {
            return (int) $limit;
        }
        
        $value = (int) $limit;
        $unit = strtolower(substr($limit, -1));
        
        switch ($unit) {
            case 'g':
                return $value * 1024 * 1024 * 1024;
            case 'm':
                return $value * 1024 * 1024;
            case 'k':
                return $value * 1024;
            default:
                return $value;
        }
    }
    
    /**
     * Check if scan time limit has been exceeded
     * Throws exception if limit exceeded
     * 
     * @throws VMPFence_Scan_Duration_Exception
     */
    public static function check_time_limit() {
        if (self::$scan_start_time === null) {
            return; // Not initialized
        }
        
        self::$performance_metrics['time_limit_checks']++;
        
        $max_duration = self::get_max_scan_duration();
        $elapsed = time() - self::$scan_start_time;
        
        if ($elapsed > $max_duration) {
            $error = sprintf(
                'VMPFence scan time limit of %d seconds has been exceeded (elapsed: %d seconds). The scan has been terminated.',
                $max_duration,
                $elapsed
            );
            throw new VMPFence_Scan_Duration_Exception( esc_html( $error ) );
        }
    }
    
    /**
     * Handle stage transition with performance controls
     * This is called between scanner stages
     * 
     * @param string $stage_name
     * @param bool $is_fork Whether this is a fork process
     */
    public static function stage_transition($stage_name, $is_fork = false) {
        // Check time limits first
        self::check_time_limit();
        
        // Update performance metrics
        self::$performance_metrics['stages_completed']++;
        self::$performance_metrics['peak_memory'] = max(
            self::$performance_metrics['peak_memory'],
            memory_get_usage(true)
        );
        
        // Handle low resource scanning
        if (self::use_low_resource_scanning()) {
            self::handle_low_resource_transition($stage_name, $is_fork);
        }
        
        // Reset stage timer
        self::$stage_start_time = time();
    }
    
    /**
     * Handle low resource scanning transition
     * 
     * @param string $stage_name
     * @param bool $is_fork
     */
    private static function handle_low_resource_transition($stage_name, $is_fork) {
        // Toggle wait step
        self::$low_resource_wait_step = !self::$low_resource_wait_step;
        
        if ($is_fork && self::$low_resource_wait_step) {
            $sleep_time = (int) round(self::get_max_exec_time() / 2);
            sleep($sleep_time);
            self::$performance_metrics['sleep_cycles']++;
        } else if (!$is_fork) {
            // error_log("VMPFence Performance: Low resource mode active - non-fork execution, applying throttling");
        }
    }
    
    /**
     * Throttle processing for low resource mode
     * Call this during intensive operations
     * 
     * @param int $items_processed Number of items processed since last throttle
     */
    public static function throttle_processing($items_processed = 1) {
        if (!self::use_low_resource_scanning()) {
            return;
        }
        
        // Sleep briefly every 10 items in low resource mode
        if ($items_processed > 0 && $items_processed % 10 === 0) {
            usleep(100000); // 0.1 seconds
            self::$performance_metrics['sleep_cycles']++;
        }
        
        // Also throttle on every 50 items for heavier throttling
        if ($items_processed > 0 && $items_processed % 50 === 0) {
            usleep(250000); // 0.25 seconds additional
            self::$performance_metrics['sleep_cycles']++;
        }
    }
    
    /**
     * Check if we should limit the number of issues
     * 
     * @param array $issues Current issues array
     * @return array Potentially limited issues array
     */
    public static function limit_issues($issues) {
        $max_issues = self::get_max_issues();
        
        if ($max_issues > 0 && count($issues) > $max_issues) {
            return array_slice($issues, 0, $max_issues);
        }
        
        return $issues;
    }
    
    /**
     * Get current performance metrics
     * 
     * @return array
     */
    public static function get_performance_metrics() {
        if (self::$scan_start_time) {
            self::$performance_metrics['total_duration'] = time() - self::$scan_start_time;
        }
        
        self::$performance_metrics['peak_memory'] = max(
            self::$performance_metrics['peak_memory'],
            memory_get_usage(true)
        );
        
        self::$performance_metrics['current_memory'] = memory_get_usage(true);
        self::$performance_metrics['memory_used'] = self::$performance_metrics['current_memory'] - 
                                                   self::$performance_metrics['memory_start'];
        
        return self::$performance_metrics;
    }
    
    /**
     * Format bytes for human reading
     * 
     * @param int $bytes
     * @return string
     */
    private static function format_bytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        
        $bytes /= (1 << (10 * $pow));
        
        return round($bytes, 2) . ' ' . $units[$pow];
    }
    
    /**
     * Reset performance tracking (for new scan)
     */
    public static function reset() {
        self::$scan_start_time = null;
        self::$stage_start_time = null;
        self::$low_resource_wait_step = false;
        self::$performance_metrics = [];
    }
    
    /**
     * Log performance summary at end of scan
     */
    public static function log_performance_summary() {
        $metrics = self::get_performance_metrics();
    }
}

/**
 * Custom exception for scan duration limit exceeded
 */
class VMPFence_Scan_Duration_Exception extends Exception {}
