<?php

namespace XZD\AI\V5\Tools;

use XZD\AI\V5\Core\Logger;

class KnowledgeManager
{
    /** 搜索知识库 */
    public static function search(array $params, int $userId): ToolResult
    {
        global $wpdb;
        $table = $wpdb->prefix . 'xzd_ai_knowledge';
        $faqTable = $wpdb->prefix . 'xzd_ai_faq';
        $keyword = $params['keyword'] ?? '';
        $limit = min((int) ($params['limit'] ?? 10), 50);

        $knowledgeResults = [];
        $faqResults = [];

        if (empty($keyword)) {
            $knowledgeResults = $wpdb->get_results(
                "SELECT id, title, LEFT(content, 800) AS content, source_type, access_level, created_at FROM {$table} WHERE status = 'active' ORDER BY created_at DESC LIMIT {$limit}",
                ARRAY_A
            );
        } else {
            // 中文分词以提高搜索精度
            $tokenizer = new ChineseTokenizer();
            $tokens = $tokenizer->segment($keyword);
            $searchTokens = !empty($tokens) ? $tokens : [$keyword];

            // 多关键词 OR 查询知识库
            $conditions = [];
            $queryParams = [];
            foreach ($searchTokens as $token) {
                $like = '%' . $wpdb->esc_like($token) . '%';
                $conditions[] = '(title LIKE %s OR content LIKE %s)';
                $queryParams[] = $like;
                $queryParams[] = $like;
            }

            $where = implode(' OR ', $conditions);
            $queryParams[] = $limit;
            $sql = $wpdb->prepare(
                "SELECT id, title, LEFT(content, 800) AS content, source_type, access_level, created_at FROM {$table} WHERE status = 'active' AND ({$where}) LIMIT %d",
                ...$queryParams
            );
            $knowledgeResults = $wpdb->get_results($sql, ARRAY_A);

            // 分词后无结果，回退到原始关键词模糊搜索
            if (empty($knowledgeResults) && count($searchTokens) > 1) {
                $like = '%' . $wpdb->esc_like($keyword) . '%';
                $knowledgeResults = $wpdb->get_results($wpdb->prepare(
                    "SELECT id, title, LEFT(content, 800) AS content, source_type, access_level, created_at FROM {$table} WHERE status = 'active' AND (title LIKE %s OR content LIKE %s) LIMIT %d",
                    $like, $like, $limit
                ), ARRAY_A);
            }

            // 同时搜索FAQ表
            $faqConditions = [];
            $faqParams = [];
            foreach ($searchTokens as $token) {
                $like = '%' . $wpdb->esc_like($token) . '%';
                $faqConditions[] = '(question LIKE %s OR answer LIKE %s)';
                $faqParams[] = $like;
                $faqParams[] = $like;
            }
            $faqWhere = implode(' OR ', $faqConditions);
            $faqParams[] = $limit;
            $faqSql = $wpdb->prepare(
                "SELECT id, question AS title, answer AS content, 'faq' AS source_type, created_at FROM {$faqTable} WHERE status = 'active' AND ({$faqWhere}) LIMIT %d",
                ...$faqParams
            );
            $faqResults = $wpdb->get_results($faqSql, ARRAY_A);

            // FAQ也做回退搜索
            if (empty($faqResults) && count($searchTokens) > 1) {
                $like = '%' . $wpdb->esc_like($keyword) . '%';
                $faqResults = $wpdb->get_results($wpdb->prepare(
                    "SELECT id, question AS title, answer AS content, 'faq' AS source_type, created_at FROM {$faqTable} WHERE status = 'active' AND (question LIKE %s OR answer LIKE %s) LIMIT %d",
                    $like, $like, $limit
                ), ARRAY_A);
            }
        }

        // 权限过滤：根据用户角色过滤知识库结果
        $userRole = \XZD\AI\V5\Core\UserHelper::getRole($userId);
        $filteredKnowledge = [];
        foreach ($knowledgeResults ?: [] as &$item) {
            $level = $item['access_level'] ?? 'public';
            if ($level === 'public' || $userRole === 'admin') {
                $filteredKnowledge[] = $item;
                continue;
            }
            if ($level === 'logged_in' && $userRole === 'guest') {
                $item['content'] = '[此内容需要登录后查看]';
                $item['restricted'] = true;
                $filteredKnowledge[] = $item;
            } elseif ($level === 'vip' && !in_array($userRole, ['vip', 'admin'])) {
                $item['content'] = '[此内容为VIP专属，请升级后查看]';
                $item['restricted'] = true;
                $filteredKnowledge[] = $item;
            } elseif (str_starts_with($level, 'paid_')) {
                $postId = (int) substr($level, 5);
                $canAccess = false;
                if (function_exists('zibpay_is_paid')) {
                    try { $canAccess = zibpay_is_paid($postId, $userId); } catch (\Throwable $e) {}
                }
                if (!$canAccess && $userRole !== 'admin') {
                    $item['content'] = '[此内容需要付费购买后查看]';
                    $item['restricted'] = true;
                }
                $filteredKnowledge[] = $item;
            } else {
                $filteredKnowledge[] = $item;
            }
        }

        // 向量搜索：与关键词搜索并行，结果合并去重
        $vectorResults = [];
        if (!empty($keyword)) {
            $vectorResults = self::vectorSearch($keyword, $limit, $userId);
        }

        // 合并知识库结果、FAQ结果和向量搜索结果（按ID去重）
        $allResults = $filteredKnowledge;
        $seenIds = [];
        foreach ($allResults as $r) {
            $seenIds['k_' . ($r['id'] ?? 0)] = true;
        }
        foreach ($faqResults ?: [] as $fr) {
            $key = 'f_' . ($fr['id'] ?? 0);
            if (!isset($seenIds[$key])) {
                $allResults[] = $fr;
                $seenIds[$key] = true;
            }
        }
        foreach ($vectorResults as $vr) {
            if (!isset($seenIds['k_' . ($vr['id'] ?? 0)])) {
                $allResults[] = $vr;
                $seenIds['k_' . ($vr['id'] ?? 0)] = true;
            }
        }

        return ToolResult::success(['results' => $allResults, 'count' => count($allResults),
            'knowledge_count' => count($filteredKnowledge) + count($vectorResults),
            'faq_count' => count($faqResults ?: [])]);
    }

    /**
     * 向量语义搜索
     *
     * 使用embedding API将用户查询转为向量，与知识库chunks计算余弦相似度。
     * 向量搜索不可用时返回空数组，不影响主搜索流程。
     */
    private static function vectorSearch(string $query, int $limit, int $userId): array
    {
        global $wpdb;
        $chunkTable = $wpdb->prefix . 'xzd_ai_knowledge_chunks';
        $knowledgeTable = $wpdb->prefix . 'xzd_ai_knowledge';

        // 生成查询向量
        $queryEmbedding = self::getQueryEmbedding($query);
        if (!$queryEmbedding) return [];

        // 获取所有有embedding的chunks（限制200条防止OOM）
        $chunks = $wpdb->get_results(
            "SELECT c.id AS chunk_id, c.knowledge_id, c.embedding, k.id, k.title, k.content, k.source_type, k.access_level, k.created_at
             FROM {$chunkTable} c
             JOIN {$knowledgeTable} k ON c.knowledge_id = k.id
             WHERE c.embedding IS NOT NULL AND c.embedding != '' AND k.status = 'active'
             LIMIT 200",
            ARRAY_A
        );

        if (empty($chunks)) return [];

        // 计算余弦相似度
        $scores = [];
        foreach ($chunks as $chunk) {
            $vec = json_decode($chunk['embedding'], true);
            if (!$vec || !is_array($vec)) continue;

            $similarity = self::cosineSimilarity($queryEmbedding, $vec);
            $kid = $chunk['knowledge_id'];
            if (!isset($scores[$kid]) || $scores[$kid]['score'] < $similarity) {
                $scores[$kid] = [
                    'score'      => $similarity,
                    'id'         => $chunk['id'],
                    'title'      => $chunk['title'],
                    'content'    => mb_substr($chunk['content'], 0, 800),
                    'source_type'=> $chunk['source_type'],
                    'access_level'=> $chunk['access_level'],
                    'created_at' => $chunk['created_at'],
                ];
            }
        }

        // 按相似度降序排序
        uasort($scores, fn($a, $b) => $b['score'] <=> $a['score']);

        // 过滤低相似度（阈值0.5）并限制数量
        $results = [];
        $userRole = \XZD\AI\V5\Core\UserHelper::getRole($userId);
        foreach ($scores as $s) {
            if ($s['score'] < 0.5) continue;
            if (count($results) >= $limit) break;

            // 权限过滤（与主搜索一致）
            $level = $s['access_level'] ?? 'public';
            if ($level !== 'public' && $userRole !== 'admin') {
                if ($level === 'logged_in' && $userRole === 'guest') {
                    $s['content'] = '[此内容需要登录后查看]';
                    $s['restricted'] = true;
                } elseif ($level === 'vip' && !in_array($userRole, ['vip', 'admin'])) {
                    $s['content'] = '[此内容为VIP专属，请升级后查看]';
                    $s['restricted'] = true;
                }
            }

            $results[] = $s;
        }

        return $results;
    }

    /** 获取查询文本的embedding向量 */
    private static function getQueryEmbedding(string $text): ?array
    {
        try {
            $connections = \XZD\AI\V5\Core\Config::instance()->get('connections.text', []);
            foreach ($connections as $conn) {
                if (empty($conn['api_key']) || empty($conn['is_enabled'] ?? true)) continue;

                $provider = $conn['provider'] ?? '';
                if (!in_array($provider, ['deepseek', 'openai', 'zhipu', 'moonshot', 'qwen'])) continue;

                $apiKey = $conn['api_key'];
                if (str_starts_with($apiKey, XZD_AI_ENC_PREFIX)) {
                    $apiKey = \XZD\AI\V5\Core\Config::instance()->decrypt($apiKey);
                }
                $baseUrl = $conn['api_base_url'] ?? '';

                // 构建embedding API请求
                if ($provider === 'deepseek') {
                    $url = $baseUrl ? rtrim($baseUrl, '/') . '/v1/embeddings' : 'https://api.deepseek.com/v1/embeddings';
                    $body = ['model' => 'deepseek-embed', 'input' => $text];
                } else {
                    $apiBase = $baseUrl ?: 'https://api.openai.com/v1';
                    $url = rtrim($apiBase, '/') . '/embeddings';
                    $model = match($provider) {
                        'openai'   => 'text-embedding-3-small',
                        'zhipu'    => 'embedding-2',
                        'moonshot' => 'moonshot-v1-8k',
                        'qwen'     => 'text-embedding-v3',
                        default    => 'text-embedding-3-small',
                    };
                    $body = ['model' => $model, 'input' => $text];
                }

                $response = wp_remote_post($url, [
                    'headers' => [
                        'Authorization' => "Bearer {$apiKey}",
                        'Content-Type'  => 'application/json',
                    ],
                    'body'    => json_encode($body),
                    'timeout' => 15,
                ]);

                if (is_wp_error($response)) continue;
                $data = json_decode(wp_remote_retrieve_body($response), true);
                return $data['data'][0]['embedding'] ?? null;
            }
        } catch (\Throwable $e) {
            Logger::debug('knowledge_vector', "查询向量化失败: {$e->getMessage()}");
        }
        return null;
    }

    /** 计算余弦相似度 */
    private static function cosineSimilarity(array $a, array $b): float
    {
        if (count($a) !== count($b)) return 0;
        $dot = 0; $normA = 0; $normB = 0;
        $n = count($a);
        for ($i = 0; $i < $n; $i++) {
            $dot += $a[$i] * $b[$i];
            $normA += $a[$i] * $a[$i];
            $normB += $b[$i] * $b[$i];
        }
        if ($normA == 0 || $normB == 0) return 0;
        return $dot / (sqrt($normA) * sqrt($normB));
    }

    /** 添加 FAQ */
    public static function addFaq(array $params, int $userId): ToolResult
    {
        global $wpdb;
        $table = $wpdb->prefix . 'xzd_ai_faq';
        $wpdb->insert($table, [
            'question'  => $params['question'] ?? '',
            'answer'    => $params['answer'] ?? '',
            'category'  => $params['category'] ?? '',
            'sort_order' => (int) ($params['sort_order'] ?? 0),
            'status'    => 'active',
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        ]);
        return ToolResult::success(['id' => $wpdb->insert_id], 'FAQ 已添加');
    }

    /** 删除 FAQ */
    public static function deleteFaq(array $params, int $userId): ToolResult
    {
        global $wpdb;
        $id = (int) ($params['faq_id'] ?? 0);
        $wpdb->delete($wpdb->prefix . 'xzd_ai_faq', ['id' => $id]);
        return ToolResult::success(null, 'FAQ 已删除');
    }

    /** 重建知识库索引 */
    public static function rebuildIndex(array $params, int $userId): ToolResult
    {
        global $wpdb;
        $table = $wpdb->prefix . 'xzd_ai_knowledge';
        $chunkTable = $wpdb->prefix . 'xzd_ai_knowledge_chunks';

        // 事务保护：重建失败则回滚
        $wpdb->query('START TRANSACTION');
        try {
            // 清空旧分块
            $wpdb->query("DELETE FROM {$chunkTable} WHERE 1=1");

            // 重新对每条知识库内容分块
            $rows = $wpdb->get_results("SELECT id, title, content FROM {$table} WHERE status = 'active'", ARRAY_A);
            $totalChunks = 0;

            foreach ($rows as $row) {
                $chunks = self::chunkContent($row['content'], 500);
                $i = 0;
                foreach ($chunks as $chunk) {
                    $wpdb->insert($chunkTable, [
                        'knowledge_id' => $row['id'],
                        'chunk_index'  => $i++,
                        'content'      => $chunk,
                        'created_at'   => current_time('mysql'),
                    ]);
                    $totalChunks++;
                }
                $wpdb->update($table, ['chunk_count' => $i, 'updated_at' => current_time('mysql')], ['id' => $row['id']]);
            }

            $wpdb->query('COMMIT');
        } catch (\Throwable $e) {
            $wpdb->query('ROLLBACK');
            Logger::error('knowledge_rebuild_index', '索引重建失败: ' . $e->getMessage());
            return ToolResult::error('索引重建失败，数据已回滚');
        }

        return ToolResult::success(['documents' => count($rows), 'chunks' => $totalChunks], '索引重建完成');
    }

    private static function chunkContent(string $text, int $maxLen): array
    {
        $chunks = [];
        $len = mb_strlen($text);
        for ($i = 0; $i < $len; $i += $maxLen) {
            $chunks[] = mb_substr($text, $i, $maxLen);
        }
        return $chunks;
    }
}
