<?php

namespace App\LLM;

/**
 * Anthropic (Claude) provider implementation.
 *
 * Config keys (from sp_orgs_meta metval JSON):
 *   secretKey   — required — Anthropic API key (sk-ant-...)
 *   model       — optional — default: claude-sonnet-4-6
 *   max_tokens  — optional — default: 4096
 *   temperature — optional — default: 0.7
 *
 * Usage:
 *   $llm = LLMManager::forOrg($orgId, 'anthropic');
 *   $res = $llm->sendPrompt([['role' => 'user', 'content' => 'Hello']]);
 *   echo $res['content'];
 */
class svcAnthropic extends LLMProvider
{
    private const API_BASE    = 'https://api.anthropic.com/v1';
    private const API_CHAT    = self::API_BASE . '/messages';
    private const API_VERSION = '2023-06-01';

    private static array $availableModels = [
        ['id' => 'claude-opus-4-6',          'label' => 'Claude Opus 4.6'],
        ['id' => 'claude-sonnet-4-6',         'label' => 'Claude Sonnet 4.6'],
        ['id' => 'claude-haiku-4-5-20251001', 'label' => 'Claude Haiku 4.5'],
    ];

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

        $this->apiKey      = $config['secretKey'];
        $this->model       = $config['model']        ?? 'claude-sonnet-4-6';
        $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(!empty($options['cache_system'])), $payload);

            $content = $response['content'][0]['text'] ?? '';
            $usage   = $response['usage'] ?? [];

            return $this->successResponse($content, [
                'prompt_tokens'     => $usage['input_tokens']  ?? 0,
                'completion_tokens' => $usage['output_tokens'] ?? 0,
                'total_tokens'      => ($usage['input_tokens'] ?? 0) + ($usage['output_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);
            $streamError = null;

            $this->curlStream(self::API_CHAT, $this->headers(!empty($options['cache_system'])), $payload, function(array $chunk) use ($callback, &$streamError) {
                $type = $chunk['type'] ?? '';

                if ($type === 'content_block_delta') {
                    $delta = $chunk['delta']['text'] ?? '';
                    if ($delta !== '') {
                        $callback($delta);
                    }
                } elseif ($type === 'error') {
                    // Anthropic sends inline error events during streaming
                    $streamError = $chunk['error']['message'] ?? 'Anthropic streaming error';
                }
            });

            if ($streamError) {
                return ['success' => false, 'error' => $streamError];
            }

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

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

    /**
     * Return supported Claude models.
     */
    public function getModels(): array
    {
        return self::$availableModels;
    }

    /**
     * Validate the API key with a minimal single-token request.
     */
    public function testConnection(): array
    {
        try {
            $start = microtime(true);

            $payload = [
                'model'      => $this->model,
                'max_tokens' => 1,
                'messages'   => [['role' => 'user', 'content' => 'hi']],
            ];

            $this->curlPost(self::API_CHAT, $this->headers(), $payload);

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

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

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

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

    private function headers(bool $withCache = false): array
    {
        $h = [
            'Content-Type: application/json',
            'x-api-key: ' . $this->apiKey,
            'anthropic-version: ' . self::API_VERSION,
        ];
        if ($withCache) {
            $h[] = 'anthropic-beta: prompt-caching-2024-07-31';
        }
        return $h;
    }

    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);
        $cacheSystem = !empty($options['cache_system']);

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

        // Anthropic uses a top-level 'system' key.
        // When cache_system is set, use block format with cache_control so
        // the large static report payload is cached between turns.
        if ($this->systemPrompt !== '') {
            if ($cacheSystem) {
                $payload['system'] = [[
                    'type'          => 'text',
                    'text'          => $this->systemPrompt,
                    'cache_control' => ['type' => 'ephemeral'],
                ]];
            } else {
                $payload['system'] = $this->systemPrompt;
            }
        }

        return $payload;
    }
}
