<?php

/**
 * VMPFence Pattern Performance Optimizer
 * 
 * Optimizes regex patterns for maximum performance and prevents backtrack issues
 * 
 * @package VMPFence
 * @subpackage Security
 * @since 1.0.0
 */

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}

class VMPFence_Pattern_Optimizer {
    
    private $compiled_patterns = [];
    private $pattern_performance = [];
    private $backtrack_limits = [];
    private $optimization_cache = [];
    private $benchmark_results = [];
    
    public function __construct() {
        $this->init_optimization_settings();
    }
    
    /**
     * Initialize pattern optimization settings
     * 
     */
    private function init_optimization_settings() {
        // Set default backtrack limits to prevent catastrophic backtracking
        $this->backtrack_limits = [
            'default' => 1000000,
            'complex' => 500000,
            'simple' => 2000000
        ];
        
        $this->optimization_cache = [];
        $this->pattern_performance = [];
        
        // $this->log_optimization('INFO', 'Pattern optimizer initialized');
    }
    
    /**
     * Validate pattern compilation
     * 
     */
    public function validate_pattern_compilation($patterns) {
        $validation_results = [
            'valid_patterns' => [],
            'invalid_patterns' => [],
            'compilation_errors' => [],
            'performance_warnings' => [],
            'total_patterns' => count($patterns)
        ];
        
        foreach ($patterns as $pattern_id => $pattern_data) {
            $pattern = $pattern_data['pattern'] ?? $pattern_data;
            
            try {
                // Test pattern compilation
                $compilation_result = $this->test_pattern_compilation($pattern, $pattern_id);
                
                if ($compilation_result['valid']) {
                    $validation_results['valid_patterns'][$pattern_id] = [
                        'pattern' => $pattern,
                        'compilation_time' => $compilation_result['compilation_time'],
                        'complexity_score' => $compilation_result['complexity_score']
                    ];
                    
                    // Store compiled pattern for reuse
                    $this->compiled_patterns[$pattern_id] = $compilation_result['compiled_pattern'];
                } else {
                    $validation_results['invalid_patterns'][$pattern_id] = [
                        'pattern' => $pattern,
                        'error' => $compilation_result['error']
                    ];
                    $validation_results['compilation_errors'][] = "Pattern {$pattern_id}: " . $compilation_result['error'];
                }
                
                // Check for performance warnings
                if ($compilation_result['complexity_score'] > 80) {
                    $validation_results['performance_warnings'][] = "Pattern {$pattern_id} has high complexity score: {$compilation_result['complexity_score']}";
                }
                
            } catch (Exception $e) {
                $validation_results['invalid_patterns'][$pattern_id] = [
                    'pattern' => $pattern,
                    'error' => $e->getMessage()
                ];
                $validation_results['compilation_errors'][] = "Pattern {$pattern_id}: " . $e->getMessage();
            }
        }
        
        // Calculate validation statistics
        $validation_results['validation_summary'] = [
            'success_rate' => round((count($validation_results['valid_patterns']) / $validation_results['total_patterns']) * 100, 2),
            'error_rate' => round((count($validation_results['invalid_patterns']) / $validation_results['total_patterns']) * 100, 2),
            'warning_count' => count($validation_results['performance_warnings'])
        ];
        
        // $this->log_optimization('INFO', 'Pattern compilation validation completed', [
        //     'total_patterns' => $validation_results['total_patterns'],
        //     'valid_patterns' => count($validation_results['valid_patterns']),
        //     'invalid_patterns' => count($validation_results['invalid_patterns']),
        //     'success_rate' => $validation_results['validation_summary']['success_rate'] . '%'
        // ]);
        
        return $validation_results;
    }
    
    /**
     * Test individual pattern compilation
     * Internal method for pattern validation
     */
    private function test_pattern_compilation($pattern, $pattern_id) {
        $start_time = microtime(true);
        
        try {
            // Ensure pattern has proper delimiters
            $formatted_pattern = $this->format_pattern_with_delimiters($pattern);
            
            // Test pattern compilation with error suppression
            $test_result = @preg_match($formatted_pattern, 'test_string');
            
            if ($test_result === false) {
                $error = error_get_last();
                return [
                    'valid' => false,
                    'error' => $error['message'] ?? 'Unknown compilation error',
                    'compilation_time' => 0,
                    'complexity_score' => 100
                ];
            }
            
            $compilation_time = microtime(true) - $start_time;
            $complexity_score = $this->calculate_pattern_complexity($pattern);
            
            return [
                'valid' => true,
                'compiled_pattern' => $formatted_pattern,
                'compilation_time' => $compilation_time,
                'complexity_score' => $complexity_score,
                'error' => null
            ];
            
        } catch (Exception $e) {
            return [
                'valid' => false,
                'error' => $e->getMessage(),
                'compilation_time' => 0,
                'complexity_score' => 100
            ];
        }
    }
    
    /**
     * Format pattern with proper regex delimiters
     * Ensures patterns can be used with preg_match()
     */
    private function format_pattern_with_delimiters($pattern) {
        // Check if pattern already has delimiters
        if (preg_match('/^[^a-zA-Z0-9\\\\\x00].*[^a-zA-Z0-9\\\\\x00][imsxuAJD]*$/', $pattern)) {
            return $pattern; // Already has delimiters
        }
        
        // Add delimiters - use ~ as delimiter since it's less likely to appear in patterns
        return '~' . $pattern . '~i';
    }
    
    /**
     * Calculate pattern complexity score
     * Helps identify patterns that may cause performance issues
     */
    private function calculate_pattern_complexity($pattern) {
        $complexity = 0;
        
        // Count quantifiers
        $complexity += substr_count($pattern, '*') * 10;
        $complexity += substr_count($pattern, '+') * 8;
        $complexity += substr_count($pattern, '?') * 5;
        
        // Count character classes
        $complexity += substr_count($pattern, '[') * 3;
        
        // Count groups
        $complexity += substr_count($pattern, '(') * 4;
        
        // Count alternations
        $complexity += substr_count($pattern, '|') * 6;
        
        // Count lookaheads/lookbehinds
        $complexity += substr_count($pattern, '(?=') * 15;
        $complexity += substr_count($pattern, '(?!') * 15;
        $complexity += substr_count($pattern, '(?<=') * 20;
        $complexity += substr_count($pattern, '(?<!') * 20;
        
        // Penalize very long patterns
        if (strlen($pattern) > 200) {
            $complexity += (strlen($pattern) - 200) / 10;
        }
        
        return min(100, $complexity);
    }
    
    /**
     * Optimize backtrack limits
     * Required by roadmap: optimize_backtrack_limits() method
     */
    public function optimize_backtrack_limits($patterns, $target_limit = 1000000) {
        $optimization_results = [
            'original_limit' => ini_get('pcre.backtrack_limit'),
            'target_limit' => $target_limit,
            'optimized_patterns' => [],
            'backtrack_test_results' => [],
            'recommended_limit' => $target_limit
        ];
        
        // Set the target backtrack limit
        ini_set('pcre.backtrack_limit', $target_limit);
        
        foreach ($patterns as $pattern_id => $pattern_data) {
            $pattern = $pattern_data['pattern'] ?? $pattern_data;
            
            // Test pattern with various backtrack scenarios
            $backtrack_test = $this->test_backtrack_behavior($pattern, $pattern_id, $target_limit);
            
            $optimization_results['backtrack_test_results'][$pattern_id] = $backtrack_test;
            
            if ($backtrack_test['needs_optimization']) {
                $optimized_pattern = $this->optimize_pattern_for_backtrack($pattern);
                $optimization_results['optimized_patterns'][$pattern_id] = [
                    'original' => $pattern,
                    'optimized' => $optimized_pattern,
                    'improvement' => $backtrack_test['backtrack_count'] - $this->test_backtrack_behavior($optimized_pattern, $pattern_id, $target_limit)['backtrack_count']
                ];
            }
        }
        
        // Calculate recommended backtrack limit based on test results
        $max_backtrack_needed = 0;
        foreach ($optimization_results['backtrack_test_results'] as $test_result) {
            $max_backtrack_needed = max($max_backtrack_needed, $test_result['backtrack_count']);
        }
        
        $optimization_results['recommended_limit'] = min($target_limit, max(100000, $max_backtrack_needed * 1.5));
        
        // $this->log_optimization('INFO', 'Backtrack limit optimization completed', [
        //     'target_limit' => $target_limit,
        //     'recommended_limit' => $optimization_results['recommended_limit'],
        //     'patterns_optimized' => count($optimization_results['optimized_patterns']),
        //     'max_backtrack_needed' => $max_backtrack_needed
        // ]);
        
        return $optimization_results;
    }
    
    /**
     * Test pattern backtrack behavior
     * Tests patterns against strings that could cause excessive backtracking
     */
    private function test_backtrack_behavior($pattern, $pattern_id, $limit) {
        $test_strings = [
            // Strings designed to cause backtracking
            str_repeat('a', 1000) . 'x',
            str_repeat('ab', 500) . 'x',
            str_repeat('<?php ', 200) . 'malware',
            str_repeat('<script>', 100) . 'evil_code',
            str_repeat('eval(', 200) . 'code);'
        ];
        
        $max_backtrack = 0;
        $total_time = 0;
        $backtrack_exceeded = false;
        
        foreach ($test_strings as $test_string) {
            $start_time = microtime(true);
            
            // Temporarily lower backtrack limit to test
            $original_limit = ini_get('pcre.backtrack_limit');
            ini_set('pcre.backtrack_limit', $limit);
            
            $result = @preg_match($pattern, $test_string);
            
            // Restore original limit
            ini_set('pcre.backtrack_limit', $original_limit);
            
            $execution_time = microtime(true) - $start_time;
            $total_time += $execution_time;
            
            // Check if backtrack limit was exceeded
            if ($result === false && preg_last_error() === PREG_BACKTRACK_LIMIT_ERROR) {
                $backtrack_exceeded = true;
                $max_backtrack = $limit; // Assume it hit the limit
            }
            
            // Estimate backtrack count based on execution time
            $estimated_backtrack = min($limit, $execution_time * 10000000); // Rough estimation
            $max_backtrack = max($max_backtrack, $estimated_backtrack);
        }
        
        return [
            'pattern_id' => $pattern_id,
            'backtrack_count' => $max_backtrack,
            'total_time' => $total_time,
            'backtrack_exceeded' => $backtrack_exceeded,
            'needs_optimization' => $backtrack_exceeded || $total_time > 0.1,
            'complexity_score' => $this->calculate_pattern_complexity($pattern)
        ];
    }
    
    /**
     * Optimize pattern to reduce backtracking
     * Applies various optimization techniques to reduce backtrack potential
     */
    private function optimize_pattern_for_backtrack($pattern) {
        $optimized = $pattern;
        
        // Replace greedy quantifiers with possessive where appropriate
        $optimized = preg_replace('/([^\\\\])\*/', '$1*+', $optimized);
        $optimized = preg_replace('/([^\\\\])\+/', '$1++', $optimized);
        
        // Add atomic groups around alternations that don't need backtracking
        $optimized = preg_replace('/\(([^|)]+\|[^)]+)\)/', '(?>$1)', $optimized);
        
        // Optimize common patterns that cause backtracking
        $optimizations = [
            '/\.\*\?\.\*/' => '[^]*+', // Non-greedy followed by greedy
            '/\.\+\?\.\+/' => '[^]++', // Non-greedy followed by greedy
            '/\(\.\*\)/' => '([^]*+)', // Capturing group with greedy match
        ];
        
        foreach ($optimizations as $search => $replace) {
            $optimized = preg_replace($search, $replace, $optimized);
        }
        
        return $optimized;
    }
    
    /**
     * Benchmark pattern performance
     * 
     */
    public function benchmark_pattern_performance($patterns, $iterations = 10000) {
        $benchmark_results = [
            'total_patterns' => count($patterns),
            'iterations_per_pattern' => $iterations,
            'pattern_benchmarks' => [],
            'performance_summary' => [],
            'slowest_patterns' => [],
            'fastest_patterns' => []
        ];
        
        // Generate test data for benchmarking
        $test_data = $this->generate_benchmark_test_data();
        
        foreach ($patterns as $pattern_id => $pattern_data) {
            $pattern = $pattern_data['pattern'] ?? $pattern_data;
            
            $pattern_benchmark = $this->benchmark_single_pattern($pattern, $pattern_id, $test_data, $iterations);
            $benchmark_results['pattern_benchmarks'][$pattern_id] = $pattern_benchmark;
        }
        
        // Analyze benchmark results
        $this->analyze_benchmark_results($benchmark_results);
        
        // Store results for future reference
        $this->benchmark_results = $benchmark_results;
        
        // $this->log_optimization('INFO', 'Pattern performance benchmarking completed', [
        //     'total_patterns' => $benchmark_results['total_patterns'],
        //     'iterations' => $iterations,
        //     'average_time_per_pattern' => $benchmark_results['performance_summary']['average_time_per_pattern'] ?? 0,
        //     'slowest_pattern' => $benchmark_results['slowest_patterns'][0]['pattern_id'] ?? 'none'
        // ]);
        
        return $benchmark_results;
    }
    
    /**
     * Generate test data for pattern benchmarking
     * Creates realistic test strings that patterns might encounter
     */
    private function generate_benchmark_test_data() {
        // Load test patterns from JSON file
        $loader_path = dirname(__FILE__) . '/class-signature-loader.php';
        require_once $loader_path;
        $loader = VMPFence_Signature_Loader::get_instance();
        $test_patterns = $loader->get('test_patterns');
        
        // If test patterns are loaded from JSON, use them
        if (!empty($test_patterns)) {
            $result = array();
            
            // Add clean code samples
            if (isset($test_patterns['clean_code'])) {
                $result = array_merge($result, $test_patterns['clean_code']);
            }
            
            // Add suspicious code samples
            if (isset($test_patterns['suspicious_code'])) {
                $result = array_merge($result, $test_patterns['suspicious_code']);
            }
            
            // Add malware samples
            if (isset($test_patterns['malware_samples'])) {
                $result = array_merge($result, $test_patterns['malware_samples']);
            }
            
            // Add special cases
            if (isset($test_patterns['special_cases'])) {
                $result = array_merge($result, $test_patterns['special_cases']);
            }
            
            return $result;
        }
        
        // Fallback to hardcoded test data if JSON not available
        return array(
            '<?php echo "Hello World"; ?>',
            '<?php $wpdb->get_results("SELECT * FROM posts"); ?>',
            '<?php wp_enqueue_script("jquery"); ?>',
            str_repeat('Lorem ipsum dolor sit amet, ', 100)
        );
    }
    
    /**
     * Benchmark single pattern performance
     * Tests a single pattern against various test data
     */
    private function benchmark_single_pattern($pattern, $pattern_id, $test_data, $iterations) {
        $total_time = 0;
        $match_count = 0;
        $error_count = 0;
        $memory_usage = [];
        
        // Format pattern with proper delimiters
        $formatted_pattern = $this->format_pattern_with_delimiters($pattern);
        
        for ($i = 0; $i < $iterations; $i++) {
            $test_string = $test_data[$i % count($test_data)];
            
            $start_memory = memory_get_usage(true);
            $start_time = microtime(true);
            
            try {
                $result = @preg_match($formatted_pattern, $test_string);
                if ($result === 1) {
                    $match_count++;
                } elseif ($result === false) {
                    $error_count++;
                }
            } catch (Exception $e) {
                $error_count++;
            }
            
            $end_time = microtime(true);
            $end_memory = memory_get_usage(true);
            
            $execution_time = $end_time - $start_time;
            $total_time += $execution_time;
            $memory_usage[] = $end_memory - $start_memory;
        }
        
        return [
            'pattern_id' => $pattern_id,
            'total_time' => $total_time,
            'average_time' => $total_time / $iterations,
            'matches_found' => $match_count,
            'errors' => $error_count,
            'match_rate' => ($match_count / $iterations) * 100,
            'error_rate' => ($error_count / $iterations) * 100,
            'memory_usage' => [
                'average' => array_sum($memory_usage) / count($memory_usage),
                'peak' => max($memory_usage),
                'total' => array_sum($memory_usage)
            ],
            'performance_score' => $this->calculate_performance_score($total_time, $match_count, $error_count, $iterations)
        ];
    }
    
    /**
     * Calculate performance score for a pattern
     * Higher score = better performance
     */
    private function calculate_performance_score($total_time, $matches, $errors, $iterations) {
        $base_score = 100;
        
        // Penalize slow execution time (over 1ms average is bad)
        $avg_time = $total_time / $iterations;
        if ($avg_time > 0.001) {
            $base_score -= min(50, ($avg_time - 0.001) * 50000);
        }
        
        // Penalize errors heavily
        $error_rate = ($errors / $iterations) * 100;
        $base_score -= $error_rate * 2;
        
        // Small bonus for finding matches (shows the pattern is working)
        $match_rate = ($matches / $iterations) * 100;
        if ($match_rate > 0 && $match_rate < 50) { // Not too many matches (would indicate false positives)
            $base_score += min(10, $match_rate / 5);
        }
        
        return max(0, min(100, $base_score));
    }
    
    /**
     * Analyze benchmark results and generate summary
     * Creates performance summary and identifies problematic patterns
     */
    private function analyze_benchmark_results(&$benchmark_results) {
        $times = [];
        $scores = [];
        $pattern_data = [];
        
        foreach ($benchmark_results['pattern_benchmarks'] as $pattern_id => $benchmark) {
            $times[] = $benchmark['average_time'];
            $scores[] = $benchmark['performance_score'];
            $pattern_data[] = [
                'pattern_id' => $pattern_id,
                'average_time' => $benchmark['average_time'],
                'performance_score' => $benchmark['performance_score'],
                'error_rate' => $benchmark['error_rate']
            ];
        }
        
        // Sort patterns by performance
        usort($pattern_data, function($a, $b) {
            return $b['performance_score'] <=> $a['performance_score'];
        });
        
        $benchmark_results['performance_summary'] = [
            'average_time_per_pattern' => array_sum($times) / count($times),
            'median_time' => $this->calculate_median($times),
            'average_performance_score' => array_sum($scores) / count($scores),
            'total_benchmark_time' => array_sum($times) * $benchmark_results['iterations_per_pattern']
        ];
        
        // Identify fastest and slowest patterns
        $benchmark_results['fastest_patterns'] = array_slice($pattern_data, 0, 5);
        $benchmark_results['slowest_patterns'] = array_slice(array_reverse($pattern_data), 0, 5);
    }
    
    /**
     * Calculate median value from array
     */
    private function calculate_median($values) {
        sort($values);
        $count = count($values);
        $middle = floor($count / 2);
        
        if ($count % 2 == 0) {
            return ($values[$middle - 1] + $values[$middle]) / 2;
        } else {
            return $values[$middle];
        }
    }
    
    /**
     * Get compiled patterns
     * Returns pre-compiled patterns for use in scanning
     */
    public function get_compiled_patterns() {
        return $this->compiled_patterns;
    }
    
    /**
     * Get optimization recommendations
     * Provides recommendations based on benchmark and validation results
     */
    public function get_optimization_recommendations() {
        $recommendations = [];
        
        if (!empty($this->benchmark_results)) {
            // Recommend removing or optimizing slow patterns
            foreach ($this->benchmark_results['slowest_patterns'] as $slow_pattern) {
                if ($slow_pattern['performance_score'] < 30) {
                    $recommendations[] = [
                        'type' => 'remove_slow_pattern',
                        'pattern_id' => $slow_pattern['pattern_id'],
                        'reason' => 'Pattern performance score too low: ' . $slow_pattern['performance_score'],
                        'priority' => 'high'
                    ];
                }
            }
        }
        
        // Recommend backtrack limit adjustments
        foreach ($this->backtrack_limits as $type => $limit) {
            if ($limit < 500000) {
                $recommendations[] = [
                    'type' => 'increase_backtrack_limit',
                    'limit_type' => $type,
                    'current_limit' => $limit,
                    'recommended_limit' => 1000000,
                    'priority' => 'medium'
                ];
            }
        }
        
        return $recommendations;
    }
    
    /**
     * Export optimization results
     * Exports results for external analysis or reporting
     */
    public function export_optimization_results() {
        return [
            'compiled_patterns' => $this->compiled_patterns,
            'pattern_performance' => $this->pattern_performance,
            'backtrack_limits' => $this->backtrack_limits,
            'benchmark_results' => $this->benchmark_results,
            'optimization_recommendations' => $this->get_optimization_recommendations(),
            'export_timestamp' => time(),
            'optimizer_version' => '1.9.0'
        ];
    }
    
    /**
     * Log optimization events
     * Centralized logging for optimization activities
     */
    private function log_optimization($level, $message, $data = []) {
        $timestamp = gmdate('Y-m-d H:i:s');
        $log_entry = sprintf(
            "[%s] [%s] [OPTIMIZATION] %s: %s\n",
            $timestamp,
            $level,
            $message,
            !empty($data) ? json_encode($data, JSON_PRETTY_PRINT) : 'N/A'
        );
        
        // Write to optimization log file
        $log_file = plugin_dir_path(__FILE__) . 'vmpfence-optimization.log';
        file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
    }
}
