<?php

namespace app\service\Search;

use app\service\LLM\LLMRetryQueue;
use app\service\LLM\OpenAICompatibleClient;
use Throwable;

class SearchKeywordService
{
    private OpenAICompatibleClient $client;
    private LLMRetryQueue $retryQueue;

    public function __construct(?OpenAICompatibleClient $client = null, ?LLMRetryQueue $retryQueue = null)
    {
        $this->client = $client ?? new OpenAICompatibleClient($this->clientConfig());
        $this->retryQueue = $retryQueue ?? new LLMRetryQueue();
    }

    public function generate(string $query): array
    {
        if (!$this->client->isConfigured()) {
            return $this->fallback($query, 'LLM API is not configured.');
        }

        try {
            $result = $this->retryQueue->run(
                fn (): array => $this->client->chatJson($this->messages($query), [
                    'model' => config('LLMapi.search_keywords.model', config('LLMapi.metadata.model')),
                    'temperature' => config('LLMapi.search_keywords.temperature', 0.1),
                    'max_tokens' => config('LLMapi.search_keywords.max_tokens', 300),
                    'stream' => false,
                    'response_format' => config('LLMapi.search_keywords.response_format', ['type' => 'json_object']),
                    'thinking' => config('LLMapi.search_keywords.thinking', ['type' => 'disabled']),
                    'request_id' => 'search-keywords-' . substr(hash('sha256', $query), 0, 32),
                ]),
                config('LLMapi.search_keywords.retry', config('LLMapi.metadata.retry', []))
            );
        } catch (Throwable $exception) {
            return $this->fallback($query, $exception->getMessage());
        }

        $keywords = $this->keywords($result);
        if ($keywords === []) {
            return $this->fallback($query, 'LLM returned no usable keywords.');
        }

        return [
            'enabled' => true,
            'attempted' => true,
            'error' => null,
            'keywords' => $keywords,
            'query' => implode(' ', $keywords),
            'model' => config('LLMapi.search_keywords.model', config('LLMapi.metadata.model')),
        ];
    }

    private function messages(string $query): array
    {
        return [
            [
                'role' => 'system',
                'content' => implode("\n", [
                    '你是历史档案检索关键词生成助手。',
                    '任务：把用户的自然语言问题改写为适合 BM25 全文检索的关键词。',
                    '优先输出档案中可能出现的英文专名、政策名、事件名、人物名、地点名、缩写和年份。',
                    '如果用户输入中文，请翻译或扩展为可能出现在英文档案中的关键词。',
                    '只返回 JSON 对象，不要 Markdown，不要解释。',
                    'JSON 格式：{"keywords":["keyword1","keyword2"],"query":"keyword1 keyword2"}。',
                    'keywords 数量 3-12 个；不要编造过于具体而输入中没有依据的事实。',
                ]),
            ],
            [
                'role' => 'user',
                'content' => json_encode(['query' => $query], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            ],
        ];
    }

    private function clientConfig(): array
    {
        $config = config('LLMapi.default', []);
        $config['timeout'] = config('LLMapi.search_keywords.timeout', 12);
        $config['connect_timeout'] = config('LLMapi.search_keywords.connect_timeout', 5);

        return $config;
    }

    private function keywords(array $result): array
    {
        $keywords = [];
        if (isset($result['keywords']) && is_array($result['keywords'])) {
            $keywords = $result['keywords'];
        } elseif (isset($result['query']) && is_string($result['query'])) {
            $keywords = preg_split('/\s+/', $result['query']) ?: [];
        }

        return array_values(array_unique(array_filter(array_map(
            static fn (mixed $value): string => trim((string) $value),
            $keywords
        ))));
    }

    private function fallback(string $query, string $error): array
    {
        return [
            'enabled' => true,
            'attempted' => false,
            'error' => $error,
            'keywords' => [],
            'query' => $query,
            'model' => null,
        ];
    }
}
