<?php
/**
 * services/GeminiService.php
 * Llamada a Gemini Flash y contabilidad del presupuesto de tokens de LIA.
 *
 * La usan el asistente del turnero (contexto operativo del día) y el de la
 * documentación (base de conocimiento filtrada por rol). Ambos comparten el
 * mismo presupuesto, guardado en lab_config.lia_tokens_usados.
 */

final class GeminiService
{
    public const TOKENS_MAX = 1_000_000;
    private const MODELO    = 'gemini-3.5-flash';
    private const ENDPOINT  = 'https://generativelanguage.googleapis.com/v1beta/models/';

    private PDO    $pdo;
    private string $apiKey;
    private int    $tokensUsados;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
        $cfg = $pdo->query(
            "SELECT clave, valor FROM lab_config WHERE clave IN ('gemini_api_key','lia_tokens_usados')"
        )->fetchAll(PDO::FETCH_KEY_PAIR);

        $this->apiKey       = trim($cfg['gemini_api_key'] ?? '');
        $this->tokensUsados = (int)($cfg['lia_tokens_usados'] ?? 0);
    }

    public function hayClave(): bool          { return $this->apiKey !== ''; }
    public function presupuestoAgotado(): bool { return $this->tokensUsados >= self::TOKENS_MAX; }
    public function tokensUsados(): int        { return $this->tokensUsados; }
    public function tokensRestantes(): int     { return max(0, self::TOKENS_MAX - $this->tokensUsados); }

    /**
     * Envía una consulta y devuelve la respuesta ya contabilizada.
     *
     * @param  array $historial  Turnos previos: [['role'=>'user'|'model','parts'=>[['text'=>...]]], ...]
     * @return array{respuesta:string, truncada:bool, tokens_usados:int, tokens_restantes:int, tokens_max:int}
     * @throws RuntimeException  si la API falla; el mensaje es apto para mostrar
     */
    public function preguntar(string $systemPrompt, string $pregunta, array $historial = [], int $maxTokens = 2048): array
    {
        $payload = json_encode([
            'system_instruction' => ['parts' => [['text' => $systemPrompt]]],
            'contents' => array_merge($historial, [
                ['role' => 'user', 'parts' => [['text' => $pregunta]]],
            ]),
            'generationConfig' => [
                'temperature'     => 0.3,
                'maxOutputTokens' => $maxTokens,
            ],
        ], JSON_UNESCAPED_UNICODE);

        $ch = curl_init(self::ENDPOINT . self::MODELO . ':generateContent?key=' . $this->apiKey);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => $payload,
            CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
            CURLOPT_TIMEOUT        => 25,
            CURLOPT_SSL_VERIFYPEER => true,
        ]);
        $raw  = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if (!$raw) throw new RuntimeException('Sin respuesta del servicio de IA');

        $json = json_decode($raw, true);
        if ($code !== 200 || empty($json['candidates'][0]['content']['parts'][0]['text'])) {
            throw new RuntimeException('Error IA: ' . ($json['error']['message'] ?? 'Error desconocido'));
        }

        $respuesta = $json['candidates'][0]['content']['parts'][0]['text'];

        // Gemini avisa con finishReason cuando corta; sin esto el usuario recibe
        // un texto truncado a media frase sin saber que faltó contenido.
        $truncada = (($json['candidates'][0]['finishReason'] ?? null) === 'MAX_TOKENS');
        if ($truncada) {
            $respuesta .= "\n\n_(respuesta cortada por longitud — pide el detalle por partes)_";
        }

        $this->contabilizar(
            (int)($json['usageMetadata']['totalTokenCount']
                  ?? (mb_strlen($pregunta) + mb_strlen($respuesta)) / 4)
        );

        return [
            'respuesta'        => $respuesta,
            'truncada'         => $truncada,
            'tokens_usados'    => $this->tokensUsados,
            'tokens_restantes' => $this->tokensRestantes(),
            'tokens_max'       => self::TOKENS_MAX,
        ];
    }

    private function contabilizar(int $tokens): void
    {
        $this->tokensUsados += $tokens;
        $this->pdo->prepare(
            "INSERT INTO lab_config (clave, valor) VALUES ('lia_tokens_usados', ?)
             ON DUPLICATE KEY UPDATE valor = ?"
        )->execute([$this->tokensUsados, $this->tokensUsados]);
    }
}
