# -*- coding: utf-8 -*-
"""
search_engine.py — 问题知识库搜索引擎

功能：
  基于关键词匹配 + TF-IDF 相似度，从问题知识库中检索最相关的历史案例。

用法：
  from search_engine import SearchEngine

  engine = SearchEngine()
  results = engine.search("门口屏 MQTT 连接失败", top_k=5)
"""

import os
import re
import json
import math
from pathlib import Path
from collections import defaultdict, Counter

# ============================================================
# 配置
# ============================================================

SCRIPT_DIR = Path(__file__).resolve().parent  # .../web
DATA_DIR = SCRIPT_DIR.parent  # 部署时即 /opt/troubleshoot

# 搜索索引路径（按优先级查找：部署环境 → 开发环境）
SEARCH_INDEX_PATHS = [
    DATA_DIR / "搜索索引.json",                                    # 部署环境（/opt/troubleshoot/）
    Path("E:/github/ubains-module-test/develop/Docs/PRD/问题知识库/搜索索引.json"),  # 开发环境绝对路径
    SCRIPT_DIR.parent.parent.parent.parent / "Docs" / "PRD" / "问题知识库" / "搜索索引.json",  # 相对路径
]


def find_search_index():
    """按优先级查找搜索索引文件"""
    for path in SEARCH_INDEX_PATHS:
        if path.exists():
            print(f"[搜索引擎] 找到索引文件：{path}")
            return path
    raise FileNotFoundError(f"搜索索引不存在，已查找路径：{SEARCH_INDEX_PATHS}")

# 停用词（过滤无意义的词）
STOPWORDS = {
    '的', '了', '是', '在', '有', '我', '不', '和', '与', '或',
    '这个', '那个', '怎么', '为什么', '哪里', '什么', '可以',
    '应该', '需要', '请', '帮', '一下', '吗', '呢', '啊', '吧',
    'the', 'a', 'an', 'is', 'are', 'was', 'were', 'to', 'of', 'in',
    'and', 'or', 'not', 'for', 'on', 'at', 'by', 'be', 'it', 'its',
    'as', 'if', 'then', 'than', 'that', 'this', 'these', 'those',
}


def load_search_index():
    """加载搜索索引"""
    index_file = find_search_index()
    with open(index_file, 'r', encoding='utf-8') as f:
        return json.load(f)


def tokenize(text):
    """中文分词（简单实现：按字切分 + 关键词提取）"""
    # 提取中文词组（2-4字）
    cn_words = re.findall(r'[一-龥]{2,4}', text)
    # 提取英文单词
    en_words = re.findall(r'[a-zA-Z]{2,}', text.lower())
    # 提取数字
    numbers = re.findall(r'\d+', text)

    tokens = cn_words + en_words + numbers
    # 过滤停用词
    tokens = [t for t in tokens if t not in STOPWORDS]

    return tokens


def compute_tf(tokens):
    """计算词频 (TF)"""
    tf = Counter(tokens)
    total = len(tokens) if tokens else 1
    return {word: count / total for word, count in tf.items()}


def compute_idf(documents):
    """计算逆文档频率 (IDF)"""
    N = len(documents)
    df = defaultdict(int)

    for doc in documents:
        unique_tokens = set(doc)
        for token in unique_tokens:
            df[token] += 1

    idf = {}
    for token, count in df.items():
        # 平滑处理：避免除零
        idf[token] = math.log((N + 1) / (count + 1)) + 1

    return idf


def compute_tfidf(tf, idf):
    """计算 TF-IDF 向量"""
    return {word: tf_val * idf.get(word, 1) for word, tf_val in tf.items()}


def cosine_similarity(vec1, vec2):
    """计算余弦相似度"""
    # 找到共同词
    common_words = set(vec1.keys()) & set(vec2.keys())
    if not common_words:
        return 0.0

    # 计算点积
    dot_product = sum(vec1[w] * vec2[w] for w in common_words)

    # 计算模长
    norm1 = math.sqrt(sum(v ** 2 for v in vec1.values()))
    norm2 = math.sqrt(sum(v ** 2 for v in vec2.values()))

    if norm1 == 0 or norm2 == 0:
        return 0.0

    return dot_product / (norm1 * norm2)


class SearchEngine:
    """问题知识库搜索引擎"""

    def __init__(self):
        """初始化：加载索引并构建 TF-IDF 模型"""
        self.index = load_search_index()
        self.records = self.index.get('records', [])

        # 预处理：为每条记录构建 TF-IDF 向量
        self.doc_tokens = []
        self.doc_vectors = []
        self.idf = {}

        if self.records:
            # 对所有记录进行分词
            self.doc_tokens = [
                tokenize(r.get('full_text', '') + ' ' + r.get('title', ''))
                for r in self.records
            ]

            # 计算 IDF
            self.idf = compute_idf(self.doc_tokens)

            # 预计算每条记录的 TF-IDF 向量
            self.doc_vectors = [
                compute_tfidf(compute_tf(tokens), self.idf)
                for tokens in self.doc_tokens
            ]

        print(f"[搜索引擎] 初始化完成：{len(self.records)} 条记录")

    def search(self, query, top_k=5, project_filter=None, category_filter=None):
        """
        搜索相似问题记录。

        参数：
          query: 搜索查询字符串
          top_k: 返回结果数量
          project_filter: 项目名称过滤（可选）
          category_filter: 分类过滤（可选）

        返回：
          [
            {
              'rank': 1,
              'score': 0.85,
              'record': {...}  # 原始记录
            },
            ...
          ]
        """
        if not self.records:
            return []

        # 对查询进行分词和向量化
        query_tokens = tokenize(query)
        query_tf = compute_tf(query_tokens)
        query_vector = compute_tfidf(query_tf, self.idf)

        # 计算与每条记录的相似度
        scores = []
        for i, doc_vector in enumerate(self.doc_vectors):
            # 应用过滤器
            record = self.records[i]

            if project_filter:
                if record.get('project', '').lower() != project_filter.lower():
                    continue

            if category_filter:
                if category_filter not in record.get('category', []):
                    continue

            # 计算相似度
            similarity = cosine_similarity(query_vector, doc_vector)

            # 关键词匹配加分
            matched_keywords = 0
            for kw in record.get('keywords', []):
                if kw.lower() in query.lower():
                    matched_keywords += 1
            keyword_bonus = matched_keywords * 0.05  # 每匹配一个关键词加 5%

            # 项目匹配加分
            project_bonus = 0
            if project_filter and record.get('project', '').lower() == project_filter.lower():
                project_bonus = 0.1  # 同项目加 10%

            # 综合得分
            final_score = min(1.0, similarity + keyword_bonus + project_bonus)

            if final_score > 0:
                scores.append((i, final_score))

        # 按得分排序
        scores.sort(key=lambda x: x[1], reverse=True)

        # 返回 Top K
        results = []
        for rank, (idx, score) in enumerate(scores[:top_k], start=1):
            results.append({
                'rank': rank,
                'score': round(score, 3),
                'record': self.records[idx],
            })

        return results

    def get_projects(self):
        """获取所有项目名称列表"""
        return self.index.get('project_names', [])

    def get_categories(self):
        """获取所有分类列表"""
        return list(self.index.get('categories', {}).keys())


# 模块级单例
_engine = None


def get_engine():
    """获取搜索引擎单例"""
    global _engine
    if _engine is None:
        _engine = SearchEngine()
    return _engine


if __name__ == "__main__":
    # 测试
    engine = SearchEngine()

    test_queries = [
        "门口屏 MQTT 连接失败",
        "redis 连接失败",
        "预定系统启动失败",
        "配置文件不生效",
    ]

    for q in test_queries:
        print(f"\n{'='*50}")
        print(f"查询：{q}")
        print(f"{'='*50}")

        results = engine.search(q, top_k=3)
        for r in results:
            rec = r['record']
            print(f"[{r['rank']}] 得分：{r['score']}")
            print(f"    项目：{rec.get('project', '无')}")
            print(f"    标题：{rec.get('title', '无')}")
            print(f"    分类：{rec.get('category', [])}")
