<?php
/**
 * OpenAI Provider.
 *
 * Implements the OpenAI Chat Completions API.
 *
 * @package Inqyra
 */

// If this file is called directly, abort.
if (!defined('WPINC')) {
    die;
}

/**
 * OpenAI Provider class.
 */
class Inqyra_OpenAI_Provider extends Inqyra_AI_Provider_Abstract {

    /**
     * API endpoint.
     */
    const API_URL = 'https://api.openai.com/v1/chat/completions';

    /**
     * Pricing per 1M tokens (input/output) by model.
     */
    const PRICING = [
        'gpt-4o' => ['input' => 2.50, 'output' => 10.00],
        'gpt-4o-mini' => ['input' => 0.15, 'output' => 0.60],
        'gpt-4-turbo' => ['input' => 10.00, 'output' => 30.00],
        'gpt-4' => ['input' => 30.00, 'output' => 60.00],
        'gpt-3.5-turbo' => ['input' => 0.50, 'output' => 1.50],
        'o1' => ['input' => 15.00, 'output' => 60.00],
        'o1-mini' => ['input' => 3.00, 'output' => 12.00],
    ];

    /**
     * Send a chat completion request.
     *
     * @param array $messages Array of messages with 'role' and 'content'.
     * @param array $options  Additional options.
     * @return array Response with 'content', 'tokens', and 'model'.
     * @throws Exception On API error.
     */
    public function chat(array $messages, array $options = []): array {
        if (!$this->is_configured()) {
            throw new Exception(esc_html(__('OpenAI API key not configured.', 'inqyra')));
        }

        $model = $options['model'] ?? $this->get_default_model();
        $max_tokens = $options['max_tokens'] ?? 4096;

        $request_data = [
            'model' => $model,
            'messages' => $messages,
        ];

        // o1 models don't support max_tokens, use max_completion_tokens instead
        if (str_starts_with($model, 'o1')) {
            $request_data['max_completion_tokens'] = $max_tokens;
        } else {
            $request_data['max_tokens'] = $max_tokens;
        }

        if (isset($options['temperature'])) {
            $request_data['temperature'] = (float) $options['temperature'];
        }

        $response = $this->make_request(
            self::API_URL,
            $request_data,
            [
                'Authorization' => 'Bearer ' . $this->api_key,
            ]
        );

        $content = $response['choices'][0]['message']['content'] ?? '';
        $tokens = [
            'input' => $response['usage']['prompt_tokens'] ?? 0,
            'output' => $response['usage']['completion_tokens'] ?? 0,
        ];

        $cost = $this->calculate_cost($tokens['input'], $tokens['output'], $model);

        $this->log_api_call('openai', $model, $tokens, $cost);

        return [
            'content' => $content,
            'tokens' => $tokens,
            'model' => $response['model'] ?? $model,
            'cost' => $cost,
        ];
    }

    /**
     * Get the provider identifier.
     *
     * @return string Provider ID.
     */
    public function get_id(): string {
        return 'openai';
    }

    /**
     * Get the provider name.
     *
     * @return string Provider name.
     */
    public function get_name(): string {
        return 'OpenAI';
    }

    /**
     * Get the default model.
     *
     * @return string Default model ID.
     */
    public function get_default_model(): string {
        return 'gpt-4o';
    }

    /**
     * Calculate cost for token usage.
     *
     * @param int    $input_tokens  Number of input tokens.
     * @param int    $output_tokens Number of output tokens.
     * @param string $model         Model identifier.
     * @return float Cost in USD.
     */
    public function calculate_cost(int $input_tokens, int $output_tokens, string $model): float {
        $pricing = $this->get_model_pricing($model);

        $input_cost = ($input_tokens / 1000000) * $pricing['input'];
        $output_cost = ($output_tokens / 1000000) * $pricing['output'];

        return $input_cost + $output_cost;
    }

    /**
     * Get hardcoded models - curated list of recommended chat models.
     *
     * @return array Model list with pricing.
     */
    public function get_hardcoded_models(): array {
        return [
            'gpt-4o' => 'GPT-4o ($2.50/$10.00 per 1M)',
            'gpt-4o-mini' => 'GPT-4o Mini ($0.15/$0.60 per 1M)',
            'gpt-4-turbo' => 'GPT-4 Turbo ($10.00/$30.00 per 1M)',
        ];
    }

    /**
     * Fetch available models from the OpenAI API.
     *
     * @return array Associative array of model_id => display_name.
     */
    public function fetch_available_models(): array {
        if (!$this->is_configured()) {
            return [];
        }

        $response = $this->make_get_request(
            'https://api.openai.com/v1/models',
            [
                'Authorization' => 'Bearer ' . $this->api_key,
            ]
        );

        $models = [];
        if (!empty($response['data']) && is_array($response['data'])) {
            foreach ($response['data'] as $model) {
                $id = $model['id'] ?? '';
                if ($id && $this->is_chat_model($id)) {
                    $models[$id] = $id;
                }
            }
        }

        return $models;
    }

    /**
     * Check if a model ID is a chat-compatible model.
     *
     * @param string $model_id Model identifier.
     * @return bool Whether this is a chat model.
     */
    private function is_chat_model(string $model_id): bool {
        $chat_prefixes = ['gpt-4', 'gpt-3.5', 'o1-', 'o3-', 'o4-', 'chatgpt-'];
        $exclude = ['instruct', 'realtime', 'audio', 'search', 'transcribe'];

        $matches_prefix = false;
        foreach ($chat_prefixes as $prefix) {
            if (str_starts_with($model_id, $prefix)) {
                $matches_prefix = true;
                break;
            }
        }
        if (!$matches_prefix) {
            return false;
        }

        foreach ($exclude as $term) {
            if (str_contains($model_id, $term)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get pricing for a specific model.
     *
     * @param string $model Model identifier.
     * @return array Pricing with 'input' and 'output' per 1M tokens.
     */
    protected function get_model_pricing(string $model): array {
        // Check exact match first
        if (isset(self::PRICING[$model])) {
            return self::PRICING[$model];
        }

        // Check for partial matches (e.g., gpt-4o-2024-08-06 matches gpt-4o)
        foreach (self::PRICING as $price_model => $pricing) {
            if (strpos($model, $price_model) === 0) {
                return $pricing;
            }
        }

        // Default to gpt-4o pricing if unknown
        return self::PRICING['gpt-4o'];
    }
}
