<?php

namespace XZD\AI\V5\AI;

/**
 * OpenAI 兼容格式 Provider 抽象基类
 */
abstract class OpenAICompatibleProvider implements ProviderInterface
{
    protected array $config;

    public function __construct(array $config)
    {
        $this->config = array_merge([
            'provider'    => $this->getProviderKey(),
            'api_key'     => '',
            'base_url'    => '',
            'model'       => '',
            'timeout'     => 90,
            'max_retries' => 3,
        ], $config);

        // 用户配置的timeout不得低于60秒
        if (($this->config['timeout'] ?? 0) < 60) {
            $this->config['timeout'] = 60;
        }
    }

    /**
     * 返回默认 Base URL（子类覆盖）
     */
    abstract public static function defaultBaseUrl(): string;

    /**
     * 返回默认模型名（子类覆盖）
     */
    abstract public static function defaultModel(): string;

    public function chat(array $messages, array $options = []): array
    {
        $disableThinking = $options['disable_thinking'] ?? false;
        $body = $this->buildRequestBody($messages, $options, false);
        $result = $this->request('/chat/completions', $body);

        // 自动重试：如果模型不支持 thinking 参数（如 deepseek-reasoner），去掉后重试
        if ($disableThinking && isset($result['error']) && (
            stripos($result['error'], 'thinking') !== false ||
            stripos($result['error'], 'does not support') !== false
        )) {
            $options['disable_thinking'] = false;
            $body = $this->buildRequestBody($messages, $options, false);
            $result = $this->request('/chat/completions', $body);
        }

        if (isset($result['error'])) {
            return Types::error($result['error'], $result['error_code'] ?? 'api_error');
        }

        $choice = $result['choices'][0]['message'] ?? [];
        $usage = $result['usage'] ?? [];

        // 推理模型 (如 deepseek-v4-pro) 的思考内容在 reasoning_content，
        // 绝不要把思考过程当作回复内容展示给用户
        $content = $choice['content'] ?? '';

        return Types::response(
            content: $content,
            inputTokens: $usage['prompt_tokens'] ?? 0,
            outputTokens: $usage['completion_tokens'] ?? 0,
            toolCalls: $this->parseToolCalls($choice['tool_calls'] ?? null),
            cacheTokens: self::extractCacheTokens($usage),
        );
    }

    /**
     * 从 usage 中提取缓存命中 token
     */
    private static function extractCacheTokens(array $usage): int
    {
        if (isset($usage['prompt_cache_hit_tokens'])) {
            return (int) $usage['prompt_cache_hit_tokens'];
        }
        if (isset($usage['prompt_tokens_details']['cached_tokens'])) {
            return (int) $usage['prompt_tokens_details']['cached_tokens'];
        }
        if (isset($usage['cache_tokens'])) {
            return (int) $usage['cache_tokens'];
        }
        return 0;
    }

    public function chatStream(array $messages, array $options = [], ?callable $onChunk = null): \Generator
    {
        // 对推理模型（如 deepseek-reasoner）不发送 thinking 参数，避免 API 报错
        $modelLower = strtolower($this->config['model']);
        if (str_contains($modelLower, 'reasoner') || str_contains($modelLower, 'r1')) {
            $options['disable_thinking'] = false;
        }

        $body = $this->buildRequestBody($messages, $options, true);
        $url = rtrim($this->getBaseUrl(), '/') . '/chat/completions';

        $buffer = '';
        $chunks = [];

        $ch = $this->initCurl($url, $body);
        curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($_, $data) use (&$buffer, &$chunks, $onChunk) {
            $buffer .= $data;
            while (($pos = strpos($buffer, "\n")) !== false) {
                $line = trim(substr($buffer, 0, $pos));
                $buffer = substr($buffer, $pos + 1);

                if ($line === '' || $line === 'data: [DONE]') {
                    continue;
                }
                if (!str_starts_with($line, 'data: ')) {
                    continue;
                }
                $json = json_decode(substr($line, 6), true);
                if (!$json) {
                    continue;
                }

                $delta = $json['choices'][0]['delta'] ?? [];
                $finish = $json['choices'][0]['finish_reason'] ?? null;
                $usage = $json['usage'] ?? null;

                $content = $delta['content'] ?? '';
                if (($content === '' || $content === null) && !isset($delta['tool_calls']) && !$usage) {
                    continue;
                }

                $chunk = [
                    'delta'         => $content,
                    'type'          => isset($delta['tool_calls']) ? 'tool_call' : 'text',
                    'tool_call'     => $delta['tool_calls'] ?? null,
                    'done'          => $finish !== null,
                    'finish_reason' => $finish,
                    'usage'         => $usage,
                ];
                $chunks[] = $chunk;

                // chunk 到达立刻通知 Controller
                if ($onChunk) {
                    $onChunk($chunk);
                }
            }
            return strlen($data);
        });

        curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        curl_close($ch);

        if ($curlError || $httpCode >= 400) {
            $errorDetail = '';
            if (!empty($buffer)) {
                $cleanBuffer = trim($buffer);
                // 尝试从 SSE 格式中提取 JSON（流式错误响应）
                if (preg_match('/^\s*data:\s*(\{.+?\})\s*$/ms', $cleanBuffer, $sseMatch)) {
                    $errorData = json_decode($sseMatch[1], true);
                } elseif (preg_match_all('/data:\s*(\{[^\n]+\})/', $cleanBuffer, $sseMatches)) {
                    // 多个 SSE 行，取最后一条有效 JSON
                    $errorData = null;
                    foreach ($sseMatches[1] as $jsonStr) {
                        $parsed = json_decode($jsonStr, true);
                        if ($parsed && isset($parsed['error'])) {
                            $errorData = $parsed;
                        }
                    }
                } else {
                    $errorData = json_decode($cleanBuffer, true);
                }
                if ($errorData && isset($errorData['error']['message'])) {
                    $errorDetail = ': ' . $errorData['error']['message'];
                } elseif ($errorData && isset($errorData['error'])) {
                    $errorDetail = ': ' . json_encode($errorData['error'], JSON_UNESCAPED_UNICODE);
                } elseif (strlen($cleanBuffer) < 500) {
                    $errorDetail = ': ' . $cleanBuffer;
                }
            }
            $errorMsg = $curlError
                ? self::friendlyCurlError($curlError)
                : "HTTP {$httpCode}{$errorDetail}";
            error_log("[XZD-AI ERROR] chatStream failed: " . ($curlError ?: "HTTP {$httpCode}{$errorDetail}"));
            $errChunk = ['delta' => '', 'type' => 'error', 'done' => true, 'error' => $errorMsg];
            if ($onChunk) $onChunk($errChunk);
            yield $errChunk;
            return;
        }

        foreach ($chunks as $chunk) {
            yield $chunk;
        }
    }

    public function testConnection(): array
    {
        // 方案1: 尝试 /models 端点(快速、无Token消耗)
        $modelsResult = $this->testModelsEndpoint();
        if ($modelsResult['success']) {
            return $modelsResult;
        }

        // 方案2: 发送简单的 chat 请求
        // 对推理模型禁用thinking，使用tool_choice确保content返回
        $result = $this->chat(
            [Types::user('Say "OK"')],
            ['max_tokens' => 20, 'disable_thinking' => true]
        );
        if (isset($result['error'])) {
            return ['success' => false, 'message' => $result['error']];
        }
        // 检查是否有有效回复
        $content = $result['content'] ?? '';
        if (empty(trim($content))) {
            return ['success' => false, 'message' => 'API返回空内容，可能是模型配置问题'];
        }
        return ['success' => true, 'message' => '连接正常', 'model' => $this->config['model'] ?? ''];
    }

    /**
     * 测试 /models 端点是否可用
     */
    private function testModelsEndpoint(): array
    {
        $url = rtrim($this->getBaseUrl(), '/') . '/models';
        $response = wp_remote_get($url, [
            'timeout' => 8,
            'headers' => $this->buildHeaders(),
        ]);

        if (is_wp_error($response)) {
            return ['success' => false];
        }

        $code = wp_remote_retrieve_response_code($response);
        if ($code === 200) {
            return ['success' => true, 'message' => '连接正常'];
        }
        return ['success' => false];
    }

    public function getModels(): array
    {
        // OpenAI /models 端点；部分厂商可能返回404
        $url = rtrim($this->getBaseUrl(), '/') . '/models';
        $response = wp_remote_get($url, [
            'timeout' => 10,
            'headers' => $this->buildHeaders(),
        ]);

        if (is_wp_error($response)) {
            return ['success' => false, 'models' => [], 'error' => $response->get_error_message()];
        }

        $body = json_decode(wp_remote_retrieve_body($response), true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            return ['success' => false, 'models' => [], 'error' => 'JSON 解析失败'];
        }

        $models = [];
        foreach (($body['data'] ?? []) as $m) {
            $models[] = ['id' => $m['id'], 'name' => $m['id']];
        }

        return ['success' => true, 'models' => $models];
    }

    public function getConfig(): array
    {
        return $this->config;
    }

    protected function buildRequestBody(array $messages, array $options, bool $stream): array
    {
        $model = $options['model'] ?? ($this->config['model'] ?: static::defaultModel());
        $body = [
            'model'    => $model,
            'messages' => $messages,
            'stream'   => $stream,
        ];

        if (isset($options['temperature'])) {
            $body['temperature'] = $options['temperature'];
        }
        if (isset($options['max_tokens'])) {
            $body['max_tokens'] = $options['max_tokens'];
        }
        if (!empty($options['tools'])) {
            $body['tools'] = $options['tools'];
        }
        if (!empty($options['tool_choice'])) {
            $body['tool_choice'] = $options['tool_choice'];
        }
        // 禁用推理/思考模式 (DeepSeek 等模型默认开启)
        if ($options['disable_thinking'] ?? false) {
            $body['thinking'] = ['type' => 'disabled'];
        }

        if ($stream) {
            $body['stream_options'] = ['include_usage' => true];
        }

        return $body;
    }

    protected function buildHeaders(): array
    {
        return [
            'Authorization' => 'Bearer ' . $this->config['api_key'],
            'Content-Type'  => 'application/json',
            'Accept'        => 'application/json',
        ];
    }

    protected function getBaseUrl(): string
    {
        return $this->config['base_url'] ?: static::defaultBaseUrl();
    }

    /**
     * HTTP POST 请求（非流式）
     */
    protected function request(string $path, array $body, array $extraHeaders = []): array
    {
        $url = rtrim($this->getBaseUrl(), '/') . $path;
        $attempts = 0;
        $maxRetries = $this->config['max_retries'] ?? 3;
        $lastError = null;

        while ($attempts <= $maxRetries) {
            $response = wp_remote_post($url, [
                'timeout' => $this->config['timeout'] ?? 90,
                'headers' => array_merge($this->buildHeaders(), $extraHeaders),
                'body'    => wp_json_encode($body),
            ]);

            if (is_wp_error($response)) {
                $lastError = $response->get_error_message();
                $attempts++;
                usleep(min(4000000, 500000 * (2 ** ($attempts - 1))));
                continue;
            }

            $httpCode = wp_remote_retrieve_response_code($response);
            $respBody = wp_remote_retrieve_body($response);
            $data = json_decode($respBody, true);

            if ($httpCode >= 500) {
                $lastError = "服务器错误 {$httpCode}";
                $attempts++;
                usleep(500000 * $attempts); // 指数退避
                continue;
            }

            if ($httpCode >= 400) {
                $errMsg = $data['error']['message'] ?? "HTTP {$httpCode}";
                $errCode = ErrorClassifier::classify($errMsg, $httpCode);
                return ['error' => $errMsg, 'error_code' => $errCode];
            }

            if (json_last_error() !== JSON_ERROR_NONE) {
                return ['error' => '响应解析失败: ' . $respBody, 'error_code' => 'parse_error'];
            }

            return $data;
        }

        return ['error' => $lastError ?? '未知错误', 'error_code' => 'max_retries'];
    }

    /**
     * 初始化 cURL (流式用)
     */
    protected function initCurl(string $url, array $body): \CurlHandle
    {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => wp_json_encode($body),
            CURLOPT_HTTPHEADER     => $this->buildCurlHeaders(),
            CURLOPT_RETURNTRANSFER => false,
            CURLOPT_TIMEOUT        => $this->config['timeout'] ?? 90,
            CURLOPT_CONNECTTIMEOUT => 15,
        ]);
        return $ch;
    }

    protected function buildCurlHeaders(): array
    {
        $headers = [];
        foreach ($this->buildHeaders() as $k => $v) {
            $headers[] = "{$k}: {$v}";
        }
        return $headers;
    }

    /**
     * 对API错误消息进行分类
     * @deprecated 已提取至 ErrorClassifier::classify()
     */
    protected static function classifyError(string $errMsg, int $httpCode): string
    {
        return ErrorClassifier::classify($errMsg, $httpCode);
    }

    /**
     * 将底层 cURL 错误转为用户友好的中文提示
     */
    private static function friendlyCurlError(string $curlError): string
    {
        $lower = strtolower($curlError);
        if (str_contains($lower, 'timed out') || str_contains($lower, 'timeout')) {
            if (str_contains($lower, 'resolve') || str_contains($lower, 'dns')) {
                return 'AI服务连接超时（DNS解析失败），请稍后重试';
            }
            if (str_contains($lower, 'connect')) {
                return 'AI服务连接超时，请稍后重试';
            }
            return 'AI服务响应超时，请稍后重试或缩短问题';
        }
        if (str_contains($lower, 'resolve') || str_contains($lower, 'host')) {
            return '无法解析AI服务域名，请检查服务器DNS配置';
        }
        if (str_contains($lower, 'refused')) {
            return 'AI服务拒绝连接，请稍后重试';
        }
        // SSL 连接重置 / 被对端断开
        if (str_contains($lower, 'reset by peer') || str_contains($lower, 'errno 104')) {
            return 'AI服务连接中断，正在自动切换备用连接重试';
        }
        // SSL 协议错误
        if (str_contains($lower, 'ssl_read') || str_contains($lower, 'ssl_write')
            || str_contains($lower, 'unrecognized name')) {
            return 'AI服务SSL连接异常，可能该服务商暂时不可用，请检查连接配置';
        }
        if (str_contains($lower, 'ssl') || str_contains($lower, 'certificate')) {
            return 'AI服务SSL证书验证失败，请检查服务器时间';
        }
        // 兜底：隐藏原始错误细节，只给通用提示
        error_log('[XZD-AI] 原始cURL错误: ' . $curlError);
        return 'AI服务暂时不可用，请稍后重试';
    }

    /**
     * 解析 tool_calls (OpenAI 格式)
     */
    protected function parseToolCalls(?array $toolCalls): ?array
    {
        if (empty($toolCalls)) {
            return null;
        }
        $parsed = [];
        foreach ($toolCalls as $tc) {
            $parsed[] = [
                'id'   => $tc['id'] ?? '',
                'type' => 'function',
                'function' => [
                    'name'      => $tc['function']['name'] ?? '',
                    'arguments' => $tc['function']['arguments'] ?? '{}',
                ],
            ];
        }
        return $parsed;
    }
}
