"""
舷号搜索策略模块
提供三级搜索策略：
1. 指定舷号搜索
2. 同型号所有舷号搜索
3. 全局搜索

新增功能：
- 智能舷号映射：支持舷号、舰名的智能识别和映射
"""
from typing import List, Dict, Any, Optional, Tuple
import random
import re
import httpx
from utils.function_tracker import get_all_callbacks
from tools.ship_model_db import load_ship_model_mapping
from tools.rag_tools import rag_search
from modelsAPI.model_api import OpenaiAPI
from config import KB_TREE_CONFIG


_kb_name_to_id_cache: Optional[Dict[str, str]] = None


async def _load_kb_tree() -> Dict[str, str]:
    """
    从知识库树 API 加载所有知识库，返回 {名称: kb_id} 的映射
    递归遍历树结构，带缓存，只请求一次
    """
    global _kb_name_to_id_cache
    if _kb_name_to_id_cache is not None:
        return _kb_name_to_id_cache

    url = KB_TREE_CONFIG.get("url", "")
    if not url:
        print("[ship_number_search] KB_TREE_CONFIG.url 未配置")
        return {}

    headers = {
        "accept": "application/json",
        "x-user-id": "1",
        "x-user-name": "testuser",
        "x-role": "admin",
    }

    try:
        async with httpx.AsyncClient(timeout=30.0, verify=False) as client:
            response = await client.get(url, headers=headers)
            if response.status_code != 200:
                print(f"[ship_number_search] 知识库树 API 请求失败: HTTP {response.status_code}")
                return {}

            result = response.json()

            if isinstance(result, list):
                top_nodes = result
            elif isinstance(result, dict):
                kb_data = result.get("data") or result.get("list") or {}
                top_nodes = kb_data.get("children", []) if isinstance(kb_data, dict) else []
            else:
                top_nodes = []

            name_to_id = {}

            def _collect(nodes):
                for node in nodes:
                    kb_name = node.get("name") or ""
                    kb_id = str(node.get("id", "")) if node.get("id") is not None else ""
                    if kb_name and kb_id:
                        name_to_id[kb_name] = kb_id
                    children = node.get("children", [])
                    if children:
                        _collect(children)

            _collect(top_nodes)

            _kb_name_to_id_cache = name_to_id
            return name_to_id
    except Exception as e:
        print(f"[ship_number_search] 加载知识库树失败: {str(e)}")
        return {}


async def resolve_kb_id(ship_number: str) -> Optional[str]:
    """
    根据舷号从知识库树中查找对应的真实 kb_id

    匹配策略：在知识库名称中查找包含该舷号的条目
    例如 ship_number="163"，知识库名称为 "163舰资料"，则匹配成功

    Args:
        ship_number: 舷号（如 "163"）

    Returns:
        匹配到的 kb_id，未找到返回 None
    """
    if not ship_number:
        return None

    name_to_id = await _load_kb_tree()

    if ship_number in name_to_id:
        return name_to_id[ship_number]

    for kb_name, kb_id in name_to_id.items():
        if re.search(rf'(?<!\d){re.escape(ship_number)}(?!\d)', kb_name):
            return kb_id

    return None


async def get_ship_numbers_by_model(ship_number: str) -> list:
    """
    根据舷号获取同型号的所有舷号

    Args:
        ship_number: 舷号

    Returns:
        list: 同型号的所有舷号列表，如果未找到则返回只包含输入舷号的列表
    """
    ship_number = str(ship_number).strip()
    ships = await load_ship_model_mapping()
    target_model = None
    for ship in ships:
        if ship["hull_number"] == ship_number:
            target_model = ship["model_name"]
            break
    if target_model:
        return [s["hull_number"] for s in ships if s["model_name"] == target_model]
    return [ship_number]


async def _build_ship_mapping_candidates() -> Dict[str, Dict[str, Any]]:
    """
    构建舷号映射候选词库
    从每艘舰的记录中构建候选词，支持舷号和舰名匹配
    """
    candidates = {}

    ships = await load_ship_model_mapping()

    model_numbers: Dict[str, List[str]] = {}
    for ship in ships:
        model = ship["model_name"]
        if model not in model_numbers:
            model_numbers[model] = []
        model_numbers[model].append(ship["hull_number"])

    for ship in ships:
        model = ship["model_name"]
        hull_number = ship["hull_number"]
        ship_name = ship.get("ship_name", "")
        numbers = model_numbers.get(model, [])

        mapping_info = {
            "model": model,
            "numbers": numbers.copy()
        }

        candidates[hull_number] = {
            "type": "number",
            "ship_number": hull_number,
            **mapping_info
        }

        if ship_name:
            candidates[ship_name] = {
                "type": "name",
                "ship_number": hull_number,
                **mapping_info
            }

    return candidates


_SHIP_MAPPING_CANDIDATES = None

async def get_ship_mapping_candidates() -> Dict[str, Dict[str, Any]]:
    """
    获取舷号映射候选词库（带缓存）
    """
    global _SHIP_MAPPING_CANDIDATES
    if _SHIP_MAPPING_CANDIDATES is None:
        _SHIP_MAPPING_CANDIDATES = await _build_ship_mapping_candidates()
    return _SHIP_MAPPING_CANDIDATES

async def smart_ship_number_mapping(
    user_input: str,
    similarity_threshold: float = 0.8
) -> Tuple[Optional[str], Optional[str], Optional[List[str]], str]:
    """
    智能舷号映射函数
    将用户输入的舷号/舰名映射到准确的舷号或型号

    Returns:
        Tuple: (matched_ship_number, matched_model, all_numbers, match_type)
        - matched_ship_number: 匹配到的具体舷号（舰名也会映射到对应舷号）
        - matched_model: 匹配到的型号名称
        - all_numbers: 该型号下的所有舷号列表
        - match_type: 匹配类型 ("exact_number", "exact_name", "embedding", "none")
    """
    if not user_input or not user_input.strip():
        return None, None, None, "none"

    user_input = user_input.strip()
    candidates = await get_ship_mapping_candidates()

    if user_input in candidates:
        info = candidates[user_input]
        match_type = f"exact_{info['type']}"
        model = info["model"]
        numbers = info["numbers"]
        ship_number = info.get("ship_number")

        return ship_number, model, numbers, match_type

    try:
        candidate_texts = list(candidates.keys())
        query_embedding = await OpenaiAPI.get_embeddings_async(user_input)
        candidate_embeddings = await OpenaiAPI.get_embeddings_batch_async(candidate_texts)

        best_score = -1
        best_idx = -1

        for idx, emb in enumerate(candidate_embeddings):
            score = OpenaiAPI.cosine_similarity(query_embedding, emb)
            if score > best_score:
                best_score = score
                best_idx = idx

        if best_score >= similarity_threshold and best_idx >= 0:
            matched_text = candidate_texts[best_idx]
            info = candidates[matched_text]
            model = info["model"]
            numbers = info["numbers"]
            ship_number = info.get("ship_number")

            print(f"[智能映射] 用户输入 '{user_input}' -> 匹配到 '{matched_text}' (相似度: {best_score:.3f}, 类型: {info['type']})")

            return ship_number, model, numbers, "embedding"
    except Exception as e:
        print(f"[智能映射] Embedding 匹配失败: {str(e)}")

    return None, None, None, "none"

def send_search_status(title: str, details: str):
    """发送搜索状态事件"""
    title_options = [f"🔍 {title}", f"🔎 {title}", f"📚 {title}"]
    start_event = {
        "type": "function_execution",
        "title": random.choice(title_options),
        "details": details
    }
    for callback in get_all_callbacks():
        try:
            callback(start_event)
        except Exception:
            pass

def convert_rag_result(rag_result):
    """将 rag_search 的返回结果转换为原来的格式"""
    if not rag_result or not rag_result.get("success", False):
        return []
    source_citation = rag_result.get("sourceCitation", {})
    flat_results = []
    for resource, items in source_citation.items():
        for item in items:
            flat_results.append(item)
    return flat_results

async def search_with_ship_number_strategy(
    base_query: str,
    ship_number: str,
    top_k: int = 5,
    search_type: str = "fault"
) -> Tuple[List[Dict[str, Any]], str, str, str]:
    """
    使用舷号三级搜索策略进行RAG检索
    返回: (rag_results, matched_kb_name, matched_kb_id, matched_ship_number)
    matched_ship_number: 映射后的舷号，未映射到具体舷号时为空字符串
    """
    rag_results = []
    matched_kb_name = ""
    matched_kb_id = ""

    matched_ship_number = None
    matched_model = None
    model_all_numbers = None
    match_type = "none"

    if ship_number and ship_number.strip():
        send_search_status(
            "正在智能识别",
            f"识别 '{ship_number}' 的含义（舷号/舰名）..."
        )

        matched_ship_number, matched_model, model_all_numbers, match_type = await smart_ship_number_mapping(ship_number)

        if match_type != "none":
            print(f"[智能映射结果] 输入='{ship_number}' -> 舷号={matched_ship_number}, 型号={matched_model}, 所有舷号={model_all_numbers}, 匹配类型={match_type}")
        else:
            print(f"[智能映射结果] 输入='{ship_number}' -> 未找到匹配，将使用原始值进行检索")

    if match_type in ["exact_number", "exact_name", "embedding"] and matched_ship_number:
        real_kb_id = await resolve_kb_id(matched_ship_number)
        if real_kb_id:
            send_search_status(
                "正在指定舷号检索",
                f"使用舷号 {matched_ship_number} 进行检索..."
            )

            rag_query = f"{base_query}"

            try:
                rag_result_raw = await rag_search(rag_query, top_k=top_k, kb_id=real_kb_id)
                rag_results = convert_rag_result(rag_result_raw)

                if rag_results:
                    print(f"步骤1 - 舷号 {matched_ship_number} 检索完成，返回 {len(rag_results)} 条结果")
                    for item in rag_results:
                        if isinstance(item, dict):
                            found_kb_name = item.get("kb_name", "")
                            found_kb_id = item.get("kb_id", "")
                            if found_kb_name and not matched_kb_name:
                                matched_kb_name = str(found_kb_name).strip()
                            if found_kb_id and not matched_kb_id:
                                matched_kb_id = str(found_kb_id).strip()
                    return rag_results, matched_kb_name, matched_kb_id, matched_ship_number or ""
            except Exception as e:
                print(f"步骤1 - 舷号 {matched_ship_number} 检索失败: {str(e)}")
        else:
            print(f"步骤1 - 舷号 {matched_ship_number} 无对应知识库，跳过指定舷号检索")

    if match_type in ["exact_number", "exact_name", "embedding"] and model_all_numbers:
        send_search_status(
            "正在型号检索",
            f"识别为型号 '{matched_model}'，使用该型号所有舷号 {', '.join(model_all_numbers)} 进行检索..."
        )

        for num in model_all_numbers:
            rag_query = f"{base_query}"

            try:
                real_kb_id = await resolve_kb_id(num)
                if not real_kb_id:
                    continue
                rag_result_raw = await rag_search(rag_query, top_k=top_k, kb_id=real_kb_id)
                current_results = convert_rag_result(rag_result_raw)

                if current_results:
                    rag_results.extend(current_results)
                    print(f"步骤2 - 舷号 {num} 检索完成，返回 {len(current_results)} 条结果")

                    if not matched_kb_name:
                        for item in current_results:
                            if isinstance(item, dict):
                                found_kb_name = item.get("kb_name", "")
                                found_kb_id = item.get("kb_id", "")
                                if found_kb_name and not matched_kb_name:
                                    matched_kb_name = str(found_kb_name).strip()
                                if found_kb_id and not matched_kb_id:
                                    matched_kb_id = str(found_kb_id).strip()
            except Exception as e:
                print(f"步骤2 - 舷号 {num} 检索失败: {str(e)}")

        if rag_results:
            return rag_results, matched_kb_name, matched_kb_id, matched_ship_number or ""

    if ship_number and ship_number.strip() and match_type == "none":
        real_kb_id = await resolve_kb_id(ship_number)
        if real_kb_id:
            send_search_status(
                "正在指定舰名检索",
                f"使用舰名 {ship_number} 进行检索..."
            )

            rag_query = f"{base_query}"

            try:
                rag_result_raw = await rag_search(rag_query, top_k=top_k, kb_id=real_kb_id)
                rag_results = convert_rag_result(rag_result_raw)

                if rag_results:
                    print(f"步骤1 - 舷号 {ship_number} 检索完成，返回 {len(rag_results)} 条结果")
                    for item in rag_results:
                        if isinstance(item, dict):
                            found_kb_name = item.get("kb_name", "")
                            found_kb_id = item.get("kb_id", "")
                            if found_kb_name and not matched_kb_name:
                                matched_kb_name = str(found_kb_name).strip()
                            if found_kb_id and not matched_kb_id:
                                matched_kb_id = str(found_kb_id).strip()
                    return rag_results, matched_kb_name, matched_kb_id, matched_ship_number or ""
            except Exception as e:
                print(f"步骤1 - 舷号 {ship_number} 检索失败: {str(e)}")
        else:
            print(f"步骤1 - 舷号 {ship_number} 无对应知识库，跳过指定舰名检索")

        model_ship_numbers = await get_ship_numbers_by_model(ship_number)
        if len(model_ship_numbers) > 1 or (len(model_ship_numbers) == 1 and ship_number not in model_ship_numbers):
            send_search_status(
                "正在同型号检索",
                f"使用同型号舷号 {', '.join(model_ship_numbers)} 进行检索..."
            )

            for num in model_ship_numbers:
                if num == ship_number:
                    continue

                rag_query = f"{base_query}"

                try:
                    real_kb_id = await resolve_kb_id(num)
                    if not real_kb_id:
                        continue
                    rag_result_raw = await rag_search(rag_query, top_k=top_k, kb_id=real_kb_id)
                    current_results = convert_rag_result(rag_result_raw)

                    if current_results:
                        rag_results.extend(current_results)
                        print(f"步骤2 - 舷号 {num} 检索完成，返回 {len(current_results)} 条结果")

                        if not matched_kb_name:
                            for item in current_results:
                                if isinstance(item, dict):
                                    found_kb_name = item.get("kb_name", "")
                                    found_kb_id = item.get("kb_id", "")
                                    if found_kb_name and not matched_kb_name:
                                        matched_kb_name = str(found_kb_name).strip()
                                    if found_kb_id and not matched_kb_id:
                                        matched_kb_id = str(found_kb_id).strip()
                except Exception as e:
                    print(f"步骤2 - 舷号 {num} 检索失败: {str(e)}")

            if rag_results:
                return rag_results, matched_kb_name, matched_kb_id, matched_ship_number or ""

    send_search_status(
        "正在全局检索",
        "未找到舷号相关资料，进行全局检索..."
    )

    try:
        rag_result_raw = await rag_search(base_query, top_k=top_k)
        rag_results = convert_rag_result(rag_result_raw)

        if rag_results:
            print(f"步骤3 - 全局检索完成，返回 {len(rag_results)} 条结果")
            for item in rag_results:
                if isinstance(item, dict):
                    found_kb_name = item.get("kb_name", "")
                    found_kb_id = item.get("kb_id", "")
                    if found_kb_name and not matched_kb_name:
                        matched_kb_name = str(found_kb_name).strip()
                    if found_kb_id and not matched_kb_id:
                        matched_kb_id = str(found_kb_id).strip()
    except Exception as e:
        print(f"步骤3 - 全局检索失败: {str(e)}")
        rag_results = []

    return rag_results, matched_kb_name, matched_kb_id, matched_ship_number or ""

