<?php
/**
 * سودانا — AIClient v4 (PATCHED)
 * 3-key cascade: Gemini Flash → Claude Sonnet → GPT-4o
 * 
 * v4 CHANGES:
 *  - call() now accepts $opts array as 4th arg (max_tokens, temperature, ...)
 *  - Backward-compat: 4th-6th positional args still accepted (ignored if int/bool)
 *  - Returns ['text', 'content', 'tokens_in', 'tokens_out', 'cost_usd', 'provider', 'model_used']
 *  - 'content' is an alias for 'text' (legacy callers expect 'content')
 *  - Captures real token usage from each API response
 */
class AIClient {

    private static array $log = [];

    // Pricing per 1M tokens (USD) — used for cost_usd calculation
    private const PRICING = [
        'gemini' => ['in' => 0.075, 'out' => 0.30],   // Gemini 2.0 Flash
        'claude' => ['in' => 3.00,  'out' => 15.00],  // Claude Sonnet
        'gpt'    => ['in' => 2.50,  'out' => 10.00],  // GPT-4o
    ];

    /**
     * Main entry. New signature accepts opts array.
     * Old callers: call($p, $s, 'auto'), call($p, $s, ['max_tokens'=>2000])
     *              call($p, $s, 'fast', 300, 0.7, false) — extra args ignored
     */
    public static function call(string $prompt, string $system = '', $modelOrOpts = 'auto', ...$rest): array {
        // Parse 3rd arg: model string OR opts array
        $model = 'auto';
        $opts  = [];
        if (is_array($modelOrOpts)) {
            $opts  = $modelOrOpts;
            $model = $opts['model'] ?? 'auto';
        } elseif (is_string($modelOrOpts)) {
            $model = $modelOrOpts;
        }
        // Legacy 4th positional: max_tokens (int)
        if (isset($rest[0]) && is_int($rest[0]) && !isset($opts['max_tokens'])) {
            $opts['max_tokens'] = $rest[0];
        }
        // Legacy 5th positional: temperature (float)
        if (isset($rest[1]) && is_numeric($rest[1]) && !isset($opts['temperature'])) {
            $opts['temperature'] = (float)$rest[1];
        }

        $maxTokens   = (int)($opts['max_tokens']  ?? 1000);
        $temperature = (float)($opts['temperature'] ?? 0.4);

        $providers = [
            ['name' => 'gemini', 'fn' => 'callGemini'],
            ['name' => 'claude', 'fn' => 'callClaude'],
            ['name' => 'gpt',    'fn' => 'callGPT'],
        ];
        if ($model !== 'auto') {
            $providers = match (true) {
                str_contains($model, 'gemini') => [['name'=>'gemini','fn'=>'callGemini']],
                str_contains($model, 'claude') => [['name'=>'claude','fn'=>'callClaude']],
                str_contains($model, 'gpt')    => [['name'=>'gpt','fn'=>'callGPT']],
                default                        => $providers,
            };
        }

        $lastErr = null;
        foreach ($providers as $p) {
            try {
                $result = self::{$p['fn']}($prompt, $system, $maxTokens, $temperature);
                if (!empty($result['text'])) {
                    self::$log[] = [
                        'provider'   => $p['name'],
                        'ok'         => true,
                        'tokens_in'  => $result['tokens_in']  ?? 0,
                        'tokens_out' => $result['tokens_out'] ?? 0,
                        'cost_usd'   => $result['cost_usd']   ?? 0,
                    ];
                    // Add legacy 'content' alias for callers that expect it
                    $result['content']    = $result['text'];
                    $result['model_used'] = $p['name'];
                    return $result;
                }
            } catch (Throwable $e) {
                $lastErr = $e->getMessage();
                self::$log[] = ['provider' => $p['name'], 'ok' => false, 'error' => substr($lastErr, 0, 80)];
                error_log("[AIClient::{$p['name']}] " . $lastErr);
                continue;
            }
        }
        return [
            'text'       => '',
            'content'    => '',
            'tokens_in'  => 0,
            'tokens_out' => 0,
            'cost_usd'   => 0,
            'model_used' => 'none',
            'error'      => $lastErr,
        ];
    }

    /** Compute cost in USD given provider + token counts */
    private static function cost(string $provider, int $tokensIn, int $tokensOut): float {
        $p = self::PRICING[$provider] ?? ['in' => 0, 'out' => 0];
        return round(
            ($tokensIn  * $p['in']  / 1_000_000) +
            ($tokensOut * $p['out'] / 1_000_000),
            8
        );
    }

    // ── Gemini Flash 2.0 ───────────────────────────────────────────
    private static function callGemini(string $prompt, string $system, int $maxTokens, float $temperature): array {
        if (!defined('GEMINI_API_KEY') || strlen(GEMINI_API_KEY) < 10) {
            throw new RuntimeException('GEMINI_API_KEY not configured');
        }
        $full = $system ? "SYSTEM: $system\n\nUSER: $prompt" : $prompt;
        $url  = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=' . GEMINI_API_KEY;
        $body = json_encode([
            'contents'         => [['role'=>'user','parts'=>[['text'=>$full]]]],
            'generationConfig' => ['maxOutputTokens'=>$maxTokens, 'temperature'=>$temperature],
        ]);
        $resp = self::http($url, $body, ['Content-Type: application/json']);
        $data = json_decode($resp, true);
        $text = $data['candidates'][0]['content']['parts'][0]['text'] ?? '';
        if (empty($text)) throw new RuntimeException('Gemini empty response');

        $tIn  = (int)($data['usageMetadata']['promptTokenCount']     ?? 0);
        $tOut = (int)($data['usageMetadata']['candidatesTokenCount'] ?? 0);

        return [
            'text'       => $text,
            'provider'   => 'gemini',
            'tokens_in'  => $tIn,
            'tokens_out' => $tOut,
            'cost_usd'   => self::cost('gemini', $tIn, $tOut),
        ];
    }

    // ── Claude Sonnet ──────────────────────────────────────────────
    private static function callClaude(string $prompt, string $system, int $maxTokens, float $temperature): array {
        if (!defined('CLAUDE_API_KEY') || strlen(CLAUDE_API_KEY) < 10) {
            throw new RuntimeException('CLAUDE_API_KEY not configured');
        }
        $body = json_encode([
            'model'       => 'claude-sonnet-4-6',
            'max_tokens'  => $maxTokens,
            'temperature' => $temperature,
            'system'      => $system ?: 'أنت مساعد سودانا الذكي. أجب بالعربية.',
            'messages'    => [['role'=>'user','content'=>$prompt]],
        ]);
        $resp = self::http('https://api.anthropic.com/v1/messages', $body, [
            'Content-Type: application/json',
            'x-api-key: ' . CLAUDE_API_KEY,
            'anthropic-version: 2023-06-01',
        ]);
        $data = json_decode($resp, true);
        $text = $data['content'][0]['text'] ?? '';
        if (empty($text)) throw new RuntimeException('Claude empty response: ' . ($data['error']['message'] ?? 'unknown'));

        $tIn  = (int)($data['usage']['input_tokens']  ?? 0);
        $tOut = (int)($data['usage']['output_tokens'] ?? 0);

        return [
            'text'       => $text,
            'provider'   => 'claude',
            'tokens_in'  => $tIn,
            'tokens_out' => $tOut,
            'cost_usd'   => self::cost('claude', $tIn, $tOut),
        ];
    }

    // ── GPT-4o ─────────────────────────────────────────────────────
    private static function callGPT(string $prompt, string $system, int $maxTokens, float $temperature): array {
        if (!defined('OPENAI_API_KEY') || strlen(OPENAI_API_KEY) < 10) {
            throw new RuntimeException('OPENAI_API_KEY not configured');
        }
        $messages = [];
        if ($system) $messages[] = ['role'=>'system','content'=>$system];
        $messages[] = ['role'=>'user','content'=>$prompt];
        $body = json_encode([
            'model'       => 'gpt-4o',
            'max_tokens'  => $maxTokens,
            'temperature' => $temperature,
            'messages'    => $messages,
        ]);
        $resp = self::http('https://api.openai.com/v1/chat/completions', $body, [
            'Content-Type: application/json',
            'Authorization: Bearer ' . OPENAI_API_KEY,
        ]);
        $data = json_decode($resp, true);
        $text = $data['choices'][0]['message']['content'] ?? '';
        if (empty($text)) throw new RuntimeException('GPT empty response: ' . ($data['error']['message'] ?? 'unknown'));

        $tIn  = (int)($data['usage']['prompt_tokens']     ?? 0);
        $tOut = (int)($data['usage']['completion_tokens'] ?? 0);

        return [
            'text'       => $text,
            'provider'   => 'gpt',
            'tokens_in'  => $tIn,
            'tokens_out' => $tOut,
            'cost_usd'   => self::cost('gpt', $tIn, $tOut),
        ];
    }

    // ── Shared HTTP ────────────────────────────────────────────────
    private static function http(string $url, string $body, array $headers): string {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => $body,
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 25,
            CURLOPT_CONNECTTIMEOUT => 8,
            CURLOPT_SSL_VERIFYPEER => true,
        ]);
        $resp = curl_exec($ch);
        $err  = curl_error($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($err)         throw new RuntimeException("cURL: $err");
        if ($code >= 500) throw new RuntimeException("HTTP $code from API");
        if ($code >= 400) throw new RuntimeException("HTTP $code: " . substr($resp, 0, 200));
        return $resp;
    }

    public static function getLogs(): array { return self::$log; }
    public static function reset(): void    { self::$log = []; }
}
