#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Embedding 客户端

统一通过 model_handler 获取 Embedding 模型，配置从 config.ini 读取
"""

import math
import re
from typing import List, Optional, Tuple

from .config import EMBEDDING_SIMILARITY_THRESHOLD
from foundation.ai.models.model_handler import model_handler
from foundation.observability.logger.loggering import review_logger as logger


class EmbeddingClient:
    """Embedding模型客户端，用于计算文本相似度"""

    def __init__(self):
        """初始化 Embedding 客户端，通过 model_handler 获取模型"""
        # 统一通过 model_handler 获取 Embedding 模型
        self._embedding_model = None

    @property
    def embedding_model(self):
        """懒加载获取 Embedding 模型实例"""
        if self._embedding_model is None:
            self._embedding_model = model_handler.get_embedding_model()
        return self._embedding_model

    async def get_embedding(self, text: str) -> Optional[List[float]]:
        """获取文本的embedding向量"""
        try:
            # 使用 model_handler 提供的 embedding 模型
            embedding = self.embedding_model.embed_query(text)
            return embedding
        except Exception as e:
            logger.error(f"Embedding API调用失败: {e}")
            return None

    async def get_embeddings_batch(self, texts: List[str]) -> List[Optional[List[float]]]:
        """批量获取文本的embedding向量"""
        try:
            # 使用 model_handler 提供的 embedding 模型
            embeddings = self.embedding_model.embed_documents(texts)
            return embeddings
        except Exception as e:
            logger.error(f"Embedding API批量调用失败: {e}")
            return [None] * len(texts)

    def cosine_similarity(self, vec1: List[float], vec2: List[float]) -> float:
        """计算两个向量的余弦相似度"""
        if not vec1 or not vec2 or len(vec1) != len(vec2):
            return 0.0

        dot_product = sum(a * b for a, b in zip(vec1, vec2))
        norm1 = math.sqrt(sum(a * a for a in vec1))
        norm2 = math.sqrt(sum(b * b for b in vec2))

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

        return dot_product / (norm1 * norm2)

    def _clean_section_name(self, section_name: str) -> str:
        """清理section名称，去除序号等前缀

        例如:
        - "一）编制依据" -> "编制依据"
        - "二） 技术保证措施" -> "技术保证措施"
        - "1. 施工计划" -> "施工计划"
        - "(1) 工艺流程" -> "工艺流程"
        """
        cleaned = section_name.strip()

        # 去除开头的序号模式:
        # 1. 中文数字+）或中文数字+、 如 "一）"、"二、"
        # 2. 阿拉伯数字+. 或阿拉伯数字+）如 "1.", "2）"
        # 3. 括号数字如 "(1)", "（一）"
        patterns = [
            r'^[一二三四五六七八九十百千]+[）\\)、\\.\\s]+',  # 中文数字+标点
            r'^\\d+[\\.\\)\\）、\\s]+',  # 阿拉伯数字+标点
            r'^[（(]\\d+[)）][\\s\\.]*',  # 括号数字
            r'^[（(][一二三四五六七八九十][)）][\\s\\.]*',  # 括号中文数字
        ]

        for pattern in patterns:
            cleaned = re.sub(pattern, '', cleaned)

        return cleaned.strip()

    async def check_similarity(
        self,
        section_name: str,
        section_content: str,
        second_category_name: str,
        second_category_raw_content: str = ""
    ) -> Tuple[bool, float]:
        """
        检查待审查内容与二级分类标准的相似度

        比较:
        - 左侧: section的实际内容（待审查的施工方案内容）
        - 右侧: second_raw_content（来自construction_plan_standards.csv的标准定义）

        返回: (is_similar, similarity_score)
        - is_similar: 是否相似（相似度 > 阈值 或标题完全匹配）
        - similarity_score: 相似度分数 (0-1)
        """
        # 步骤1: 先判断标题是否匹配
        # 清理文本进行比较（去除序号等前缀）
        cleaned_section_name = self._clean_section_name(section_name).lower()
        cleaned_second_name = second_category_name.strip().lower()

        # 标题直接相等检查（清理后的）
        if cleaned_section_name == cleaned_second_name:
            # 标题匹配，继续用embedding比较内容相似度
            pass
        else:
            # 标题不匹配，检查是否包含关系
            if cleaned_second_name in cleaned_section_name or cleaned_section_name in cleaned_second_name:
                # 要求包含的部分至少4个字符，避免短词误判
                if len(cleaned_second_name) >= 4 or len(cleaned_section_name) >= 4:
                    # 标题部分匹配，继续用embedding比较内容
                    pass
                else:
                    # 标题不匹配且太短，直接返回不相似
                    return False, 0.0
            else:
                # 标题完全不匹配，直接返回不相似
                return False, 0.0

        # 步骤2: 使用embedding计算内容相似度
        # 左侧: section的实际内容（待审查的施工方案实际内容）
        # 右侧: second_raw_content（该second_name的标准定义）
        section_text = section_content[:800]  # 取前800字符的实际内容
        category_text = second_category_raw_content[:800] if second_category_raw_content else second_category_name

        # 获取embedding
        embeddings = await self.get_embeddings_batch([section_text, category_text])

        if embeddings[0] is None or embeddings[1] is None:
            # embedding获取失败，保守起见返回不相似
            return False, 0.0

        # 计算相似度
        similarity = self.cosine_similarity(embeddings[0], embeddings[1])

        # 判断结果
        is_similar = similarity >= EMBEDDING_SIMILARITY_THRESHOLD

        return is_similar, similarity
