<?php

/**
 * VMPFence Progress Tracker
 * 
 * Provides real-time scan progress updates and handles scan interruptions
 * 
 * @package VMPFence
 * @subpackage Security
 * @since 1.0.0
 * @since 2.1.1 Major overhaul for improved progress tracking and interruption handling
 */

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

class VMPFence_Progress_Tracker {
    
    private $scan_id;
    private $total_files = 0;
    private $processed_files = 0;
    private $current_file = '';
    private $start_time;
    private $last_update_time;
    private $progress_data = [];
    private $interruption_data = [];
    private $progress_file;
    private $checkpoint_interval = 50; // Save progress every 50 files
    
    /**
     * Timestamp of last database write for throttling
     */
    private $last_db_write_time = 0;
    
    /**
     * Files processed at last database write for throttling
     */
    private $files_at_last_db_write = 0;
    
    public function __construct() {
        $this->scan_id = uniqid('vmpfence_progress_');
        $this->start_time = microtime(true);
        $this->last_update_time = $this->start_time;
        $this->last_db_write_time = $this->start_time;
        $this->files_at_last_db_write = 0;
        $this->progress_file = plugin_dir_path(__FILE__) . 'vmpfence-progress.json';
        $this->init_progress_tracking();
    }
    
    /**
     * Initialize progress tracking system
     * Required by roadmap for real-time progress monitoring
     */
    private function init_progress_tracking() {
        $this->progress_data = [
            'scan_id' => $this->scan_id,
            'started' => gmdate('Y-m-d H:i:s'),
            'status' => 'initializing',
            'total_files' => 0,
            'processed_files' => 0,
            'current_file' => '',
            'percentage' => 0,
            'estimated_completion' => null,
            'files_per_second' => 0,
            'elapsed_time' => 0,
            'remaining_time' => null,
            'last_checkpoint' => microtime(true),
            'interruption_point' => null,
            'can_resume' => false
        ];
        
        // Only save initial checkpoint if no interrupted state exists
        if (!$this->has_existing_interrupted_scan()) {
            $this->save_progress_checkpoint();
        }
        // $this->log_progress('INFO', 'Progress tracking initialized', ['scan_id' => $this->scan_id]);
    }
    
    /**
     * Update scan progress
     * Required by roadmap: update_progress() method
     * CRITICAL FIX: Throttles DB writes to max every 5 files or 3 seconds
     * @since 1.0.0
     * @since 2.1.1 Major overhaul for performance and reliability
     */
    public function update_progress($current_file = '', $files_processed = null) {
        $current_time = microtime(true);
        
        if ($files_processed !== null) {
            $this->processed_files = $files_processed;
        } else {
            $this->processed_files++;
        }
        
        $this->current_file = $current_file;
        $this->last_update_time = $current_time;
        
        // Calculate progress percentage
        $percentage = $this->total_files > 0 ? ($this->processed_files / $this->total_files) * 100 : 0;
        $percentage = min(100, max(0, $percentage));
        
        // Calculate processing speed
        $elapsed_time = $current_time - $this->start_time;
        $files_per_second = $elapsed_time > 0 ? ($this->processed_files / $elapsed_time) : 0;
        
        // Update progress data
        $this->progress_data = array_merge($this->progress_data, [
            'status' => 'scanning',
            'processed_files' => $this->processed_files,
            'current_file' => basename($current_file),
            'current_file_path' => $current_file,
            'percentage' => round($percentage, 2),
            'files_per_second' => round($files_per_second, 2),
            'elapsed_time' => round($elapsed_time, 2),
            'last_update' => gmdate('Y-m-d H:i:s')
        ]);
        
        // Calculate estimated completion
        if ($files_per_second > 0 && $this->total_files > 0) {
            $remaining_files = max(0, $this->total_files - $this->processed_files);
            $remaining_seconds = $remaining_files / $files_per_second;
            $this->progress_data['remaining_time'] = round($remaining_seconds, 2);
            $this->progress_data['estimated_completion'] = gmdate('Y-m-d H:i:s', (int)(time() + $remaining_seconds));
        }
        
        // CRITICAL FIX: Throttle database writes to reduce load
        // Write to DB only if:
        // 1. At least 5 files processed since last write, OR
        // 2. At least 3 seconds passed since last write, OR
        // 3. Checkpoint interval reached
        $files_since_write = $this->processed_files - $this->files_at_last_db_write;
        $time_since_write = $current_time - $this->last_db_write_time;
        $should_write_db = (
            $files_since_write >= 5 || 
            $time_since_write >= 3 ||
            ($this->processed_files % $this->checkpoint_interval === 0)
        );
        
        if ($should_write_db) {
            // Save checkpoint to database
            $this->save_progress_checkpoint();
            $this->last_db_write_time = $current_time;
            $this->files_at_last_db_write = $this->processed_files;
        }
        // Note: Progress data in memory is ALWAYS updated above,
        // so UI polling (which reads from memory) remains responsive
        
        // Log progress for major milestones
        // if ($percentage > 0 && fmod($percentage, 10) < 0.1) {
        //     $this->log_progress('INFO', 'Progress milestone reached', [
        //         'percentage' => $percentage,
        //         'files_processed' => $this->processed_files,
        //         'files_per_second' => $files_per_second,
        //         'estimated_completion' => $this->progress_data['estimated_completion']
        //     ]);
        // }
        
        return $this->progress_data;
    }
    
    /**
     * Set total files count for progress calculation
     * Required for accurate progress percentage
     */
    public function set_total_files($total) {
        $this->total_files = max(0, intval($total));
        $this->progress_data['total_files'] = $this->total_files;
        $this->save_progress_checkpoint();
        
        // $this->log_progress('INFO', 'Total files count set', [
        //     'total_files' => $this->total_files
        // ]);
    }
    
    /**
     * Estimate completion time
     * Required by roadmap: estimate_completion() method
     */
    public function estimate_completion() {
        if ($this->processed_files === 0 || $this->total_files === 0) {
            return [
                'estimated_seconds' => null,
                'estimated_time' => null,
                'confidence' => 'low',
                'message' => 'Insufficient data for estimation'
            ];
        }
        
        $elapsed_time = microtime(true) - $this->start_time;
        $avg_time_per_file = $elapsed_time / $this->processed_files;
        $remaining_files = max(0, $this->total_files - $this->processed_files);
        $estimated_remaining_seconds = $remaining_files * $avg_time_per_file;
        
        // Calculate confidence based on sample size and consistency
        $confidence = 'low';
        if ($this->processed_files >= 100) {
            $confidence = 'high';
        } elseif ($this->processed_files >= 50) {
            $confidence = 'medium';
        }
        
        // Adjust for varying file sizes and processing complexity
        $speed_variance = $this->calculate_speed_variance();
        if ($speed_variance > 0.3) {
            $confidence = $confidence === 'high' ? 'medium' : 'low';
            $estimated_remaining_seconds *= 1.2; // Add buffer for variance
        }
        
        $estimated_completion_time = time() + $estimated_remaining_seconds;
        
        return [
            'estimated_seconds' => round($estimated_remaining_seconds, 2),
            'estimated_time' => gmdate('Y-m-d H:i:s', (int)$estimated_completion_time),
            'estimated_readable' => $this->format_duration($estimated_remaining_seconds),
            'confidence' => $confidence,
            'progress_percentage' => round(($this->processed_files / $this->total_files) * 100, 2),
            'files_remaining' => $remaining_files,
            'current_speed' => round($this->processed_files / $elapsed_time, 2),
            'message' => sprintf('Estimated %s remaining (%s confidence)', 
                $this->format_duration($estimated_remaining_seconds), 
                $confidence
            )
        ];
    }
    
    /**
     * Handle scan interruptions
     * Required by roadmap: handle_interruptions() method
     */
    public function handle_interruptions() {
        // Check for interruption signals
        $interruption_detected = $this->detect_interruption();
        
        if ($interruption_detected) {
            // $this->log_progress('WARNING', 'Scan interruption detected', [
            //     'interruption_type' => $interruption_detected['type'],
            //     'files_processed' => $this->processed_files,
            //     'percentage_complete' => round(($this->processed_files / max(1, $this->total_files)) * 100, 2)
            // ]);
            
            // Save interruption data
            $this->interruption_data = [
                'interrupted_at' => gmdate('Y-m-d H:i:s'),
                'interruption_type' => $interruption_detected['type'],
                'files_processed_before_interruption' => $this->processed_files,
                'total_files' => $this->total_files,
                'current_file' => $this->current_file,
                'scan_id' => $this->scan_id,
                'can_resume' => true,
                'resume_data' => [
                    'start_from_file' => $this->processed_files,
                    'last_processed_file' => $this->current_file,
                    'progress_data' => $this->progress_data
                ]
            ];
            
            $this->progress_data['status'] = 'interrupted';
            $this->progress_data['interruption_point'] = $this->processed_files;
            $this->progress_data['can_resume'] = true;
            
            // Save final checkpoint before interruption
            $this->save_progress_checkpoint();
            $this->save_interruption_data();
            
            return $this->interruption_data;
        }
        
        return false;
    }
    
    /**
     * Resume interrupted scan
     * Allows continuing from last checkpoint
     */
    public function resume_scan($scan_id = null) {
        // Load checkpoint data
        $checkpoint_data = null;
        
        if ($scan_id && $scan_id !== $this->scan_id) {
            // Load different scan's checkpoint
            if (!file_exists($this->progress_file)) {
                // $this->log_progress('ERROR', 'No checkpoint file found for resume');
                return false;
            }
            
            $checkpoint_data = json_decode(file_get_contents($this->progress_file), true);
            
            if (!$checkpoint_data) {
                // $this->log_progress('ERROR', 'Invalid checkpoint data');
                return false;
            }
            
            // Load the checkpoint data
            $this->scan_id = $scan_id;
            $this->progress_data = $checkpoint_data['progress_data'];
            $this->processed_files = $checkpoint_data['processed_files'];
            $this->total_files = $checkpoint_data['total_files'];
            $this->current_file = $checkpoint_data['current_file'];
        }
        
        // Check if we can resume
        $can_resume = isset($this->progress_data['can_resume']) ? $this->progress_data['can_resume'] : true;
        
        if (!$can_resume) {
            // $this->log_progress('ERROR', 'Cannot resume scan - scan not in resumable state');
            return false;
        }
        
        $this->progress_data['status'] = 'resuming';
        $this->progress_data['resumed_at'] = gmdate('Y-m-d H:i:s');
        $this->progress_data['can_resume'] = true;
        
        // $this->log_progress('INFO', 'Scan resumed from interruption', [
        //     'resuming_from_file' => $this->processed_files,
        //     'total_files' => $this->total_files,
        //     'percentage_completed' => round(($this->processed_files / max(1, $this->total_files)) * 100, 2)
        // ]);
        
        $this->save_progress_checkpoint();
        
        return [
            'resumed' => true,
            'resume_point' => $this->processed_files,
            'remaining_files' => max(0, $this->total_files - $this->processed_files),
            'scan_id' => $this->scan_id
        ];
    }
    
    /**
     * Complete scan progress tracking
     * Finalize progress and generate completion report
     */
    public function complete_scan($final_stats = []) {
        $completion_time = microtime(true);
        $total_duration = $completion_time - $this->start_time;
        
        // Don't override can_resume if the scan was interrupted
        $preserve_resume_state = isset($this->progress_data['status']) && $this->progress_data['status'] === 'interrupted';
        
        $completion_data = [
            'status' => 'completed',
            'completed_at' => gmdate('Y-m-d H:i:s'),
            'total_duration' => round($total_duration, 2),
            'final_files_processed' => $this->processed_files,
            'percentage' => 100,
            'avg_files_per_second' => round($this->processed_files / $total_duration, 2),
            'interruption_point' => null
        ];
        
        // Only set can_resume to false if we weren't interrupted
        if (!$preserve_resume_state) {
            $completion_data['can_resume'] = false;
        }
        
        $this->progress_data = array_merge($this->progress_data, $completion_data);
        
        // Merge any additional final stats
        if (!empty($final_stats)) {
            $this->progress_data['final_stats'] = $final_stats;
        }
        
        $this->save_progress_checkpoint();
        
        // $this->log_progress('INFO', 'Scan completed successfully', [
        //     'total_duration' => $total_duration,
        //     'files_processed' => $this->processed_files,
        //     'avg_speed' => $this->progress_data['avg_files_per_second']
        // ]);
        
        return $this->progress_data;
    }
    
    /**
     * Get current progress status
     * Returns current progress data
     */
    public function get_progress() {
        return $this->progress_data;
    }
    
    /**
     * Detect interruption signals
     * Checks for common interruption scenarios
     */
    private function detect_interruption() {
        // Check for PHP timeout
        $max_execution_time = ini_get('max_execution_time');
        if ($max_execution_time > 0) {
            $elapsed = microtime(true) - $this->start_time;
            if ($elapsed > ($max_execution_time * 0.9)) { // 90% of max time
                return ['type' => 'timeout_approaching', 'details' => 'PHP execution time limit approaching'];
            }
        }
        
        // Check for memory limit
        $memory_limit = ini_get('memory_limit');
        if ($memory_limit !== '-1') {
            $memory_limit_bytes = $this->convert_to_bytes($memory_limit);
            $current_usage = memory_get_usage(true);
            if ($current_usage > ($memory_limit_bytes * 0.9)) { // 90% of memory limit
                return ['type' => 'memory_limit_approaching', 'details' => 'Memory limit approaching'];
            }
        }
        
        // Check for user interruption file
        $interrupt_file = WP_CONTENT_DIR . '/vmpfence-interrupt.flag';
        if (file_exists($interrupt_file)) {
            wp_delete_file($interrupt_file); // Remove the flag
            return ['type' => 'user_interruption', 'details' => 'User requested interruption'];
        }
        
        // Check if WordPress is in maintenance mode
        if (file_exists(ABSPATH . '.maintenance')) {
            return ['type' => 'maintenance_mode', 'details' => 'WordPress maintenance mode detected'];
        }
        
        return false;
    }
    
    /**
     * Calculate speed variance for confidence estimation
     */
    private function calculate_speed_variance() {
        // Simple variance calculation based on recent progress updates
        // This is a placeholder - could be enhanced with more sophisticated analysis
        return 0.1; // Default low variance
    }
    
    /**
     * Convert memory limit string to bytes
     */
    private function convert_to_bytes($val) {
        $val = trim($val);
        $last = strtolower($val[strlen($val)-1]);
        $val = (int) $val;
        
        switch($last) {
            case 'g':
                $val *= 1024;
            case 'm':
                $val *= 1024;
            case 'k':
                $val *= 1024;
        }
        
        return $val;
    }
    
    /**
     * Save progress checkpoint to file
     * Enables resuming interrupted scans
     */
    private function save_progress_checkpoint() {
        $checkpoint_data = [
            'scan_id' => $this->scan_id,
            'checkpoint_time' => microtime(true),
            'progress_data' => $this->progress_data,
            'processed_files' => $this->processed_files,
            'total_files' => $this->total_files,
            'current_file' => $this->current_file
        ];
        
        file_put_contents($this->progress_file, json_encode($checkpoint_data, JSON_PRETTY_PRINT), LOCK_EX);
    }
    
    /**
     * Load progress checkpoint from file
     */
    private function load_progress_checkpoint($scan_id = null) {
        if (!file_exists($this->progress_file)) {
            return false;
        }
        
        $checkpoint_data = json_decode(file_get_contents($this->progress_file), true);
        
        if (!$checkpoint_data) {
            return false;
        }
        
        if ($scan_id && $checkpoint_data['scan_id'] !== $scan_id) {
            return false;
        }
        
        return $checkpoint_data['progress_data'] ?? false;
    }
    
    /**
     * Save interruption data for later analysis
     */
    private function save_interruption_data() {
        $interruption_file = WP_CONTENT_DIR . '/vmpfence-interruption.json';
        file_put_contents($interruption_file, json_encode($this->interruption_data, JSON_PRETTY_PRINT), LOCK_EX);
    }
    
    /**
     * Format duration for human readability
     */
    private function format_duration($seconds) {
        if ($seconds < 60) {
            return round($seconds) . ' seconds';
        } elseif ($seconds < 3600) {
            $minutes = floor($seconds / 60);
            $secs = $seconds % 60;
            return sprintf('%d minutes, %d seconds', $minutes, round($secs));
        } else {
            $hours = floor($seconds / 3600);
            $minutes = floor(($seconds % 3600) / 60);
            return sprintf('%d hours, %d minutes', $hours, $minutes);
        }
    }
    
    /**
     * Log progress events
     */
    private function log_progress($level, $message, $data = []) {
        $timestamp = gmdate('Y-m-d H:i:s');
        $log_entry = sprintf(
            "[%s] [%s] [PROGRESS] %s: %s\n",
            $timestamp,
            $level,
            $message,
            !empty($data) ? json_encode($data, JSON_PRETTY_PRINT) : 'N/A'
        );
        
        $log_file = plugin_dir_path(__FILE__) . 'vmpfence-progress.log';
        file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
    }
    
    /**
     * Clean up progress tracking files
     */
    public function cleanup() {
        if (file_exists($this->progress_file)) {
            wp_delete_file($this->progress_file);
        }
        
        $interruption_file = WP_CONTENT_DIR . '/vmpfence-interruption.json';
        if (file_exists($interruption_file)) {
            wp_delete_file($interruption_file);
        }
        
        // $this->log_progress('INFO', 'Progress tracking cleanup completed', ['scan_id' => $this->scan_id]);
    }
    
    /**
     * Check if there's an existing interrupted scan
     * Prevents overwriting interrupted state during initialization
     * PUBLIC: Called by VMPFence_Malware_Scanner to check before clearing checkpoints
     * @since 1.0.0
     * @since 2.1.1 Made the method public
     */
    public function has_existing_interrupted_scan() {
        if (!file_exists($this->progress_file)) {
            return false;
        }
        
        $checkpoint_data = json_decode(file_get_contents($this->progress_file), true);
        if (!$checkpoint_data || !isset($checkpoint_data['progress_data'])) {
            return false;
        }
        
        $progress_data = $checkpoint_data['progress_data'];
        
        // Check if the existing checkpoint indicates an interrupted scan
        return isset($progress_data['can_resume']) && $progress_data['can_resume'] === true;
    }
}
