<?php
/**
 * Google Gemini Provider
 */

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

// Include base Gemini class
require_once dirname(__FILE__) . '/../aiautotool_ai_base_gemini.php';

class AIAutotool_AI_Provider_Gemini extends AIAutotool_AI_Provider_Base {
    
    protected $name = 'Google Gemini';
    protected $id = 'gemini';
    protected $base_url = 'https://generativelanguage.googleapis.com/v1beta';
    protected $default_model = 'gemini-2.5-pro';
    
    protected $available_models = array(
        // Các mô hình Gemini 2.5
        'gemini-2.5-pro' => 'Gemini 2.5 Pro (Mạnh mẽ nhất, Context dài)',
        'gemini-2.5-flash' => 'Gemini 2.5 Flash (Nhanh, Tối ưu hóa chi phí)'
    );
    public function __construct($api_key = '') {
        parent::__construct($api_key);
        
        if (empty($this->api_key)) {
            $this->api_key = $this->get_rotated_key();
        }
    }
    
    /**
     * Chat completion
     */
    public function chat_completion($messages, $options = array()) {
        // Track tried keys to avoid infinite loops
        $tried_keys = isset($options['_tried_keys']) ? $options['_tried_keys'] : array();
        if (!is_array($tried_keys)) {
            $tried_keys = array();
        }
        unset($options['_tried_keys']); // Remove internal flag
        unset($options['_retry_attempt']); // Remove old flag for backward compatibility
        
        // Request lock to prevent concurrent requests (only for first request, not retries)
        $is_retry = (count($tried_keys) > 0);
        $lock_key = 'gemini_api_lock_' . $this->id;
        $lock_acquired = false;
        
        if (!$is_retry) {
            $lock_timeout = 30; // 30 seconds max lock time
            $max_wait = 5; // Max 5 seconds to wait for lock
            $wait_interval = 0.2; // Check every 200ms
            $waited = 0;
            
            // Try to acquire lock
            while ($waited < $max_wait) {
                $lock_acquired = set_transient($lock_key, time(), $lock_timeout);
                if ($lock_acquired) {
                    error_log("[GEMINI] Acquired request lock (waited: {$waited}s)");
                    break;
                }
                usleep($wait_interval * 1000000); // Wait 200ms
                $waited += $wait_interval;
            }
            
            // If couldn't acquire lock, add delay anyway
            if (!$lock_acquired) {
                error_log("[GEMINI] Could not acquire lock, adding delay: {$waited}s");
                sleep(1); // Wait 1 second before proceeding
            }
        }
        
        // Add small delay between requests to avoid rate limiting (except first request)
        if (count($tried_keys) > 0) {
            $delay_ms = 500; // 500ms delay when retrying with next key
            usleep($delay_ms * 1000); // Convert to microseconds
            error_log("[GEMINI] Added delay: {$delay_ms}ms before request (key rotation retry)");
        }
        
        // Helper to release lock
        $release_lock = function() use ($is_retry, $lock_key, &$lock_acquired) {
            if (!$is_retry && $lock_acquired) {
                delete_transient($lock_key);
                error_log("[GEMINI] Released request lock");
                $lock_acquired = false;
            }
        };
        
        if (!$this->is_configured()) {
            return array(
                'success' => false,
                'error' => 'Gemini API key not configured'
            );
        }
        
        $options = $this->parse_options($options);
        
        // Get rotated key if needed
        $api_key_to_use = $this->api_key;
        $key_source = 'instance';
        if (empty($api_key_to_use)) {
            $api_key_to_use = $this->get_rotated_key();
            $key_source = 'rotated';
            if (!empty($api_key_to_use)) {
                $this->api_key = $api_key_to_use; // Update instance key
            }
        }
        
        if (empty($api_key_to_use)) {
            error_log("[GEMINI] No API key available - key not configured");
            $release_lock();
            return array(
                'success' => false,
                'error' => 'Gemini API key not configured'
            );
        }
        
        // Log which API key is being used without exposing secrets.
        $masked_key = $this->mask_api_key($api_key_to_use);
        $retry_count = count($tried_keys);
        error_log("[GEMINI] Using API key (MASKED): {$masked_key} (source: {$key_source}, retry: {$retry_count})");
        
        // Track this key as tried
        if (!in_array($api_key_to_use, $tried_keys)) {
            $tried_keys[] = $api_key_to_use;
        }
        
        // Use Gemini base class for query
        $this->log('Chat completion request', array('model' => $options['model']));
        
        // Convert messages to prompt and chatlog format for Gemini class
        $prompt = '';
        $chatlog = array();
        
        // Extract the last user message as prompt
        $last_user_message = '';
        foreach (array_reverse($messages) as $msg) {
            if (isset($msg['role']) && $msg['role'] === 'user' && isset($msg['content'])) {
                $last_user_message = $msg['content'];
                break;
            }
        }
        
        // If no user message found, use the last message
        if (empty($last_user_message) && !empty($messages)) {
            $last_msg = end($messages);
            $last_user_message = isset($last_msg['content']) ? $last_msg['content'] : '';
        }
        
        $prompt = $last_user_message;
        
        // Build chatlog from previous messages (excluding the last one)
        if (count($messages) > 1) {
            $previous_messages = array_slice($messages, 0, -1);
            foreach ($previous_messages as $msg) {
                if (isset($msg['content'])) {
                    $chatlog[] = array('message' => $msg['content']);
                }
            }
        }
        
        $op = !empty($chatlog) ? json_encode($chatlog) : '';
        
        // Initialize Gemini class with API key and model
        $gemini = new Gemini($api_key_to_use, $options['model']);
        
        // Call sendRequest
        $gemini_response = $gemini->sendRequest($prompt, $op, $options['model']);
        
        // Convert Gemini response format to provider response format
        if (isset($gemini_response['error'])) {
            // Error response
            $response = array(
                'success' => false,
                'error' => $gemini_response['error'],
                'status_code' => 400 // Default status code for errors
            );
        } else if (isset($gemini_response['raw_response'])) {
            // Success response - use raw response from Gemini API
            $response = array(
                'success' => true,
                'data' => $gemini_response['raw_response'],
                'status_code' => 200
            );
        } else if (isset($gemini_response['content'])) {
            // Fallback: use raw content if available
            $response = array(
                'success' => true,
                'data' => array(
                    'candidates' => array(
                        array(
                            'content' => array(
                                'parts' => array(
                                    array(
                                        'text' => $gemini_response['content']
                                    )
                                )
                            ),
                            'finishReason' => 'STOP'
                        )
                    )
                ),
                'status_code' => 200
            );
        } else if (isset($gemini_response['output'])) {
            // Last fallback: use HTML output
            $response = array(
                'success' => true,
                'data' => array(
                    'candidates' => array(
                        array(
                            'content' => array(
                                'parts' => array(
                                    array(
                                        'text' => $gemini_response['output']
                                    )
                                )
                            ),
                            'finishReason' => 'STOP'
                        )
                    )
                ),
                'status_code' => 200
            );
        } else {
            // Unexpected format
            $response = array(
                'success' => false,
                'error' => 'Unexpected response format from Gemini class',
                'status_code' => 500
            );
        }
        
        // Debug logging for Gemini responses
        $masked_key = $this->mask_api_key($api_key_to_use);
        if (defined('WP_DEBUG') && WP_DEBUG) {
            if (isset($response['data'])) {
                $debug_data = $response['data'];
                $has_candidates = isset($debug_data['candidates']) && !empty($debug_data['candidates']);
                $candidate_info = '';
                if ($has_candidates) {
                    $candidate = $debug_data['candidates'][0];
                    $finish_reason = isset($candidate['finishReason']) ? $candidate['finishReason'] : 'N/A';
                    $has_content = isset($candidate['content']['parts'][0]['text']);
                    $content_length = $has_content ? strlen($candidate['content']['parts'][0]['text']) : 0;
                    $candidate_info = "finishReason: {$finish_reason}, hasContent: " . ($has_content ? 'yes' : 'no') . ", contentLength: {$content_length}";
                    
                    if (isset($candidate['safetyRatings'])) {
                        $blocked_categories = array();
                        foreach ($candidate['safetyRatings'] as $rating) {
                            if (isset($rating['blocked']) && $rating['blocked'] === true) {
                                $blocked_categories[] = isset($rating['category']) ? $rating['category'] : 'Unknown';
                            }
                        }
                        if (!empty($blocked_categories)) {
                            $candidate_info .= ", blockedCategories: " . implode(', ', $blocked_categories);
                        }
                    }
                }
                error_log("=== GEMINI DEBUG (Key MASKED: {$masked_key}) ===");
                error_log("Response success: " . ($response['success'] ? 'yes' : 'no'));
                error_log("Has candidates: " . ($has_candidates ? 'yes' : 'no'));
                if ($has_candidates) {
                    error_log("Candidate info: {$candidate_info}");
                }
                if (isset($debug_data['error'])) {
                    error_log("Error in response: " . json_encode($debug_data['error']));
                }
                error_log("===================");
            }
        }
        
        // Log successful response
        if ($response['success']) {
            error_log("[GEMINI] Request successful with key (MASKED): {$masked_key}");
        }
        
        if (!$response['success']) {
            // Ensure error is always set
            if (!isset($response['error'])) {
                $response['error'] = 'API request failed';
            }
            
            // Log error with API key info (masked_key already defined above)
            $status_code = isset($response['status_code']) ? $response['status_code'] : 'N/A';
            error_log("[GEMINI] Request failed with key {$masked_key} - Error: {$response['error']} (HTTP: {$status_code})");
            
            // Preserve status_code for quota detection
            if (isset($response['status_code'])) {
                // Keep status_code for quota detection
            }
            
            // Check if this is a rate limit error (429) - immediately rotate to next key
            if ($this->is_rate_limit_error($response['error'], $response)) {
                error_log("[GEMINI] RATE LIMIT (429) detected with key (MASKED): {$masked_key} - Immediately rotating to next key");
                
                // Release lock before rotating to next key
                $release_lock();
                
                // Immediately rotate to next key instead of waiting and retrying
                return $this->retry_with_next_key($messages, $options, $api_key_to_use, $tried_keys);
            }
            
            // Check if this is a quota error (not rate limit) and we can retry with another key
            if ($this->is_quota_error($response['error'], $response)) {
                error_log("[GEMINI] QUOTA ERROR detected with key (MASKED): {$masked_key} - Attempting to rotate to next key");
                error_log("[GEMINI] Keys tried so far: " . count($tried_keys));
                // Release lock before rotating to next key
                $release_lock();
                return $this->retry_with_next_key($messages, $options, $api_key_to_use, $tried_keys);
            }
            
            $release_lock();
            return $response;
        }
        
        $data = $response['data'];
        
        // Check for error in Gemini response (even if HTTP status is 200)
        if (isset($data['error'])) {
            $error_msg = 'Gemini API Error';
            $error_details = array();
            
            // Extract error message
            if (isset($data['error']['message'])) {
                $error_msg = 'Gemini API Error: ' . $data['error']['message'];
            } elseif (is_string($data['error'])) {
                $error_msg = 'Gemini API Error: ' . $data['error'];
            }
            
            // Extract additional error details (for quota errors, details contain quota info)
            if (isset($data['error']['details']) && is_array($data['error']['details'])) {
                $details_parts = array();
                foreach ($data['error']['details'] as $detail) {
                    if (isset($detail['@type']) && isset($detail['reason'])) {
                        $details_parts[] = $detail['reason'];
                    } elseif (is_string($detail)) {
                        $details_parts[] = $detail;
                    } elseif (isset($detail['message'])) {
                        $details_parts[] = $detail['message'];
                    }
                }
                if (!empty($details_parts)) {
                    $error_msg .= ' * ' . implode(' * ', $details_parts);
                }
            }
            
            // Build error response with status code if available
            $error_response = array(
                'success' => false,
                'error' => $error_msg
            );
            
            // Preserve status_code from make_request if available
            if (isset($response['status_code'])) {
                $error_response['status_code'] = $response['status_code'];
            }
            
            // Also check if error has status field
            if (isset($data['error']['status'])) {
                $error_response['status_code'] = $data['error']['status'];
            }
            
            // Check if this is a rate limit error (429) - immediately rotate to next key
            if ($this->is_rate_limit_error($error_msg, $error_response)) {
                $masked_key = $this->mask_api_key($api_key_to_use);
                error_log("[GEMINI] RATE LIMIT (429) detected with key (MASKED): {$masked_key} - Immediately rotating to next key");
                
                // Release lock before rotating to next key
                $release_lock();
                
                // Immediately rotate to next key instead of waiting and retrying
                return $this->retry_with_next_key($messages, $options, $api_key_to_use, $tried_keys);
            }
            
            // Check if this is a quota error (not rate limit) and we can retry with another key
            if ($this->is_quota_error($error_msg, $error_response)) {
                $masked_key = $this->mask_api_key($api_key_to_use);
                error_log("[GEMINI] QUOTA ERROR detected with key (MASKED): {$masked_key} - Error: {$error_msg} - Attempting to rotate to next key");
                error_log("[GEMINI] Keys tried so far: " . count($tried_keys));
                // Release lock before rotating to next key
                $release_lock();
                return $this->retry_with_next_key($messages, $options, $api_key_to_use, $tried_keys);
            }
            
            $release_lock();
            return $error_response;
        }
        
        // Check if candidates exist
        if (!isset($data['candidates']) || empty($data['candidates'])) {
            $release_lock();
            return array(
                'success' => false,
                'error' => 'Invalid response from Gemini API: Missing or empty candidates array'
            );
        }
        
        $candidate = $data['candidates'][0];
        $finish_reason = isset($candidate['finishReason']) ? $candidate['finishReason'] : null;
        
        // Check if content exists and has text FIRST (before checking finish reason)
        // MAX_TOKENS is not an error - it just means response hit the token limit but may still have valid content
        if (isset($candidate['content']['parts'][0]['text'])) {
            $content_text = $candidate['content']['parts'][0]['text'];
            
            // Check if content is actually empty (after trimming)
            if (empty(trim($content_text))) {
                // Only treat as error if it's a safety/recitation block, not MAX_TOKENS
                if ($finish_reason === 'SAFETY' || $finish_reason === 'RECITATION') {
                    $error_msg = 'Content blocked by Gemini API';
                    $safety_details = array();
                    
                    // Extract safety ratings if available
                    if (isset($candidate['safetyRatings']) && is_array($candidate['safetyRatings'])) {
                        foreach ($candidate['safetyRatings'] as $rating) {
                            if (isset($rating['blocked']) && $rating['blocked'] === true) {
                                $category = isset($rating['category']) ? $rating['category'] : 'Unknown';
                                $probability = isset($rating['probability']) ? $rating['probability'] : 'Unknown';
                                $safety_details[] = $category . ' (' . $probability . ')';
                            }
                        }
                    }
                    
                    if (!empty($safety_details)) {
                        $error_msg .= ': ' . implode(', ', $safety_details);
                    } else {
                        $error_msg .= ' (finishReason: ' . $finish_reason . ')';
                    }
                    
                    $release_lock();
                    return array(
                        'success' => false,
                        'error' => $error_msg,
                        'finish_reason' => $finish_reason,
                        'safety_ratings' => isset($candidate['safetyRatings']) ? $candidate['safetyRatings'] : null
                    );
                }
                
                $release_lock();
                return array(
                    'success' => false,
                    'error' => 'Gemini API returned empty content (finishReason: ' . ($finish_reason ? $finish_reason : 'UNKNOWN') . ')',
                    'finish_reason' => $finish_reason
                );
            }
            
            // Content exists and is not empty - return success even if finishReason is MAX_TOKENS
            // MAX_TOKENS just means the response was truncated, but we still have valid content
            $release_lock();
            return array(
                'success' => true,
                'content' => $content_text,
                'usage' => isset($data['usageMetadata']) ? $data['usageMetadata'] : null,
                'model' => $options['model'],
                'finish_reason' => $finish_reason,
                'safety_ratings' => isset($candidate['safetyRatings']) ? $candidate['safetyRatings'] : null,
                'truncated' => ($finish_reason === 'MAX_TOKENS') // Flag to indicate if content was truncated
            );
        }
        
        // No content available - check for safety blocking or other blocking reasons
        if ($finish_reason === 'SAFETY' || $finish_reason === 'RECITATION') {
            $error_msg = 'Content blocked by Gemini API';
            $safety_details = array();
            
            // Extract safety ratings if available
            if (isset($candidate['safetyRatings']) && is_array($candidate['safetyRatings'])) {
                foreach ($candidate['safetyRatings'] as $rating) {
                    if (isset($rating['blocked']) && $rating['blocked'] === true) {
                        $category = isset($rating['category']) ? $rating['category'] : 'Unknown';
                        $probability = isset($rating['probability']) ? $rating['probability'] : 'Unknown';
                        $safety_details[] = $category . ' (' . $probability . ')';
                    }
                }
            }
            
            if (!empty($safety_details)) {
                $error_msg .= ': ' . implode(', ', $safety_details);
            } else {
                $error_msg .= ' (finishReason: ' . $finish_reason . ')';
            }
            
            $release_lock();
            return array(
                'success' => false,
                'error' => $error_msg,
                'finish_reason' => $finish_reason,
                'safety_ratings' => isset($candidate['safetyRatings']) ? $candidate['safetyRatings'] : null
            );
        }
        
        // MAX_TOKENS but no content - this is unusual but not necessarily an error
        if ($finish_reason === 'MAX_TOKENS') {
            $release_lock();
            return array(
                'success' => false,
                'error' => 'Gemini API reached max tokens limit but returned no content (finishReason: MAX_TOKENS)',
                'finish_reason' => $finish_reason
            );
        }
        
        // More detailed error message for missing content
        $error_details = 'Invalid response structure';
        if (!isset($candidate['content'])) {
            $error_details = 'Missing content in candidate';
            if ($finish_reason) {
                $error_details .= ' (finishReason: ' . $finish_reason . ')';
            }
        } elseif (!isset($candidate['content']['parts'])) {
            $error_details = 'Missing parts in content';
        } elseif (empty($candidate['content']['parts'])) {
            $error_details = 'Empty parts array';
        } elseif (!isset($candidate['content']['parts'][0])) {
            $error_details = 'Missing first part in parts array';
        } elseif (!isset($candidate['content']['parts'][0]['text'])) {
            $error_details = 'Missing text field in part';
        }
        
        $release_lock();
        return array(
            'success' => false,
            'error' => 'Invalid response from Gemini API: ' . $error_details,
            'finish_reason' => $finish_reason,
            'raw_candidate' => $candidate // Include for debugging
        );
    }
    
    /**
     * Check if error is rate limit (429) - too many requests too fast
     * 
     * @param string $error Error message
     * @param array $result Full result array
     * @return bool True if rate limit error
     */
    private function is_rate_limit_error($error, $result = array()) {
        if (empty($error)) {
            return false;
        }
        
        $error_lower = strtolower($error);
        
        // PRIORITY: If error contains "Please retry in Xs", it's definitely rate limit
        if (preg_match('/please retry in\s+[\d.]+\s*s/i', $error)) {
            return true;
        }
        
        // Check HTTP status code - 429 is rate limit
        if (isset($result['status_code'])) {
            $status = intval($result['status_code']);
            if ($status === 429) {
                // But check if it's actually quota error (no retry time specified)
                if (!preg_match('/retry in\s+[\d.]+\s*s/i', $error)) {
                    // 429 without retry time might be quota, but usually it's rate limit
                    // If it has "quota exceeded" without retry time, it might be quota
                    if (stripos($error_lower, 'quota exceeded') !== false && 
                        stripos($error_lower, 'free_tier') !== false &&
                        !preg_match('/retry in/i', $error)) {
                        // This might be actual quota, not rate limit
                        return false;
                    }
                }
                return true;
            }
        }
        
        // Check for rate limit specific keywords
        $rate_limit_keywords = array(
            'rate limit',
            'too many requests'
        );
        
        foreach ($rate_limit_keywords as $keyword) {
            if (stripos($error_lower, $keyword) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Extract retry-after time from error message
     * Gemini API sometimes includes "Please retry in X.XXs" in error message
     * 
     * @param string $error Error message
     * @return int Retry after time in seconds, 0 if not found
     */
    private function extract_retry_after($error) {
        if (empty($error)) {
            return 0;
        }
        
        // Look for "Please retry in X.XXs" pattern
        if (preg_match('/please retry in\s+([\d.]+)\s*s/i', $error, $matches)) {
            $seconds = floatval($matches[1]);
            // Add 1 second buffer
            return intval(ceil($seconds)) + 1;
        }
        
        // Look for "retry in X.XXs" pattern
        if (preg_match('/retry in\s+([\d.]+)\s*s/i', $error, $matches)) {
            $seconds = floatval($matches[1]);
            return intval(ceil($seconds)) + 1;
        }
        
        return 0;
    }
    
    /**
     * Check if error is quota/limit related (actual quota exhausted, not rate limit)
     * 
     * @param string $error Error message
     * @param array $result Full result array
     * @return bool True if quota/limit error (not rate limit)
     */
    private function is_quota_error($error, $result = array()) {
        if (empty($error)) {
            return false;
        }
        
        // Don't treat 429 as quota error - it's rate limit
        if (isset($result['status_code'])) {
            $status = intval($result['status_code']);
            if ($status === 429) {
                return false; // Rate limit, not quota
            }
        }
        
        $error_lower = strtolower($error);
        
        // Check for quota/limit keywords (excluding rate limit keywords)
        $quota_keywords = array(
            'quota',
            'exceeded',
            'generativelanguage',
            'free_tier',
            'billing details',
            'check your plan',
            'resource has been exhausted'
        );
        
        foreach ($quota_keywords as $keyword) {
            if (stripos($error_lower, $keyword) !== false) {
                // Make sure it's not a rate limit message
                if (stripos($error_lower, 'rate limit') === false && 
                    stripos($error_lower, 'too many requests') === false &&
                    stripos($error_lower, 'please retry in') === false) {
                    return true;
                }
            }
        }
        
        // Check HTTP status codes (402 = Payment Required, often quota related)
        if (isset($result['status_code'])) {
            $status = intval($result['status_code']);
            if ($status === 402) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Retry request with next available API key
     * This is called after lock has been released and we need to rotate to next key
     * 
     * @param array $messages Messages array
     * @param array $options Options array
     * @param string $failed_key The API key that failed (for logging)
     * @param array $tried_keys Array of keys that have already been tried
     * @return array Response result
     */
    private function retry_with_next_key($messages, $options, $failed_key = '', $tried_keys = array()) {
        error_log("[GEMINI] ========== RETRY WITH NEXT KEY (after lock released) ==========");
        $masked_initial_failed_key = !empty($failed_key) ? $this->mask_api_key($failed_key) : 'N/A';
        error_log("[GEMINI] Failed key (MASKED): {$masked_initial_failed_key}");
        error_log("[GEMINI] Keys tried so far: " . count($tried_keys));
        // Check if we have multiple keys available
        $keys_manager = AIAutotool_AI_Provider_API_Keys::get_instance();
        $active_keys = $keys_manager->get_active_keys($this->id);
        
        // Also check old single key format
        $option_name = 'aiautotool_' . $this->id . '_api_key';
        $old_key = get_option($option_name, '');
        $has_old_key = !empty($old_key) && $old_key !== 'unused';
        
        // 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;
            }
        }
        
        // Count total available keys
        $total_keys = count($all_keys);
        
        // Filter out keys that have already been tried
        $untried_keys = array();
        foreach ($all_keys as $key) {
            if (!in_array($key, $tried_keys)) {
                $untried_keys[] = $key;
            }
        }
        
        // Log all available keys without exposing secrets.
        if (!empty($all_keys)) {
            error_log("[GEMINI] All available API keys - Total: {$total_keys}, Tried: " . count($tried_keys) . ", Untried: " . count($untried_keys) . ":");
            foreach ($all_keys as $idx => $key) {
                $masked = $this->mask_api_key($key);
                $is_failed = ($key === $failed_key) ? ' [FAILED]' : '';
                $is_tried = in_array($key, $tried_keys) ? ' [TRIED]' : '';
                error_log("[GEMINI]   Key #{$idx} (MASKED): {$masked}{$is_failed}{$is_tried}");
            }
        }
        
        // Log failed key
        if (!empty($failed_key)) {
            $masked_failed_key = $this->mask_api_key($failed_key);
            error_log("[GEMINI] Key (MASKED): {$masked_failed_key} hit quota/limit - Total keys available: {$total_keys}, Untried: " . count($untried_keys));
        }
        
        // If no untried keys available, all keys have been exhausted
        // Return special error to signal agent to switch to next provider
        if (empty($untried_keys)) {
            $masked_failed_key = !empty($failed_key) ? $this->mask_api_key($failed_key) : 'N/A';
            error_log("[GEMINI] ========== ALL KEYS EXHAUSTED ==========");
            error_log("[GEMINI] All {$total_keys} API keys have been tried and failed with quota errors");
            error_log("[GEMINI] Failed key (MASKED): {$masked_failed_key}");
            error_log("[GEMINI] Switching to next provider...");
            error_log("[GEMINI] ========================================");
            
            // Return error that signals to switch provider
            // The error message pattern will be detected by AI agent to try next provider
            return array(
                'success' => false,
                'error' => 'Provider exhausted: All Gemini API keys have been tried in circular rotation and failed with quota/limit errors.',
                'all_keys_exhausted' => true, // Flag for agent to detect
                'provider' => 'gemini',
                'keys_tried' => $total_keys
            );
        }
        
        // If only one key total and it's already been tried, can't rotate
        if ($total_keys <= 1) {
            if (!empty($failed_key)) {
                $masked_failed_key = $this->mask_api_key($failed_key);
                error_log("[GEMINI] Cannot rotate - Only one key available (MASKED): {$masked_failed_key}");
            }
            return array(
                'success' => false,
                'error' => 'Only one API key available, cannot rotate'
            );
        }
        
        // Find the next untried key using circular rotation
        // The key array is treated as a circular buffer: after the last key, we wrap around to the first
        // This ensures we can try all keys in sequence regardless of the starting position
        
        // Get current rotation index
        $rotation_index_option = 'aiautotool_' . $this->id . '_rotation_index';
        $current_index = get_option($rotation_index_option, 0);
        
        // Try to find next untried key starting from current index
        // Using modulo operator (%) for circular rotation
        $next_key = null;
        $next_key_index = -1;
        $attempts = 0;
        $max_attempts = count($all_keys);
        
        // Loop through all keys in circular order to find an untried one
        while ($attempts < $max_attempts && $next_key === null) {
            // Circular index: (current + offset) % total wraps around the array
            $check_index = ($current_index + $attempts + 1) % count($all_keys);
            $check_key = $all_keys[$check_index];
            
            if (!in_array($check_key, $tried_keys)) {
                $next_key = $check_key;
                $next_key_index = $check_index;
                break;
            }
            $attempts++;
        }
        
        // If still no key found, use first untried key
        if ($next_key === null && !empty($untried_keys)) {
            $next_key = $untried_keys[0];
            $next_key_index = array_search($next_key, $all_keys);
        }
        
        if (empty($next_key)) {
            $masked_failed_key = !empty($failed_key) ? $this->mask_api_key($failed_key) : 'N/A';
            error_log("[GEMINI] Cannot find untried key. Failed key (MASKED): {$masked_failed_key}");
            return array(
                'success' => false,
                'error' => 'No untried API keys available'
            );
        }
        
        // Update rotation index to the next key
        update_option($rotation_index_option, $next_key_index, false);
        
        // Find failed key index
        $failed_key_index = -1;
        if (!empty($failed_key)) {
            $failed_key_index = array_search($failed_key, $all_keys);
        }
        
        // Log key rotation without exposing secrets.
        $masked_failed_key = !empty($failed_key) ? $this->mask_api_key($failed_key) : 'N/A';
        $masked_next_key = $this->mask_api_key($next_key);
        error_log("[GEMINI] ========== QUOTA ROTATION ==========");
        error_log("[GEMINI] Rotation index: {$current_index} -> {$next_key_index}");
        error_log("[GEMINI] Rotating from key (MASKED): {$masked_failed_key} [index: {$failed_key_index}]");
        error_log("[GEMINI] Rotating to key (MASKED): {$masked_next_key} [index: {$next_key_index}]");
        error_log("[GEMINI] Keys tried: " . count($tried_keys) . ", Remaining untried: " . count($untried_keys));
        error_log("[GEMINI] ====================================");
        
        // Update instance key
        $this->api_key = $next_key;
        
        // Retry with new key, passing tried_keys to track
        // Note: Lock has already been released, so this retry will not acquire a new lock
        // (it's considered a retry because tried_keys is not empty)
        error_log("[GEMINI] Proceeding to retry with next key (lock already released)");
        $options['_tried_keys'] = $tried_keys;
        return $this->chat_completion($messages, $options);
    }
    
    /**
     * Convert OpenAI-style messages to Gemini format
     */
    private function convert_messages_to_gemini($messages) {
        $gemini_contents = array();
        
        foreach ($messages as $message) {
            $role = $message['role'];
            
            // Validate and convert roles to Gemini format
            switch ($role) {
                case 'user':
                case 'system':
                    // Both user and system messages become 'user' in Gemini
                    $role = 'user';
                    break;
                case 'assistant':
                case 'model':
                    // Both assistant and model become 'model' in Gemini
                    $role = 'model';
                    break;
                default:
                    // Invalid role, skip this message
                    continue 2;
            }
            
            $gemini_contents[] = array(
                'role' => $role,
                'parts' => array(
                    array('text' => $message['content'])
                )
            );
        }
        
        return $gemini_contents;
    }
    
    /**
     * Test connection
     */
    public function test_connection() {
        if (!$this->is_configured()) {
            return array(
                'success' => false,
                'message' => 'Gemini API key not configured'
            );
        }
        
        // Get available models for display
        $available_models = $this->get_available_models();
        $test_model = $this->default_model;
        $model_name = isset($available_models[$test_model]) ? $available_models[$test_model] : $test_model;
        
        // Test with prompt asking model to introduce itself
        $test_prompt = sprintf(
            __('Hãy giới thiệu về bạn (model AI) cùng tên model (%s) và ngày cuối cùng cập nhật dữ liệu của bạn. Trả lời bằng tiếng Việt.', 'ai-auto-tool'),
            $model_name
        );
        
        $test_messages = array(
            array(
                'role' => 'user',
                'content' => $test_prompt
            )
        );
        
        $test_result = $this->chat_completion($test_messages, array('model' => $test_model));
        
        if ($test_result['success']) {
            // Get the actual model used in response
            $response_model = isset($test_result['model']) ? $test_result['model'] : $test_model;
            $response_model_name = isset($available_models[$response_model]) ? $available_models[$response_model] : $response_model;
            
            // Get response content
            $response_content = '';
            if (isset($test_result['content'])) {
                $response_content = is_string($test_result['content']) ? trim($test_result['content']) : '';
            } elseif (isset($test_result['response'])) {
                $response_content = is_string($test_result['response']) ? trim($test_result['response']) : '';
            }
            
            return array(
                'success' => true,
                'message' => 'Gemini API connection successful!',
                'response' => $response_content,
                'model' => $response_model,
                'model_name' => $response_model_name,
                'usage' => isset($test_result['usage']) ? $test_result['usage'] : null
            );
        } else {
            return array(
                'success' => false,
                'message' => 'Gemini API Error: ' . (isset($test_result['error']) ? $test_result['error'] : 'Unknown error'),
                'model' => $test_model,
                'model_name' => $model_name
            );
        }
    }
    
    /**
     * Get models list
     */
    public function get_models() {
        if (!$this->is_configured()) {
            return array(
                'success' => false,
                'error' => 'Gemini API key not configured'
            );
        }
        
        // Get rotated key if needed
        $api_key_to_use = $this->api_key;
        if (empty($api_key_to_use)) {
            $api_key_to_use = $this->get_rotated_key();
            if (!empty($api_key_to_use)) {
                $this->api_key = $api_key_to_use; // Update instance key
            }
        }
        
        if (empty($api_key_to_use)) {
            return array(
                'success' => false,
                'error' => 'Gemini API key not configured'
            );
        }
        
        $url = $this->base_url . '/models?key=' . $api_key_to_use;
        
        $response = $this->make_request($url, null, 'GET');
        
        if ($response['success']) {
            $models = array();
            if (isset($response['data']['models'])) {
                foreach ($response['data']['models'] as $model) {
                    // Filter only generateContent models
                    if (isset($model['supportedGenerationMethods']) && 
                        in_array('generateContent', $model['supportedGenerationMethods'])) {
                        $models[] = str_replace('models/', '', $model['name']);
                    }
                }
            }
            
            return array(
                'success' => true,
                'models' => $models
            );
        }
        
        return $response;
    }
}
