<?php

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

/**
 * VMPFence Advanced File Handler
 * 
 * Handles advanced file size and memory management including:
 * - Large file handling (>2GB files)
 * - Stream processing for memory efficiency
 * - Memory usage monitoring and optimization
 * - File size detection and handling strategies
 */
class VMPFence_Advanced_File_Handler {
    
    private $memory_limit;
    private $max_file_size;
    private $stream_chunk_size;
    private $file_handle_cache;
    private $memory_monitor;
    
    public function __construct() {
        $this->memory_limit = $this->get_memory_limit();
        $this->max_file_size = min($this->memory_limit / 4, 50 * 1024 * 1024); // 50MB or 1/4 memory limit
        $this->stream_chunk_size = 8192; // 8KB chunks for stream processing
        $this->file_handle_cache = array();
        $this->memory_monitor = array();
    }
    
    /**
     * Handle large files with streaming approach
     */
    public function handle_large_files($file_path) {
        if (!file_exists($file_path) || !is_readable($file_path)) {
            return false;
        }
        
        $file_size = filesize($file_path);
        
        // Handle different file size categories
        if ($file_size === false) {
            return $this->handle_unknown_size_file($file_path);
        } elseif ($file_size > $this->max_file_size) {
            return $this->handle_oversized_file($file_path, $file_size);
        } elseif ($file_size > 1024 * 1024) { // > 1MB
            return $this->handle_medium_file($file_path, $file_size);
        } else {
            return $this->handle_small_file($file_path, $file_size);
        }
    }
    
    /**
     * Handle files with unknown size (pipes, special files)
     */
    private function handle_unknown_size_file($file_path) {
        // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fread, WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Streaming file handler for security scanning
        $handle = fopen($file_path, 'r');
        if (!$handle) {
            return false;
        }
        
        $content = '';
        $bytes_read = 0;
        $max_read = 1024 * 1024; // Limit to 1MB for unknown size files
        
        while (!feof($handle) && $bytes_read < $max_read) {
            $chunk = fread($handle, $this->stream_chunk_size);
            if ($chunk === false) {
                break;
            }
            
            $content .= $chunk;
            $bytes_read += strlen($chunk);
            
            // Monitor memory during read
            $this->monitor_memory_usage();
        }
        
        fclose($handle);
        // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fread, WordPress.WP.AlternativeFunctions.file_system_operations_fclose
        
        return array(
            'content' => $content,
            'size' => $bytes_read,
            'truncated' => $bytes_read >= $max_read,
            'method' => 'unknown_size_stream'
        );
    }
    
    /**
     * Handle oversized files (>50MB or memory limit)
     */
    private function handle_oversized_file($file_path, $file_size) {
        // For very large files, we'll use streaming with pattern matching
        return $this->stream_process_file($file_path, array(
            'max_scan_size' => min(10 * 1024 * 1024, $this->memory_limit / 8), // 10MB or 1/8 memory
            'sample_strategy' => 'head_tail', // Scan beginning and end
            'sample_size' => 1024 * 1024, // 1MB samples
            'method' => 'oversized_stream'
        ));
    }
    
    /**
     * Handle medium-sized files (1MB - 50MB)
     */
    private function handle_medium_file($file_path, $file_size) {
        // Try to load entire file if memory allows
        $available_memory = $this->get_available_memory();
        
        if ($file_size < ($available_memory * 0.3)) {
            // Safe to load entire file
            $content = file_get_contents($file_path);
            if ($content !== false) {
                return array(
                    'content' => $content,
                    'size' => $file_size,
                    'bytes_processed' => $file_size,
                    'truncated' => false,
                    'method' => 'full_load'
                );
            }
        }
        
        // Fall back to streaming
        return $this->stream_process_file($file_path, array(
            'max_scan_size' => $file_size,
            'sample_strategy' => 'full_stream',
            'method' => 'medium_stream'
        ));
    }
    
    /**
     * Handle small files (<1MB)
     */
    private function handle_small_file($file_path, $file_size) {
        $content = file_get_contents($file_path);
        
        if ($content === false) {
            return false;
        }
        
        return array(
            'content' => $content,
            'size' => $file_size,
            'bytes_processed' => $file_size,
            'truncated' => false,
            'method' => 'small_direct'
        );
    }
    
    /**
     * Stream process file with memory management
     */
    public function stream_process_file($file_path, $options = array()) {
        $default_options = array(
            'max_scan_size' => $this->max_file_size,
            'sample_strategy' => 'head_tail',
            'sample_size' => 1024 * 1024,
            'pattern_callback' => null,
            'method' => 'stream_process'
        );
        
        $options = array_merge($default_options, $options);
        
        // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fread, WordPress.WP.AlternativeFunctions.file_system_operations_fclose, WordPress.WP.AlternativeFunctions.rand_rand -- Advanced streaming file processing for security scanning
        $handle = fopen($file_path, 'r');
        if (!$handle) {
            return false;
        }
        
        $result = array(
            'patterns_found' => array(),
            'content_samples' => array(),
            'size' => filesize($file_path),
            'bytes_processed' => 0,
            'truncated' => false,
            'method' => $options['method']
        );
        
        switch ($options['sample_strategy']) {
            case 'head_tail':
                $result = $this->process_head_tail_strategy($handle, $options, $result);
                break;
            case 'full_stream':
                $result = $this->process_full_stream_strategy($handle, $options, $result);
                break;
            case 'random_samples':
                $result = $this->process_random_samples_strategy($handle, $options, $result);
                break;
            default:
                $result = $this->process_full_stream_strategy($handle, $options, $result);
        }
        
        fclose($handle);
        // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fread, WordPress.WP.AlternativeFunctions.file_system_operations_fclose, WordPress.WP.AlternativeFunctions.rand_rand
        return $result;
    }
    
    /**
     * Process file using head and tail strategy
     */
    private function process_head_tail_strategy($handle, $options, $result) {
        // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fread -- File streaming for security scanning
        $sample_size = $options['sample_size'];
        
        // Read head sample
        $head_content = fread($handle, $sample_size);
        if ($head_content !== false) {
            $result['content_samples']['head'] = $head_content;
            $result['bytes_processed'] += strlen($head_content);
            
            // Check for patterns in head
            if ($options['pattern_callback'] && is_callable($options['pattern_callback'])) {
                $head_patterns = call_user_func($options['pattern_callback'], $head_content);
                if ($head_patterns) {
                    $result['patterns_found'] = array_merge($result['patterns_found'], $head_patterns);
                }
            }
        }
        
        // Seek to tail if file is large enough
        $file_size = $result['size'];
        if ($file_size > $sample_size * 2) {
            fseek($handle, -$sample_size, SEEK_END);
            $tail_content = fread($handle, $sample_size);
            
            if ($tail_content !== false) {
                $result['content_samples']['tail'] = $tail_content;
                $result['bytes_processed'] += strlen($tail_content);
                
                // Check for patterns in tail
                if ($options['pattern_callback'] && is_callable($options['pattern_callback'])) {
                    $tail_patterns = call_user_func($options['pattern_callback'], $tail_content);
                    if ($tail_patterns) {
                        $result['patterns_found'] = array_merge($result['patterns_found'], $tail_patterns);
                    }
                }
            }
            
            $result['truncated'] = ($result['bytes_processed'] < $file_size);
        }
        // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fread
        
        return $result;
    }
    
    /**
     * Process file using full streaming strategy
     */
    private function process_full_stream_strategy($handle, $options, $result) {
        // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fread -- File streaming for security scanning
        $bytes_processed = 0;
        $max_scan_size = $options['max_scan_size'];
        $buffer = '';
        $buffer_size = $this->stream_chunk_size * 4; // 32KB buffer
        
        while (!feof($handle) && $bytes_processed < $max_scan_size) {
            $chunk = fread($handle, $this->stream_chunk_size);
            if ($chunk === false) {
                break;
            }
            
            $buffer .= $chunk;
            $bytes_processed += strlen($chunk);
            
            // Keep buffer size manageable
            if (strlen($buffer) > $buffer_size) {
                // Process current buffer
                if ($options['pattern_callback'] && is_callable($options['pattern_callback'])) {
                    $patterns = call_user_func($options['pattern_callback'], $buffer);
                    if ($patterns) {
                        $result['patterns_found'] = array_merge($result['patterns_found'], $patterns);
                    }
                }
                
                // Keep overlap for patterns spanning chunks
                $overlap_size = min(1024, strlen($buffer) / 4);
                $buffer = substr($buffer, -$overlap_size);
            }
            
            // Monitor memory every 10 chunks
            if ($bytes_processed % ($this->stream_chunk_size * 10) == 0) {
                $this->monitor_memory_usage();
                
                // Check if memory pressure requires stopping
                if ($this->is_memory_critical()) {
                    $result['truncated'] = true;
                    break;
                }
            }
        }
        
        // Process remaining buffer
        if (strlen($buffer) > 0 && $options['pattern_callback'] && is_callable($options['pattern_callback'])) {
            $patterns = call_user_func($options['pattern_callback'], $buffer);
            if ($patterns) {
                $result['patterns_found'] = array_merge($result['patterns_found'], $patterns);
            }
        }
        
        $result['bytes_processed'] = $bytes_processed;
        $result['truncated'] = $result['truncated'] || ($bytes_processed >= $max_scan_size && !feof($handle));
        // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fread
        
        return $result;
    }
    
    /**
     * Process file using random samples strategy
     */
    private function process_random_samples_strategy($handle, $options, $result) {
        // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fread -- File streaming for security scanning
        $file_size = $result['size'];
        $sample_size = $options['sample_size'];
        $num_samples = max(3, min(10, intval($file_size / $sample_size)));
        
        for ($i = 0; $i < $num_samples; $i++) {
            $random_offset = wp_rand(0, max(0, $file_size - $sample_size));
            fseek($handle, $random_offset);
            
            $sample = fread($handle, $sample_size);
            if ($sample !== false) {
                $result['content_samples']["sample_$i"] = $sample;
                $result['bytes_processed'] += strlen($sample);
                
                if ($options['pattern_callback'] && is_callable($options['pattern_callback'])) {
                    $patterns = call_user_func($options['pattern_callback'], $sample);
                    if ($patterns) {
                        $result['patterns_found'] = array_merge($result['patterns_found'], $patterns);
                    }
                }
            }
        }
        
        $result['truncated'] = ($result['bytes_processed'] < $file_size);
        // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fread
        return $result;
    }
    
    /**
     * Monitor memory usage during file processing
     */
    public function monitor_memory_usage() {
        $current_memory = memory_get_usage(true);
        $peak_memory = memory_get_peak_usage(true);
        $timestamp = microtime(true);
        
        $this->memory_monitor[] = array(
            'timestamp' => $timestamp,
            'current' => $current_memory,
            'peak' => $peak_memory,
            'percentage' => ($current_memory / $this->memory_limit) * 100
        );
        
        // Keep only last 100 measurements
        if (count($this->memory_monitor) > 100) {
            array_shift($this->memory_monitor);
        }
        
        // Trigger garbage collection if memory usage is high
        if ($current_memory > ($this->memory_limit * 0.75)) {
            if (function_exists('gc_collect_cycles')) {
                gc_collect_cycles();
            }
        }
        
        return array(
            'current' => $current_memory,
            'peak' => $peak_memory,
            'limit' => $this->memory_limit,
            'percentage' => ($current_memory / $this->memory_limit) * 100
        );
    }
    
    /**
     * Check if memory usage is critical
     */
    private function is_memory_critical() {
        $current_memory = memory_get_usage(true);
        return ($current_memory > ($this->memory_limit * 0.85));
    }
    
    /**
     * Get available memory
     */
    private function get_available_memory() {
        $current_memory = memory_get_usage(true);
        return max(0, $this->memory_limit - $current_memory);
    }
    
    /**
     * Get memory limit in bytes
     */
    private function get_memory_limit() {
        $memory_limit = ini_get('memory_limit');
        
        if ($memory_limit == -1) {
            return PHP_INT_MAX;
        }
        
        $memory_limit = trim($memory_limit);
        $last_char = strtolower($memory_limit[strlen($memory_limit) - 1]);
        $numeric_value = intval($memory_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;
    }
    
    /**
     * Get file size safely (handles >2GB files)
     */
    public function get_file_size_safe($file_path) {
        if (!file_exists($file_path)) {
            return false;
        }
        
        // Try standard filesize first
        $size = filesize($file_path);
        
        // For files >2GB on 32-bit systems, filesize() may return false or wrong value
        if ($size === false || $size < 0) {
            // Try alternative methods for large files
            if (PHP_OS === 'WINNT') {
                // Windows
                $size = exec('for %i in ("' . $file_path . '") do @echo %~zi');
                return is_numeric($size) ? intval($size) : false;
            } else {
                // Unix-like systems
                $size = exec('stat -f%z "' . escapeshellarg($file_path) . '" 2>/dev/null || stat -c%s "' . escapeshellarg($file_path) . '" 2>/dev/null');
                return is_numeric($size) ? intval($size) : false;
            }
        }
        
        return $size;
    }
    
    /**
     * Check if file can be processed safely
     */
    public function can_process_file($file_path) {
        if (!is_readable($file_path)) {
            return false;
        }
        
        $file_size = $this->get_file_size_safe($file_path);
        
        if ($file_size === false) {
            return false;
        }
        
        // Always allow small files
        if ($file_size < 1024 * 1024) { // < 1MB
            return true;
        }
        
        // Check available memory for larger files
        $available_memory = $this->get_available_memory();
        
        // For medium files, ensure we have 4x the file size available
        if ($file_size < 10 * 1024 * 1024 && $available_memory > ($file_size * 4)) {
            return true;
        }
        
        // Large files can be processed with streaming
        return true;
    }
    
    /**
     * Get memory statistics
     */
    public function get_memory_statistics() {
        if (empty($this->memory_monitor)) {
            return array();
        }
        
        $memory_values = array_column($this->memory_monitor, 'current');
        $peak_values = array_column($this->memory_monitor, 'peak');
        $percentages = array_column($this->memory_monitor, 'percentage');
        
        return array(
            'current_usage' => memory_get_usage(true),
            'peak_usage' => memory_get_peak_usage(true),
            'memory_limit' => $this->memory_limit,
            'average_usage' => array_sum($memory_values) / count($memory_values),
            'max_usage' => max($memory_values),
            'min_usage' => min($memory_values),
            'average_percentage' => array_sum($percentages) / count($percentages),
            'max_percentage' => max($percentages),
            'measurements' => count($this->memory_monitor)
        );
    }
    
    /**
     * Clean up file handle cache
     */
    public function cleanup_file_handles() {
        // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Cleanup of cached file handles for security scanning
        foreach ($this->file_handle_cache as $handle) {
            if (is_resource($handle)) {
                fclose($handle);
            }
        }
        // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fclose
        
        $this->file_handle_cache = array();
    }
    
    /**
     * Destructor to ensure file handles are closed
     */
    public function __destruct() {
        $this->cleanup_file_handles();
    }
}
