<?php

namespace App\LLM;

/**
 * OpenAI provider implementation.
 *
 * Config keys (from sp_orgs_meta metval JSON):
 *   secretKey  — required — OpenAI API key (sk-...)
 *   model      — optional — default: gpt-4o
 *   max_tokens — optional — default: 4096
 *   temperature— optional — default: 0.7
 *
 * Usage:
 *   $llm = LLMManager::forOrg($orgId, 'openai');
 *   $res = $llm->sendPrompt([['role' => 'user', 'content' => 'Hello']]);
 *   echo $res['content'];
 */
class svcOpenAI extends LLMProvider
{
    private const API_BASE    = 'https://api.openai.com/v1';
    private const API_CHAT    = self::API_BASE . '/chat/completions';
    private const API_MODELS  = self::API_BASE . '/models';

    private static array $availableModels = [
        ['id' => 'gpt-4o',            'label' => 'GPT-4o'],
        ['id' => 'gpt-4o-mini',       'label' => 'GPT-4o Mini'],
        ['id' => 'gpt-4-turbo',       'label' => 'GPT-4 Turbo'],
        ['id' => 'gpt-4',             'label' => 'GPT-4'],
        ['id' => 'gpt-3.5-turbo',     'label' => 'GPT-3.5 Turbo'],
        ['id' => 'o1',                'label' => 'o1'],
        ['id' => 'o1-mini',           'label' => 'o1 Mini'],
        ['id' => 'o3-mini',           'label' => 'o3 Mini'],
    ];

    public function __construct(array $config)
    {
        if (empty($config['secretKey'])) {
            throw new \Exception('OpenAI secretKey is required');
        }

        $this->apiKey      = $config['secretKey'];
        $this->model       = $config['model']       ?? 'gpt-4o';
        $this->maxTokens   = (int)($config['max_tokens']  ?? 4096);
        $this->temperature = (float)($config['temperature'] ?? 0.7);
    }

    /**
     * Send a chat prompt and return the full response.
     */
    public function sendPrompt(array $messages, array $options = []): array
    {
        try {
            $payload  = $this->buildPayload($messages, $options);
            $response = $this->curlPost(self::API_CHAT, $this->headers(), $payload);

            $content = $response['choices'][0]['message']['content'] ?? '';
            $usage   = $response['usage'] ?? [];

            return $this->successResponse($content, [
                'prompt_tokens'     => $usage['prompt_tokens']     ?? 0,
                'completion_tokens' => $usage['completion_tokens'] ?? 0,
                'total_tokens'      => $usage['total_tokens']      ?? 0,
            ]);

        } catch (\Exception $e) {
            return $this->errorResponse($e->getMessage());
        }
    }

    /**
     * Stream a chat prompt, calling $callback with each text chunk.
     *
     * @param callable $callback  function(string $chunk) — called with each partial text
     */
    public function streamPrompt(array $messages, array $options, callable $callback): array
    {
        try {
            $payload = $this->buildPayload($messages, $options);

            $this->curlStream(self::API_CHAT, $this->headers(), $payload, function(array $chunk) use ($callback) {
                $delta = $chunk['choices'][0]['delta']['content'] ?? '';
                if ($delta !== '') {
                    $callback($delta);
                }
            });

            return ['success' => true, 'error' => ''];

        } catch (\Exception $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }

    /**
     * Return the static list of supported OpenAI models.
     */
    public function getModels(): array
    {
        return self::$availableModels;
    }

    /**
     * Validate the API key by calling the models endpoint.
     */
    public function testConnection(): array
    {
        try {
            $start = microtime(true);

            $ch = curl_init(self::API_MODELS);
            curl_setopt_array($ch, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HTTPHEADER     => $this->headers(),
                CURLOPT_TIMEOUT        => $this->timeout,
            ]);

            $body  = curl_exec($ch);
            $error = curl_error($ch);
            $code  = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);

            $latency = (int)((microtime(true) - $start) * 1000);

            if ($error) throw new \Exception("cURL error: $error");

            $decoded = json_decode($body, true);

            if ($code !== 200) {
                $msg = $decoded['error']['message'] ?? "HTTP $code";
                throw new \Exception($msg);
            }

            return ['success' => true, 'latency_ms' => $latency, 'error' => ''];

        } catch (\Exception $e) {
            return ['success' => false, 'latency_ms' => 0, 'error' => $e->getMessage()];
        }
    }

    // ─── Private Helpers ─────────────────────────────────────────────────────

    private function headers(): array
    {
        return [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->apiKey,
        ];
    }

    private function buildPayload(array $messages, array $options): array
    {
        $model       = $options['model']       ?? $this->model;
        $maxTokens   = (int)($options['max_tokens']   ?? $this->maxTokens);
        $temperature = (float)($options['temperature'] ?? $this->temperature);

        // Prepend system prompt if set
        if ($this->systemPrompt !== '') {
            array_unshift($messages, ['role' => 'system', 'content' => $this->systemPrompt]);
        }

        $payload = [
            'model'       => $model,
            'messages'    => $messages,
            'max_tokens'  => $maxTokens,
        ];

        // o1/o3 models don't support temperature
        if (!str_starts_with($model, 'o1') && !str_starts_with($model, 'o3')) {
            $payload['temperature'] = $temperature;
        }

        return $payload;
    }
}
