<?php
/**
 * AI Provider Base Class
 * Định nghĩa interface chung cho tất cả AI providers
 */

if ( ! defined( 'ABSPATH' ) ) exit; // Thoát nếu truy cập trực tiếp

abstract class AIAutotool_AI_Provider_Base {
    
    /**
     * Provider name
     */
    protected $name = '';
    
    /**
     * Provider ID
     */
    protected $id = '';
    
    /**
     * API Key
     */
    protected $api_key = '';
    
    /**
     * Base URL
     */
    protected $base_url = '';
    
    /**
     * Default model
     */
    protected $default_model = '';
    
    /**
     * Available models
     */
    protected $available_models = array();

    /**
     * Provider capabilities.
     */
    protected $capabilities = array('text_generation');
    
    /**
     * Constructor
     */
    public function __construct($api_key = '') {
        if (!empty($api_key)) {
            $this->api_key = $api_key;
        }
    }
    
    /**
     * Get provider name
     */
    public function get_name() {
        return $this->name;
    }
    
    /**
     * Get provider ID
     */
    public function get_id() {
        return $this->id;
    }
    
    /**
     * Get base URL
     */
    public function get_base_url() {
        return $this->base_url;
    }
    
    /**
     * Set base URL (useful for Azure OpenAI or custom endpoints)
     */
    public function set_base_url($base_url) {
        $this->base_url = $base_url;
    }
    
    /**
     * Get default model
     */
    public function get_default_model() {
        $models = $this->get_available_models();
        if (!empty($models) && !isset($models[$this->default_model])) {
            return key($models);
        }

        return $this->default_model;
    }
    
    /**
     * Get available models
     */
    public function get_available_models() {
        $cache_key = $this->get_models_cache_key();
        if (function_exists('get_transient')) {
            $cached_models = get_transient($cache_key);
            if (is_array($cached_models) && !empty($cached_models)) {
                return $cached_models;
            }
        }

        $result = $this->get_models();
        if (!empty($result['success']) && !empty($result['models']) && is_array($result['models'])) {
            $models = $this->normalize_models_for_select($result['models']);
            if (!empty($models)) {
                if (function_exists('set_transient')) {
                    set_transient($cache_key, $models, 30 * MINUTE_IN_SECONDS);
                }

                return $models;
            }
        }

        return $this->available_models;
    }

    protected function get_models_cache_key() {
        return 'aiautotool_models_' . md5($this->id . '|' . rtrim($this->base_url, '/') . '|' . $this->api_key);
    }

    protected function normalize_models_for_select($models) {
        $normalized = array();

        foreach ($models as $key => $model) {
            $model_id = '';
            $model_label = '';

            if (is_string($model)) {
                $model_id = $model;
            } elseif (is_array($model)) {
                if (!empty($model['id'])) {
                    $model_id = (string) $model['id'];
                } elseif (!empty($model['model'])) {
                    $model_id = (string) $model['model'];
                } elseif (!empty($model['name'])) {
                    $model_id = (string) $model['name'];
                }

                if (!empty($model['name']) && $model['name'] !== $model_id) {
                    $model_label = (string) $model['name'];
                } elseif (!empty($model['display_name'])) {
                    $model_label = (string) $model['display_name'];
                }
            } elseif (is_object($model)) {
                if (!empty($model->id)) {
                    $model_id = (string) $model->id;
                } elseif (!empty($model->model)) {
                    $model_id = (string) $model->model;
                } elseif (!empty($model->name)) {
                    $model_id = (string) $model->name;
                }

                if (!empty($model->name) && $model->name !== $model_id) {
                    $model_label = (string) $model->name;
                } elseif (!empty($model->display_name)) {
                    $model_label = (string) $model->display_name;
                }
            } elseif (is_string($key)) {
                $model_id = $key;
                $model_label = is_scalar($model) ? (string) $model : '';
            }

            $model_id = trim($model_id);
            if ($model_id === '') {
                continue;
            }

            if ($model_label === '') {
                $model_label = $this->format_model_label($model_id);
            }

            $normalized[$model_id] = $model_label;
        }

        return $normalized;
    }

    protected function format_model_label($model_id) {
        return ucwords(str_replace(array('-', '_', '/', ':', '.'), ' ', $model_id));
    }

    /**
     * Get provider capabilities.
     */
    public function get_capabilities() {
        return $this->capabilities;
    }

    /**
     * Check whether provider supports a capability.
     */
    public function supports_capability($capability) {
        return in_array($capability, $this->capabilities, true);
    }

    /**
     * Get image generation models for providers that support images.
     */
    public function get_image_models() {
        return array();
    }
    
    /**
     * Set API key
     */
    public function set_api_key($api_key) {
        $this->api_key = $api_key;
    }
    
    /**
     * Get API key
     */
    public function get_api_key() {
        return $this->api_key;
    }
    
    /**
     * Check if provider is configured
     * Checks dynamically from API Keys Manager to detect newly added keys
     */
    public function is_configured() {
        // First check instance variable (for performance)
        if (!empty($this->api_key)) {
            return true;
        }
        
        // If instance variable is empty, check API Keys Manager dynamically
        // This allows detection of newly added keys without refreshing provider instances
        if (class_exists('AIAutotool_AI_Provider_API_Keys')) {
            $dynamic_key = '';
            
            switch ($this->id) {
                case 'openai':
                    $dynamic_key = AIAutotool_API_Manager::get_openai_key();
                    break;
                case 'gemini':
                    $dynamic_key = AIAutotool_API_Manager::get_gemini_key();
                    break;
                case 'pollinations':
                case 'llm7': // Backward compatibility
                    $dynamic_key = AIAutotool_API_Manager::get_pollinations_key();
                    break;
                case 'huggingface':
                    $dynamic_key = AIAutotool_API_Manager::get_huggingface_key();
                    break;
                case 'cloudflare':
                    $dynamic_key = AIAutotool_API_Manager::get_cloudflare_key();
                    break;
            }
            
            // Update instance variable if we found a key
            if (!empty($dynamic_key)) {
                $this->api_key = $dynamic_key;
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Chat completion - MUST be implemented by child classes
     * 
     * @param array $messages Array of messages with 'role' and 'content'
     * @param array $options Optional parameters (model, temperature, max_tokens, etc.)
     * @return array Response with 'success', 'content', 'usage', 'error'
     */
    abstract public function chat_completion($messages, $options = array());
    
    /**
     * Simple generate text - convenience wrapper
     * 
     * @param string $prompt Text prompt
     * @param array $options Optional parameters
     * @return array Response with 'success', 'content', 'error'
     */
    public function generate($prompt, $options = array()) {
        $messages = array(
            array(
                'role' => 'user',
                'content' => $prompt
            )
        );
        
        return $this->chat_completion($messages, $options);
    }

    /**
     * Generate image from prompt.
     */
    public function generate_image($prompt, $options = array()) {
        return array(
            'success' => false,
            'error' => $this->name . ' does not support image generation'
        );
    }
    
    /**
     * Test connection - MUST be implemented by child classes
     * 
     * @return array Response with 'success' and 'message'
     */
    abstract public function test_connection();
    
    /**
     * Get models list from API
     * 
     * @return array Response with 'success', 'models', 'error'
     */
    abstract public function get_models();
    
    /**
     * Parse options and merge with defaults
     */
    protected function parse_options($options = array()) {
        $defaults = array(
            'model' => $this->get_default_model(),
            'temperature' => 0.7,
            'max_tokens' => 1000,
            'top_p' => 1.0,
            'stream' => false
        );
        
        return wp_parse_args($options, $defaults);
    }
    
    /**
     * Make HTTP request helper using cURL
     */
    protected function make_request($url, $body = null, $method = 'GET', $headers = array(), $request_options = array()) {
        // Initialize cURL
        $ch = curl_init();
        
        if ($ch === false) {
            return array(
                'success' => false,
                'error' => 'Failed to initialize cURL'
            );
        }
        
        // Set URL
        curl_setopt($ch, CURLOPT_URL, $url);
        
        // Set method
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        
        $timeout = isset($request_options['timeout']) ? max(1, (int) $request_options['timeout']) : 45;
        $connect_timeout = isset($request_options['connect_timeout']) ? max(1, (int) $request_options['connect_timeout']) : 10;
        $low_speed_time = isset($request_options['low_speed_time']) ? max(1, (int) $request_options['low_speed_time']) : min($timeout, 30);

        // Set request timeouts. Long-form writing needs more than a short health-check timeout.
        curl_setopt($ch, CURLOPT_NOSIGNAL, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_TIMEOUT_MS, $timeout * 1000);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connect_timeout);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $connect_timeout * 1000);
        curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 1);
        curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, $low_speed_time);
        
        // Return response instead of outputting
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        
        // Follow redirects
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
        
        // Set headers
        $curl_headers = array();
        foreach ($headers as $key => $value) {
            $curl_headers[] = $key . ': ' . $value;
        }
        if (!empty($curl_headers)) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        }
        
        // Set body for POST requests
        if ($body !== null && $method === 'POST') {
            $body_data = is_array($body) ? json_encode($body) : $body;
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body_data);
        }
        
        // Get response headers
        curl_setopt($ch, CURLOPT_HEADER, false);
        
        // Execute request
        $response_body = curl_exec($ch);
        $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curl_error = curl_error($ch);
        $curl_errno = curl_errno($ch);
        
        // Close cURL handle
        curl_close($ch);
        
        // Handle cURL errors
        if ($curl_errno !== 0) {
            return array(
                'success' => false,
                'error' => 'cURL Error (' . $curl_errno . '): ' . $curl_error
            );
        }
        
        // Handle null status code (shouldn't happen but safety check)
        if ($status_code === null) {
            $status_code = 0;
        }
        
        // Decode JSON response
        $body = json_decode($response_body, true);
        
        // If JSON decode failed, use raw response
        if ($body === null && json_last_error() !== JSON_ERROR_NONE && !empty($response_body)) {
            $body = $response_body;
        }
        
        if ($status_code >= 200 && $status_code < 300) {
            return array(
                'success' => true,
                'data' => $body,
                'status_code' => $status_code
            );
        } else {
            $error_message = 'Unknown error';
            if (isset($body['error']['message'])) {
                $error_message = $body['error']['message'];
            } elseif (isset($body['error'])) {
                $error_message = is_string($body['error']) ? $body['error'] : json_encode($body['error']);
            } elseif (!empty($body)) {
                // Try to extract error from response body
                $error_message = 'HTTP ' . $status_code . ': ' . (is_string($body) ? substr($body, 0, 200) : json_encode($body));
            } else {
                $error_message = 'HTTP ' . $status_code . ': Empty response from server';
            }
            
            // Log error for debugging
            
            return array(
                'success' => false,
                'error' => $error_message,
                'status_code' => $status_code
            );
        }
    }
    
    /**
     * Log provider activity (optional)
     */
    protected function log($message, $data = null) {
        if (defined('WP_DEBUG') && WP_DEBUG) {
            $log_message = "[AI Provider - {$this->name}] {$message}";
            if ($data !== null) {
                $log_message .= ' | Data: ' . json_encode($data);
            }
        }
    }
    
    /**
     * Get current API key without rotation
     * Returns the current key based on rotation index, but does NOT advance the index
     * Rotation only happens when explicitly calling rotate_to_next_key()
     * @return string
     */
    protected function get_current_key() {
        $keys_manager = AIAutotool_AI_Provider_API_Keys::get_instance();
        $active_keys = $keys_manager->get_active_keys($this->id);
        
        // Also check old single key format
        $old_key = get_option('aiautotool_' . $this->id . '_api_key', '');
        $has_old_key = !empty($old_key);
        
        // Collect all available keys (new format + old format)
        $all_keys = array();
        foreach ($active_keys as $key_data) {
            if (isset($key_data['key'])) {
                $all_keys[] = $key_data['key'];
            }
        }
        if ($has_old_key) {
            // Check if old key is different from any active key
            $old_key_in_active = false;
            foreach ($active_keys as $key_data) {
                if (isset($key_data['key']) && $key_data['key'] === $old_key) {
                    $old_key_in_active = true;
                    break;
                }
            }
            if (!$old_key_in_active) {
                $all_keys[] = $old_key;
            }
        }
        
        // If no keys available, return empty
        if (empty($all_keys)) {
            return '';
        }
        
        // Get current rotation index
        $rotation_index_option = 'aiautotool_' . $this->id . '_rotation_index';
        $current_index = get_option($rotation_index_option, 0);
        
        // Ensure index is valid
        if ($current_index >= count($all_keys)) {
            $current_index = 0;
            update_option($rotation_index_option, $current_index, false);
        }
        
        // Return current key WITHOUT advancing rotation index
        return $all_keys[$current_index];
    }
    
    /**
     * Rotate to next API key (advance rotation index)
     * Call this only when current key fails with quota/rate limit error
     * @return string Next key after rotation
     */
    protected function rotate_to_next_key() {
        $keys_manager = AIAutotool_AI_Provider_API_Keys::get_instance();
        $active_keys = $keys_manager->get_active_keys($this->id);
        
        // Also check old single key format
        $old_key = get_option('aiautotool_' . $this->id . '_api_key', '');
        $has_old_key = !empty($old_key);
        
        // Collect all available keys (new format + old format)
        $all_keys = array();
        foreach ($active_keys as $key_data) {
            if (isset($key_data['key'])) {
                $all_keys[] = $key_data['key'];
            }
        }
        if ($has_old_key) {
            // Check if old key is different from any active key
            $old_key_in_active = false;
            foreach ($active_keys as $key_data) {
                if (isset($key_data['key']) && $key_data['key'] === $old_key) {
                    $old_key_in_active = true;
                    break;
                }
            }
            if (!$old_key_in_active) {
                $all_keys[] = $old_key;
            }
        }
        
        // If no keys available, return empty
        if (empty($all_keys)) {
            return '';
        }
        
        // Get current rotation index
        $rotation_index_option = 'aiautotool_' . $this->id . '_rotation_index';
        $current_index = get_option($rotation_index_option, 0);
        
        // Ensure index is valid
        if ($current_index >= count($all_keys)) {
            $current_index = 0;
        }
        
        // Advance to next key
        $next_index = ($current_index + 1) % count($all_keys);
        update_option($rotation_index_option, $next_index, false);
        
        // Return the new current key
        return $all_keys[$next_index];
    }
    
    /**
     * Backward compatibility: get_rotated_key() now calls get_current_key()
     * This ensures old code still works but no longer rotates automatically
     * @deprecated Use get_current_key() instead
     * @return string
     */
    protected function get_rotated_key() {
        return $this->get_current_key();
    }
    
    /**
     * Mask API key for logging (show first 4 and last 4 characters)
     * @param string $api_key
     * @return string
     */
    protected function mask_api_key($api_key) {
        if (empty($api_key) || strlen($api_key) <= 8) {
            return '***';
        }
        return substr($api_key, 0, 4) . '...' . substr($api_key, -4);
    }
}
