<?php
/**
 * AI Agent Executor
 * Handles execution phase: runs tasks, calls tools, manages retries
 * 
 * @package AI_Auto_Tool
 */

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

/**
 * Agent Executor Class
 * Executes plans, manages tool calling, and handles errors with retry logic
 */
class AIAutotool_Agent_Executor {
    
    /**
     * AI Provider Manager
     * @var object
     */
    private $manager;
    
    /**
     * Agent context
     * @var AIAutotool_Agent_Context
     */
    private $context;
    
    /**
     * Registered tools
     * @var array
     */
    private $tools = array();
    
    /**
     * Stream for SSE updates
     * @var object|null
     */
    private $stream;
    
    /**
     * Max retry attempts
     * @var int
     */
    private $max_retries = 3;
    
    /**
     * Model capabilities (use case mapping)
     * @var array
     */
    private $model_capabilities = array();
    
    /**
     * Constructor
     * 
     * @param object $manager Provider manager
     * @param AIAutotool_Agent_Context $context Agent context
     * @param object|null $stream SSE stream
     */
    public function __construct($manager, $context, $stream = null) {
        $this->manager = $manager;
        $this->context = $context;
        $this->stream = $stream;
        $this->load_tools();
    }
    
    /**
     * Load available tools
     * 
     * @return void
     */
    private function load_tools() {
        // Load tools file to register all tool classes
        $tools_file = plugin_dir_path(__FILE__) . 'aiautotool_ai_agent_tools.php';
        if (file_exists($tools_file)) {
            require_once $tools_file;
        }
        
        // Store reference to registered tools
        $this->tools = array();
    }
    
    /**
     * Set model capabilities
     * 
     * @param array $capabilities Model capabilities mapping
     * @return void
     */
    public function set_model_capabilities($capabilities) {
        $this->model_capabilities = $capabilities;
    }
    
    /**
     * Execute plan
     * 
     * @param array $plan Execution plan
     * @param string $original_request Original request
     * @return array Execution result
     */
    public function execute_plan($plan, $original_request) {
        $this->context->add_task('execute_plan', 'executing');
        
        if (empty($plan['steps'])) {
            return array(
                'success' => false,
                'error' => 'Empty plan'
            );
        }
        
        $results = array();
        $can_parallel = $plan['parallel_execution_possible'] ?? false;
        
        // For now, execute sequentially
        foreach ($plan['steps'] as $step) {
            $step_num = $step['step'] ?? 0;
            
            $this->send_progress('step_start', array(
                'step' => $step_num,
                'description' => $step['description'] ?? ''
            ));
            
            $result = $this->execute_step($step, $results, $original_request);
            
            // Ensure result has success field
            if (!isset($result['success'])) {
                $result['success'] = false;
                $result['error'] = isset($result['error']) ? $result['error'] : 'Step returned invalid format';
            }
            
            $results[$step_num] = $result;
            
            $this->send_progress('step_complete', array(
                'step' => $step_num,
                'success' => $result['success'],
                'error' => isset($result['error']) ? $result['error'] : null
            ));
            
            if (!$result['success']) {
                // Log detailed error
                error_log("Executor: Step {$step_num} failed - " . ($result['error'] ?? 'Unknown error'));
                
                // Check if it's a critical error
                if ($this->is_critical_error($result)) {
                    error_log("Executor: Critical error detected, stopping execution");
                    break;
                }
            }
        }
        
        $this->context->update_task_status('completed', array('results' => $results));
        
        // Combine results
        $final_result = $this->combine_results($results, $plan);
        
        return $final_result;
    }
    
    /**
     * Execute single step
     * 
     * @param array $step Step definition
     * @param array $previous_results Previous step results
     * @param string $original_request Original request
     * @return array Step result
     */
    private function execute_step($step, $previous_results, $original_request) {
        $action = $step['action'] ?? 'generate';
        
        switch ($action) {
            case 'tool_call':
                return $this->execute_tool_call($step, $previous_results);
                
            case 'generate':
                return $this->execute_generation($step, $previous_results, $original_request);
                
            case 'analyze':
                return $this->execute_analysis($step, $previous_results);
                
            default:
                return array(
                    'success' => false,
                    'error' => 'Unknown action: ' . $action
                );
        }
    }
    
    /**
     * Execute tool call
     * 
     * @param array $step Step definition
     * @param array $previous_results Previous results
     * @return array Result
     */
    private function execute_tool_call($step, $previous_results) {
        $tool_name = $step['tool_name'] ?? null;
        $tool_params = $step['tool_params'] ?? array();
        
        if (!$tool_name) {
            return array(
                'success' => false,
                'error' => 'No tool name specified'
            );
        }
        
        $this->send_progress('tool_call', array('tool' => $tool_name));
        
        // Get tool instance
        $tool = $this->get_tool($tool_name);
        
        if (!$tool) {
            return array(
                'success' => false,
                'error' => 'Tool not found: ' . $tool_name
            );
        }
        
        // Execute tool with retry
        try {
            $result = $this->execute_with_retry(function() use ($tool, $tool_params) {
                return $tool->execute($tool_params, $this->stream);
            }, 'tool_call');
        } catch (Exception $e) {
            return array(
                'success' => false,
                'error' => 'Tool execution exception: ' . $e->getMessage(),
                'tool' => $tool_name
            );
        }
        
        // Check if result is valid
        if (!isset($result) || !is_array($result)) {
            return array(
                'success' => false,
                'error' => 'Tool returned invalid result format',
                'tool' => $tool_name
            );
        }
        
        // Log tool result
        $this->context->add_tool_result($tool_name, $result);
        
        // Return detailed error if failed
        if (isset($result['success']) && !$result['success']) {
            $error_msg = isset($result['error']) ? $result['error'] : 'Unknown error';
            return array(
                'success' => false,
                'error' => "Tool '{$tool_name}' failed: {$error_msg}",
                'tool' => $tool_name,
                'tool_error' => $error_msg,
                'tool_params' => $tool_params
            );
        }
        
        return $result;
    }
    
    /**
     * Execute content generation
     * 
     * @param array $step Step definition
     * @param array $previous_results Previous results
     * @param string $original_request Original request
     * @return array Result
     */
    private function execute_generation($step, $previous_results, $original_request) {
        $use_case = $step['use_case'] ?? 'general';
        
        // Build prompt from step description and previous results
        $prompt = $this->build_generation_prompt($step, $previous_results, $original_request);
        
        $this->send_progress('generate', array('use_case' => $use_case));
        
        // Get preferred models for this use case
        $models = $this->get_preferred_models($use_case);
        
        // Try models with fallback
        $attempted_models = array();
        $last_error = null;
        
        foreach ($models as $model_combo) {
            $model_key = $model_combo['provider'] . ':' . $model_combo['model'];
            $attempted_models[] = $model_key;
            
            $result = $this->execute_with_model($prompt, $model_combo, array(
                'use_case' => $use_case,
                'step_description' => $step['description']
            ));
            
            if (isset($result['success']) && $result['success'] && !empty($result['content'])) {
                return array(
                    'success' => true,
                    'content' => $result['content'],
                    'provider' => $model_combo['provider'],
                    'model' => $model_combo['model']
                );
            }
            
            // Keep track of last error
            if (isset($result['error'])) {
                $last_error = $result['error'];
            }
        }
        
        $error_msg = 'All models failed for generation';
        if ($last_error) {
            $error_msg .= ' - Last error: ' . $last_error;
        }
        $error_msg .= ' (Attempted: ' . implode(', ', $attempted_models) . ')';
        
        return array(
            'success' => false,
            'error' => $error_msg,
            'attempted_models' => $attempted_models
        );
    }
    
    /**
     * Execute analysis
     * 
     * @param array $step Step definition
     * @param array $previous_results Previous results
     * @return array Result
     */
    private function execute_analysis($step, $previous_results) {
        // Build analysis prompt
        $prompt = $this->build_analysis_prompt($step, $previous_results);
        
        // Use reasoning models for analysis
        $models = $this->get_preferred_models('reasoning');
        
        $attempted_models = array();
        $last_error = null;
        
        foreach ($models as $model_combo) {
            $model_key = $model_combo['provider'] . ':' . $model_combo['model'];
            $attempted_models[] = $model_key;
            
            $result = $this->execute_with_model($prompt, $model_combo, array(
                'temperature' => 0.3,
                'use_case' => 'analysis'
            ));
            
            if (isset($result['success']) && $result['success'] && !empty($result['content'])) {
                return array(
                    'success' => true,
                    'analysis' => $result['content'],
                    'provider' => $model_combo['provider'],
                    'model' => $model_combo['model']
                );
            }
            
            // Keep track of last error
            if (isset($result['error'])) {
                $last_error = $result['error'];
            }
        }
        
        $error_msg = 'Analysis failed';
        if ($last_error) {
            $error_msg .= ' - Last error: ' . $last_error;
        }
        $error_msg .= ' (Attempted: ' . implode(', ', $attempted_models) . ')';
        
        return array(
            'success' => false,
            'error' => $error_msg,
            'attempted_models' => $attempted_models
        );
    }
    
    /**
     * Build generation prompt
     * 
     * @param array $step Step definition  
     * @param array $previous_results Previous results
     * @param string $original_request Original request
     * @return string
     */
    private function build_generation_prompt($step, $previous_results, $original_request) {
        $prompt = "YÊU CẦU GỐC: {$original_request}\n\n";
        $prompt .= "TASK HIỆN TẠI: {$step['description']}\n\n";
        
        // Add relevant previous results
        if (!empty($previous_results)) {
            $prompt .= "KẾT QUẢ CÁC BƯỚC TRƯỚC:\n";
            foreach ($previous_results as $step_num => $result) {
                if ($result['success']) {
                    $content = $result['content'] ?? $result['analysis'] ?? 'completed';
                    $prompt .= "Bước {$step_num}: " . substr($content, 0, 200) . "...\n";
                }
            }
            $prompt .= "\n";
        }
        
        $prompt .= "Hãy thực hiện task hiện tại dựa trên thông tin trên.";
        
        return $prompt;
    }
    
    /**
     * Build analysis prompt
     * 
     * @param array $step Step definition
     * @param array $previous_results Previous results
     * @return string
     */
    private function build_analysis_prompt($step, $previous_results) {
        $prompt = "Phân tích kết quả sau:\n\n";
        
        foreach ($previous_results as $step_num => $result) {
            if ($result['success']) {
                $content = $result['content'] ?? $result['analysis'] ?? '';
                $prompt .= "Bước {$step_num}:\n{$content}\n\n";
            }
        }
        
        $prompt .= "{$step['description']}\n\n";
        $prompt .= "Trả về phân tích chi tiết.";
        
        return $prompt;
    }
    
    /**
     * Get preferred models for use case
     * 
     * @param string $use_case Use case
     * @return array Model combinations
     */
    private function get_preferred_models($use_case) {
        if (empty($this->model_capabilities)) {
            // Default fallback
            return array(
                array('provider' => 'openai', 'model' => 'gpt-4o'),
                array('provider' => 'gemini', 'model' => 'gemini-2.5-flash'),
            );
        }
        
        return $this->model_capabilities[$use_case] ?? $this->model_capabilities['general'] ?? array();
    }
    
    /**
     * Execute with specific model
     * 
     * @param string $prompt Prompt
     * @param array $model_combo Provider/model combination
     * @param array $options Options
     * @return array Result
     */
    private function execute_with_model($prompt, $model_combo, $options = array()) {
        $provider = $this->manager->get_provider($model_combo['provider']);
        
        if (!$provider) {
            return array(
                'success' => false,
                'error' => 'Provider not available: ' . $model_combo['provider']
            );
        }
        
        $messages = array(
            array('role' => 'user', 'content' => $prompt)
        );
        
        $params = array_merge(array(
            'model' => $model_combo['model'],
            'max_tokens' => 4000
        ), $options);
        
        $result = $provider->chat_completion($messages, $params);
        
        // Normalize result to ensure it has success and error fields
        if (!isset($result['success'])) {
            // Check if result indicates success (has content)
            $has_content = isset($result['content']) && !empty($result['content']);
            $has_error = isset($result['error']) && !empty($result['error']);
            
            if ($has_content && !$has_error) {
                $result['success'] = true;
            } elseif ($has_error) {
                $result['success'] = false;
            } else {
                // Unknown state
                $result['success'] = false;
                $result['error'] = isset($result['error']) ? $result['error'] : 'Provider returned invalid response format';
            }
        }
        
        // Ensure error field exists if not successful
        if (!isset($result['success']) || !$result['success']) {
            if (!isset($result['error']) || empty($result['error'])) {
                $result['error'] = 'Unknown error from provider';
            }
        }
        
        return $result;
    }
    
    /**
     * Execute with retry logic
     * 
     * @param callable $callback Function to execute
     * @param string $operation_type Operation type for logging
     * @return array Result
     */
    private function execute_with_retry($callback, $operation_type = 'operation') {
        $attempts = 0;
        $last_error = null;
        
        while ($attempts < $this->max_retries) {
            $attempts++;
            
            try {
                $result = $callback();
                
                if ($result['success']) {
                    return $result;
                }
                
                $last_error = $result['error'] ?? 'Unknown error';
                
                // Check if should retry
                if (!$this->should_retry($result)) {
                    break;
                }
                
                // Wait before retry
                if ($attempts < $this->max_retries) {
                    $wait_time = $this->get_retry_wait_time($attempts);
                    $this->send_progress('retry', array(
                        'attempt' => $attempts,
                        'wait' => $wait_time,
                        'reason' => $last_error
                    ));
                    sleep($wait_time);
                }
                
            } catch (Exception $e) {
                $last_error = $e->getMessage();
                $this->context->add_error('execution_exception', $last_error);
            }
        }
        
        return array(
            'success' => false,
            'error' => $last_error ?? 'Max retries exceeded',
            'attempts' => $attempts
        );
    }
    
    /**
     * Check if should retry
     * 
     * @param array $result Result
     * @return bool
     */
    private function should_retry($result) {
        $error = $result['error'] ?? '';
        
        // Retry on rate limits, network errors, temporary failures
        $retriable_patterns = array(
            'rate limit',
            'quota',
            'timeout',
            'network',
            'temporary',
            '429',
            '503',
            '504'
        );
        
        foreach ($retriable_patterns as $pattern) {
            if (stripos($error, $pattern) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Get retry wait time (exponential backoff)
     * 
     * @param int $attempt Attempt number
     * @return int Wait time in seconds
     */
    private function get_retry_wait_time($attempt) {
        return min(pow(2, $attempt), 30); // Max 30 seconds
    }
    
    /**
     * Check if error is critical
     * 
     * @param array $result Result
     * @return bool
     */
    private function is_critical_error($result) {
        $error = $result['error'] ?? '';
        
        // Critical errors that should stop execution
        $critical_patterns = array(
            'authentication',
            'invalid api key',
            'permission denied',
            'not found',
            'invalid tool'
        );
        
        foreach ($critical_patterns as $pattern) {
            if (stripos($error, $pattern) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Get tool instance
     * 
     * @param string $tool_name Tool name
     * @return object|null Tool instance
     */
    private function get_tool($tool_name) {
        // Map tool names to classes
        $tool_map = array(
            'write_post' => 'AIAutotool_Write_Post_Tool',
            'create_image' => 'AIAutotool_Create_Image_Tool',
            'wordpress_post' => 'AIAutotool_WordPress_Post_Tool',
            'get_post' => 'AIAutotool_Get_Post_Tool',
            'search_posts' => 'AIAutotool_Search_Posts_Tool',
            'list_terms' => 'AIAutotool_List_Terms_Tool',
            'get_comments' => 'AIAutotool_Get_Comments_Tool',
            'reply_comment' => 'AIAutotool_Reply_Comment_Tool',
        );
        
        $class_name = $tool_map[$tool_name] ?? null;
        
        if (!$class_name || !class_exists($class_name)) {
            return null;
        }
        
        return new $class_name();
    }
    
    /**
     * Combine results from all steps
     * 
     * @param array $results Step results
     * @param array $plan Original plan
     * @return array Combined result
     */
    private function combine_results($results, $plan) {
        $success = true;
        $combined_content = '';
        $errors = array();
        
        foreach ($results as $step_num => $result) {
            if (!isset($result) || !is_array($result)) {
                $success = false;
                $errors[] = "Step {$step_num}: Invalid result format";
                continue;
            }
            
            if (!isset($result['success']) || !$result['success']) {
                $success = false;
                $error_msg = isset($result['error']) ? $result['error'] : 'Unknown error';
                $tool_info = '';
                if (isset($result['tool'])) {
                    $tool_info = " (Tool: {$result['tool']})";
                    if (isset($result['tool_error'])) {
                        $error_msg .= " - Tool error: {$result['tool_error']}";
                    }
                }
                $errors[] = "Step {$step_num}: {$error_msg}{$tool_info}";
            } else {
                // Get content from various possible fields
                $content = '';
                if (isset($result['content'])) {
                    $content = $result['content'];
                } elseif (isset($result['data']['content'])) {
                    $content = $result['data']['content'];
                } elseif (isset($result['data']) && is_string($result['data'])) {
                    $content = $result['data'];
                } elseif (isset($result['analysis'])) {
                    $content = $result['analysis'];
                }
                
                if ($content) {
                    $combined_content .= $content . "\n\n";
                }
            }
        }
        
        if (!$success) {
            // Build detailed error message
            $error_message = 'Execution failed';
            if (!empty($errors)) {
                $error_message .= ': ' . implode('; ', $errors);
            } else {
                // If no errors array but success is false, check results for errors
                $result_errors = array();
                foreach ($results as $step_num => $result) {
                    if (isset($result['success']) && !$result['success']) {
                        $step_error = isset($result['error']) ? $result['error'] : 'Unknown step error';
                        $result_errors[] = "Step {$step_num}: {$step_error}";
                    }
                }
                if (!empty($result_errors)) {
                    $error_message .= ': ' . implode('; ', $result_errors);
                } else {
                    $error_message .= ': Unknown execution error - check individual_results for details';
                }
            }
            
            return array(
                'success' => false,
                'error' => $error_message,
                'content' => trim($combined_content), // May have partial content
                'steps_completed' => count($results),
                'errors' => $errors,
                'individual_results' => $results,
                'debug_info' => array(
                    'total_steps' => count($plan['steps'] ?? array()),
                    'results_count' => count($results),
                    'has_content' => !empty($combined_content)
                )
            );
        }
        
        return array(
            'success' => true,
            'content' => trim($combined_content),
            'steps_completed' => count($results),
            'errors' => $errors,
            'individual_results' => $results
        );
    }
    
    /**
     * Send progress update via SSE
     * 
     * @param string $event Event name
     * @param array $data Event data
     * @return void
     */
    private function send_progress($event, $data = array()) {
        if ($this->stream && method_exists($this->stream, 'send')) {
            $this->stream->send(array(
                'event' => $event,
                'data' => $data,
                'timestamp' => current_time('mysql')
            ));
        }
    }
}
