<?php

if (!defined('WPINC')) {
    die;
}

class Aysa_Queue_Manager {
    
    private $table_name;
    private $processors;
    
    public function __construct() {
        global $wpdb;
        $this->table_name = esc_sql($wpdb->prefix . 'aysa_automation_queue');
        $this->processors = array();
    }
    
    public function init() {
        $this->load_processors();
        $this->create_queue_table();
        $this->register_processors();
    }
    
    private function load_processors() {
        require_once AYSA_PLUGIN_DIR . 'includes/automation/processors/class-image-processor.php';
        require_once AYSA_PLUGIN_DIR . 'includes/automation/processors/class-redirect-processor.php';
        require_once AYSA_PLUGIN_DIR . 'includes/automation/processors/class-meta-processor.php';
        require_once AYSA_PLUGIN_DIR . 'includes/automation/processors/class-keyword-processor.php';
        require_once AYSA_PLUGIN_DIR . 'includes/automation/processors/class-domain-authority-processor.php';
        require_once AYSA_PLUGIN_DIR . 'includes/automation/processors/class-association-processor.php';
        require_once AYSA_PLUGIN_DIR . 'includes/automation/processors/class-seo-entity-processor.php';
        require_once AYSA_PLUGIN_DIR . 'includes/automation/processors/class-seo-generation-processor.php';
    }
    
    private function get_safe_charset_collate() {
        global $wpdb;
        $charset_collate = $wpdb->get_charset_collate();
        
        if (strpos($charset_collate, 'UTF-8') !== false) {
            $charset_collate = str_replace('UTF-8', 'utf8mb4', $charset_collate);
        }
        
        if (empty($charset_collate) || strpos($charset_collate, 'UTF-8') !== false) {
            $charset_collate = 'DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci';
        }
        
        return $charset_collate;
    }
    
    private function create_queue_table() {
        global $wpdb;
        
        $charset_collate = $this->get_safe_charset_collate();
        aysa_debug_log('[AYSA DEBUG] Queue Manager using charset: ' . $charset_collate);
        
        $sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            processor varchar(50) NOT NULL,
            job_data longtext NOT NULL,
            priority int(11) DEFAULT 5,
            status varchar(20) DEFAULT 'pending',
            attempts int(11) DEFAULT 0,
            max_attempts int(11) DEFAULT 3,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            started_at datetime NULL,
            completed_at datetime NULL,
            error_message text NULL,
            PRIMARY KEY (id),
            KEY status (status),
            KEY priority (priority),
            KEY processor (processor),
            KEY created_at (created_at)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
    }
    
    private function register_processors() {
        $this->processors = array(
            'image_processor' => new Aysa_Image_Processor(),
            'redirect_processor' => new Aysa_Redirect_Processor(),
            'meta_processor' => new Aysa_Meta_Processor(),
            'keyword_processor' => new Aysa_Keyword_Processor(),
            'domain_authority_processor' => new Aysa_Domain_Authority_Processor(),
            'association_processor' => new Aysa_Association_Processor(),
            'seo_entity_processor' => new Aysa_Seo_Entity_Processor(),
            'seo_generation_processor' => new Aysa_Seo_Generation_Processor()
        );
    }
    
    public function add_job($processor, $job_data, $priority = 5) {
        global $wpdb;
        
        if (!isset($this->processors[$processor])) {
            return new WP_Error('invalid_processor', 'Invalid processor specified');
        }
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $result = $wpdb->insert(
            $this->table_name,
            array(
                'processor' => $processor,
                'job_data' => wp_json_encode($job_data),
                'priority' => $priority,
                'status' => 'pending',
                'created_at' => current_time('mysql')
            ),
            array('%s', '%s', '%d', '%s', '%s')
        );
        
        if ($result === false) {
            return new WP_Error('db_error', 'Failed to add job to queue');
        }
        
        return $wpdb->insert_id;
    }
    
    public function process_queue($batch_size = 10) {
        global $wpdb;
        
        // Set execution time limit for long-running jobs
        $this->set_execution_limits();
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $jobs = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM {$this->table_name} 
             WHERE status = 'pending' 
             AND attempts < max_attempts 
             ORDER BY priority ASC, created_at ASC 
             LIMIT %d",
            $batch_size
        ));
        
        $processed = 0;
        $start_time = time();
        
        foreach ($jobs as $job) {
            // Check if we're approaching execution time limits
            if (time() - $start_time > 240) { // 4 minutes max per batch
                aysa_debug_log("AYSA Queue Manager: Approaching execution time limit, stopping batch processing");
                break;
            }
            
            if ($this->process_job($job)) {
                $processed++;
            }
            
            // Small delay between jobs to prevent server overload
            usleep(100000); // 0.1 second
        }
        
        aysa_debug_log("AYSA Queue Manager: Processed {$processed} jobs in " . (time() - $start_time) . " seconds");
        return $processed;
    }
    
    /**
     * Set execution limits for long-running processes
     */
    private function set_execution_limits() {
        // Disable output buffering for long-running processes
        if (function_exists('ob_end_flush')) {
            while (ob_get_level()) {
                ob_end_flush();
            }
        }
    }
    
    private function process_job($job) {
        global $wpdb;
        
        if (!isset($this->processors[$job->processor])) {
            $this->mark_job_failed($job->id, 'Invalid processor: ' . $job->processor);
            return false;
        }
        
        $job_start_time = time();
        aysa_debug_log("AYSA Queue Manager: Starting job {$job->id} ({$job->processor})");
        
        $this->mark_job_started($job->id);
        
        try {
            $job_data = json_decode($job->job_data, true);
            
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new Exception('Invalid job data JSON: ' . json_last_error_msg());
            }
            
            $processor = $this->processors[$job->processor];
            
            // Set up timeout handler for long-running jobs
            $this->setup_timeout_handler($job->id, $job_start_time);
            
            $result = $processor->process($job_data);
            
            // Clear timeout handler
            $this->clear_timeout_handler();
            
            if (is_wp_error($result)) {
                $error_message = $result->get_error_message();
                $error_code = $result->get_error_code();
                
                // Check if it's a timeout error
                if (strpos($error_message, 'timeout') !== false || $error_code === 'api_timeout') {
                    aysa_debug_log("AYSA Queue Manager: Job {$job->id} timed out: {$error_message}");
                    $this->mark_job_failed($job->id, "Job timed out: {$error_message}");
                } else {
                    aysa_debug_log("AYSA Queue Manager: Job {$job->id} failed: {$error_message}");
                    $this->mark_job_failed($job->id, $error_message);
                }
                return false;
            } else {
                $processing_time = time() - $job_start_time;
                aysa_debug_log("AYSA Queue Manager: Job {$job->id} completed successfully in {$processing_time} seconds");
                
                $this->mark_job_completed($job->id);
                
                // Store processing time for performance monitoring
                $this->store_job_performance($job->id, $processing_time, $job->processor);
                
                do_action('aysa_automation_job_completed', $job_data, $result);
                
                return true;
            }
            
        } catch (Exception $e) {
            $this->clear_timeout_handler();
            $error_message = $e->getMessage();
            aysa_debug_log("AYSA Queue Manager: Exception during job {$job->id}: {$error_message}");
            $this->mark_job_failed($job->id, 'Exception: ' . $error_message);
            return false;
        }
    }
    
    /**
     * Set up timeout handler for long-running jobs
     * 
     * @param int $job_id Job ID
     * @param int $start_time Job start time
     */
    private function setup_timeout_handler($job_id, $start_time) {
        // Set up a timeout handler that will be called if the job takes too long
        register_shutdown_function(function() use ($job_id, $start_time) {
            $execution_time = time() - $start_time;
            if ($execution_time > 300) { // 5 minutes
                aysa_debug_log("AYSA Queue Manager: Job {$job_id} exceeded maximum execution time ({$execution_time}s)");
                $this->mark_job_failed($job_id, "Job exceeded maximum execution time: {$execution_time} seconds");
            }
        });
    }
    
    /**
     * Clear timeout handler
     */
    private function clear_timeout_handler() {
        // Clear any registered shutdown functions
        // Note: This is a simplified approach - in production you might want more sophisticated timeout handling
    }
    
    /**
     * Store job performance metrics
     * 
     * @param int $job_id Job ID
     * @param int $processing_time Processing time in seconds
     * @param string $processor Processor name
     */
    private function store_job_performance($job_id, $processing_time, $processor) {
        global $wpdb;
        
        $performance_data = get_option('aysa_job_performance', array());
        
        if (!isset($performance_data[$processor])) {
            $performance_data[$processor] = array();
        }
        
        $performance_data[$processor][] = array(
            'job_id' => $job_id,
            'processing_time' => $processing_time,
            'timestamp' => current_time('mysql')
        );
        
        // Keep only last 100 performance records per processor
        if (count($performance_data[$processor]) > 100) {
            $performance_data[$processor] = array_slice($performance_data[$processor], -100);
        }
        
        update_option('aysa_job_performance', $performance_data);
    }
    
    private function mark_job_started($job_id) {
        global $wpdb;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->update(
            $this->table_name,
            array(
                'status' => 'processing',
                'started_at' => current_time('mysql'),
                'attempts' => new WP_Query('attempts + 1')
            ),
            array('id' => $job_id),
            array('%s', '%s', '%s'),
            array('%d')
        );
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->query($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "UPDATE {$this->table_name} SET attempts = attempts + 1 WHERE id = %d",
            $job_id
        ));
    }
    
    private function mark_job_completed($job_id) {
        global $wpdb;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->update(
            $this->table_name,
            array(
                'status' => 'completed',
                'completed_at' => current_time('mysql')
            ),
            array('id' => $job_id),
            array('%s', '%s'),
            array('%d')
        );
    }
    
    private function mark_job_failed($job_id, $error_message) {
        global $wpdb;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->update(
            $this->table_name,
            array(
                'status' => 'failed',
                'error_message' => $error_message,
                'completed_at' => current_time('mysql')
            ),
            array('id' => $job_id),
            array('%s', '%s', '%s'),
            array('%d')
        );
    }
    
    public function get_queue_statistics() {
        global $wpdb;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $stats = $wpdb->get_row(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare. Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT 
                COUNT(*) as total,
                SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending,
                SUM(CASE WHEN status = 'processing' THEN 1 ELSE 0 END) as processing,
                SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
                SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$this->table_name}
             WHERE created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)",
            ARRAY_A
        );
        
        return $stats ?: array(
            'total' => 0,
            'pending' => 0,
            'processing' => 0,
            'completed' => 0,
            'failed' => 0
        );
    }
    
    public function get_jobs($status = null, $limit = 50, $offset = 0) {
        global $wpdb;
        
        $where_clause = '';
        $where_values = array();
        
        if ($status) {
            $where_clause = 'WHERE status = %s';
            $where_values[] = $status;
        }
        
        $where_values[] = $limit;
        $where_values[] = $offset;
        
        $query = "SELECT * FROM {$this->table_name} {$where_clause} ORDER BY created_at DESC LIMIT %d OFFSET %d";
        
        if (!empty($where_values)) {
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution.
            $prepared_query = $wpdb->prepare($query, $where_values);
        } else {
            $prepared_query = $query;
        }
        
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        return $wpdb->get_results($prepared_query);
    }
    
    public function retry_failed_jobs($max_jobs = 10) {
        global $wpdb;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $failed_jobs = $wpdb->get_results($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT id FROM {$this->table_name} 
             WHERE status = 'failed' 
             AND attempts < max_attempts 
             ORDER BY created_at DESC 
             LIMIT %d",
            $max_jobs
        ));
        
        $retried = 0;
        
        foreach ($failed_jobs as $job) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->update(
                $this->table_name,
                array(
                    'status' => 'pending',
                    'error_message' => null,
                    'started_at' => null,
                    'completed_at' => null
                ),
                array('id' => $job->id),
                array('%s', '%s', '%s', '%s'),
                array('%d')
            );
            
            if ($result !== false) {
                $retried++;
            }
        }
        
        return $retried;
    }
    
    public function clear_completed_jobs($older_than_days = 7) {
        global $wpdb;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        return $wpdb->query($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "DELETE FROM {$this->table_name} 
             WHERE status = 'completed' 
             AND completed_at < DATE_SUB(NOW(), INTERVAL %d DAY)",
            $older_than_days
        ));
    }
    
    public function cleanup_test_data() {
        global $wpdb;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $deleted = $wpdb->query($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "DELETE FROM {$this->table_name} 
             WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY)
             AND (job_data LIKE %s OR job_data LIKE %s)",
            '%test%',
            '%debug%'
        ));
        
        aysa_debug_log("[AYSA QUEUE] Cleaned up {$deleted} old test jobs");
        return $deleted;
    }
    
    public function get_processor_performance() {
        global $wpdb;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        return $wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare. Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT 
                processor,
                COUNT(*) as total_jobs,
                SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as successful,
                SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed,
                AVG(TIMESTAMPDIFF(SECOND, started_at, completed_at)) as avg_processing_time
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM {$this->table_name}
             WHERE created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)
             GROUP BY processor",
            ARRAY_A
        );
    }
}
