#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import asyncio
import time
import re
from typing import List, Optional, Tuple
from dataclasses import dataclass

from core.construction_review.component.reviewers.utils.reference_number_generator import generate_reference_number, validate_reference_number
from pydantic import BaseModel, Field, ValidationError
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
from foundation.ai.agent.generate.model_generate import generate_model_client
from foundation.observability.logger.loggering import review_logger as logger


# ===== 1) 定义结构 =====
class MatchResult(BaseModel):
    review_item: str = Field(..., description="审查的规范原文，与输入完全一致")
    has_related_file: bool = Field(..., description="是否有相关文件")
    has_exact_match: bool = Field(..., description="是否有名称编号都相同的文件")
    exact_match_info: str = Field("", description="名称编号相同的文件及状态，格式：《名称》（编号）状态为XXX，可为空")
    same_name_current: str = Field("", description="名称相同的现行文件，格式：《名称》（编号）状态为现行，可为空")


class MatchResults(BaseModel):
    items: List[MatchResult]


@dataclass
class ValidationMatchResult:
    """验证匹配结果"""
    review_item: str
    reference_candidates: List[str]  # 参考文件候选列表
    is_valid: bool  # 验证是否通过
    validated_number: Optional[str] = None  # 验证/生成的正确编号
    status: str = ""  # 状态（现行/废止）


# ===== 2) SYSTEM Prompt（用于初步匹配） =====
SYSTEM = """
/no_think
你是【规范文件匹配助手】。

【任务】
从参考规范库中查找每个审查规范的相关信息，用于后续的编号验证。

【输出要求】
- 为每个审查规范输出一个匹配结果
- 确保输出数量与输入的审查规范数量一致
- review_item 必须与输入完全一致
- exact_match_info 和 same_name_current 可以为空字符串
"""

HUMAN = """
请从参考规范库中查找每个审查规范的匹配信息：

【匹配规则】
1. **review_item**（审查的规范原文）
   - 必须与输入的审查规范完全一致，逐字复制

2. **has_related_file**（是否有相关文件）
   - 在参考规范库中找到名称相似或相关的文件，返回 true
   - 完全找不到任何相关文件，返回 false

3. **has_exact_match**（是否有名称编号都相同的文件）
   - 参考文件中的编号和文件名与审查规范完全匹配，返回 true
   - **重要**：比较时忽略括号格式差异（半角()和全角（）视为相同）
   - 例如：《规范》（GB 1234-2020）与《规范》（GB 1234-2020）视为完全匹配
   - 否则返回 false

4. **exact_match_info**（名称编号相同的文件及状态）
   - 如果 has_exact_match 为 true，返回该文件的完整信息
   - 格式：《规范名称》（规范编号）状态为XXX

5. **same_name_current**（名称相同的现行文件）
   - 在参考规范库中查找与审查规范名称相同且状态为"现行"的文件
   - 格式：《规范名称》（规范编号）状态为现行

【参考规范库】
{reference_text}

【审查规范】
{review_text}

【输出格式要求】
{format_instructions}
/no_think
"""

# ===== 3) Output Parser =====
parser = PydanticOutputParser(pydantic_object=MatchResults)

# ===== 4) Prompt =====
prompt = ChatPromptTemplate.from_messages([
    ("system", SYSTEM),
    ("human", HUMAN)
])

# ===== 5) LLM Client =====
model_client = generate_model_client

# ===== 6) 重试配置 =====
MAX_RETRIES = 5
BASE_DELAY = 2
MAX_DELAY = 30


def _is_retryable_error(error: Exception) -> bool:
    """判断是否为可重试的错误"""
    error_str = str(error).lower()
    retryable_codes = ['502', '503', '504', '429', 'timeout', 'connection', 'overload']
    return any(code in error_str for code in retryable_codes)


def _get_user_friendly_error(error: Exception) -> str:
    """将技术错误转换为用户友好的提示"""
    error_str = str(error).lower()

    if '502' in error_str or '503' in error_str or '504' in error_str:
        return "模型服务暂时不可用，请稍后重试"
    elif '429' in error_str or 'rate limit' in error_str:
        return "请求过于频繁，请稍后重试"
    elif 'timeout' in error_str:
        return "模型响应超时，请稍后重试"
    elif 'connection' in error_str:
        return "网络连接异常，请检查网络后重试"
    else:
        return f"规范匹配服务暂时不可用，请稍后重试"


# ===== 7) 提取第一个 JSON =====
def extract_first_json(text: str) -> dict:
    """从任意模型输出中提取第一个完整 JSON 对象 { ... }"""
    start = text.find("{")
    if start == -1:
        raise ValueError("未找到 JSON 起始 '{'")

    depth = 0
    for i in range(start, len(text)):
        ch = text[i]
        if ch == "{":
            depth += 1
        elif ch == "}":
            depth -= 1
            if depth == 0:
                return json.loads(text[start:i + 1])

    raise ValueError("JSON 花括号未闭合")


# ===== 8) 辅助函数：提取规范名称和编号 =====
def _extract_regulation_info(text: str) -> Tuple[str, Optional[str]]:
    """从文本中提取规范名称和编号"""
    # 提取书名号内的名称
    name_match = re.search(r'《([^《》]+)》', text)
    name = name_match.group(1) if name_match else text.strip()
    
    # 提取括号内的编号
    number_match = re.search(r'[（(]([^)）]+)[）)]', text)
    number = number_match.group(1).strip() if number_match else None
    
    return name, number


def _normalize_text(text: str) -> str:
    """
    标准化文本，统一括号格式用于比较
    将全角括号转换为半角括号，去除多余空格
    """
    if not text:
        return text
    # 全角括号转为半角括号
    text = text.replace('（', '(').replace('）', ')')
    # 统一书名号（中文书名号保持不变，但统一全角半角）
    text = text.replace('『', '《').replace('』', '》')
    text = text.replace('﹄', '《').replace('﹃', '》')
    # 去除多余空格
    text = ' '.join(text.split())
    return text.strip()


def _extract_core_number(number: str) -> str:
    """
    提取规范编号的核心部分（去掉年份）
    例如：JGJ 65-2013 -> JGJ65, GB/T 50010-2010 -> GB/T50010
    
    Args:
        number: 规范编号，如 "JGJ 65-2013"
        
    Returns:
        核心编号，如 "JGJ65"
    """
    if not number:
        return ""
    
    # 标准化：转大写、去空格
    normalized = number.upper().replace(' ', '')
    
    # 去掉年份部分（-YYYY 或 —YYYY）
    # 匹配末尾的年份 -4位数字 或 —4位数字 或 - 4位数字
    normalized = re.sub(r'[-—]\s*\d{4}$', '', normalized)
    
    return normalized


def _is_same_regulation_family(original_number: str, generated_number: str, threshold: int = 100) -> bool:
    """
    判断两个编号是否属于同一规范家族（核心部分相同或高度相似）
    
    Args:
        original_number: 原始编号
        generated_number: 生成的编号
        threshold: 数字差异阈值，默认100
        
    Returns:
        bool: 是否属于同一规范家族
    """
    original_core = _extract_core_number(original_number)
    generated_core = _extract_core_number(generated_number)
    
    if not original_core or not generated_core:
        return False
    
    # 如果核心部分完全相同，肯定是同一规范
    if original_core == generated_core:
        return True
    
    # 提取前缀（如 JGJ、GB/T 等）和数字部分
    def _split_core(core: str) -> tuple:
        """将核心编号拆分为前缀和数字部分"""
        match = re.match(r'^([A-Z]+(?:/[A-Z])?)(\d+(?:\.\d+)?)$', core)
        if match:
            return match.group(1), match.group(2)
        return core, ""
    
    orig_prefix, orig_num = _split_core(original_core)
    gen_prefix, gen_num = _split_core(generated_core)
    
    # 如果前缀相同但数字不同，可能是同一系列的不同规范
    # 例如 JGJ65 和 JGJ300 都是 JGJ 系列，但是完全不同的规范
    # 我们认为：如果前缀相同且数字相似（差值在一定范围内），才算同一规范家族
    if orig_prefix == gen_prefix and orig_num and gen_num:
        try:
            orig_val = float(orig_num)
            gen_val = float(gen_num)
            # 【关键阈值】如果数字差异达到或超过阈值，认为是完全不同的规范
            if abs(orig_val - gen_val) >= threshold:
                return False
            return True
        except ValueError:
            # 无法转换为数字，直接比较字符串
            pass
    
    return False


# ===== 9) 新流程：验证并生成正确编号 =====
async def validate_and_generate_number(
    review_item: str,
    reference_candidates: List[str]
) -> ValidationMatchResult:
    """
    新流程：
    1. 基于参考文件验证现有编号是否正确
    2. 如果验证失败，调用5模型生成正确编号
    
    Args:
        review_item: 审查的规范原文
        reference_candidates: 参考文件候选列表
    
    Returns:
        ValidationMatchResult 包含验证结果和正确编号
    """
    regulation_name, existing_number = _extract_regulation_info(review_item)
    
    # 构建参考文件信息
    reference_info = "\n".join(reference_candidates) if reference_candidates else "无参考文件"
    
    # 第一步：如果有现有编号，先验证是否正确
    if existing_number:
        logger.info(f"[时效性验证] 验证编号: 《{regulation_name}》 {existing_number}")
        
        # 先进行本地标准化比较：检查参考候选中是否有名称和编号都完全匹配（忽略括号差异）的
        normalized_existing_number = _normalize_text(existing_number)
        normalized_regulation_name = _normalize_text(regulation_name)
        for candidate in reference_candidates:
            # 从候选中提取名称和编号
            candidate_name, candidate_number = _extract_regulation_info(candidate)
            if (candidate_name and candidate_number and
                _normalize_text(candidate_name) == normalized_regulation_name and
                _normalize_text(candidate_number) == normalized_existing_number):
                logger.info(f"[时效性验证] 本地验证通过（名称和编号都匹配）: 《{regulation_name}》 {existing_number}")
                return ValidationMatchResult(
                    review_item=review_item,
                    reference_candidates=reference_candidates,
                    is_valid=True,
                    validated_number=existing_number,
                    status="验证通过"
                )

        # 【关键】检查是否有编号相同但名称不同的情况（规范名称错误）
        for candidate in reference_candidates:
            candidate_name, candidate_number = _extract_regulation_info(candidate)
            if (candidate_name and candidate_number and
                _normalize_text(candidate_number) == normalized_existing_number and
                _normalize_text(candidate_name) != normalized_regulation_name):
                logger.info(f"[时效性验证] 编号相同但名称不同: 《{regulation_name}》-> 应为《{candidate_name}》")
                return ValidationMatchResult(
                    review_item=review_item,
                    reference_candidates=reference_candidates,
                    is_valid=False,
                    validated_number=existing_number,
                    status="规范名称错误"
                )
        
        # 调用3模型验证
        validation = await validate_reference_number(
            regulation_name=regulation_name,
            existing_number=existing_number
        )
        
        if validation and validation.is_valid:
            logger.info(f"[时效性验证] 验证通过: 《{regulation_name}》 {existing_number}")
            return ValidationMatchResult(
                review_item=review_item,
                reference_candidates=reference_candidates,
                is_valid=True,
                validated_number=existing_number,
                status="验证通过"
            )
        else:
            logger.info(f"[时效性验证] 验证失败，需要生成新编号")
    
    # 第二步：验证失败或没有编号，调用5模型生成
    logger.info(f"[时效性验证] 生成编号: 《{regulation_name}》")
    
    existing_info = existing_number if existing_number else ""
    vote_result = await generate_reference_number(
        regulation_name=regulation_name,
        existing_info=existing_info
    )
    
    if vote_result and vote_result.confidence >= 0.5:
        logger.info(f"[时效性验证] 生成成功: {vote_result.generated_number}")
        return ValidationMatchResult(
            review_item=review_item,
            reference_candidates=reference_candidates,
            is_valid=False,  # 原始编号不正确
            validated_number=vote_result.generated_number,
            status="生成新编号"
        )
    else:
        logger.warning(f"[时效性验证] 生成失败")
        return ValidationMatchResult(
            review_item=review_item,
            reference_candidates=reference_candidates,
            is_valid=False,
            validated_number=None,
            status="无法确定"
        )


# ===== 10) 核心方法：匹配参考文件 =====
async def match_reference_files(reference_text: str, review_text: str) -> str:
    """
    从参考规范库中查找审查规范的匹配信息（带验证和生成新流程）

    Args:
        reference_text: 参考规范库内容
        review_text: 审查规范内容

    Returns:
        匹配结果的JSON字符串
    """
    # 第一步：使用LLM进行初步匹配
    format_instructions = parser.get_format_instructions()

    messages = prompt.format_messages(
        reference_text=reference_text,
        review_text=review_text,
        format_instructions=format_instructions
    )

    last_err = None
    raw_result = None

    for attempt in range(1, MAX_RETRIES + 1):
        try:
            # 使用 function_name 从 model_setting.yaml 加载模型配置（规范性审查）
            logger.info(f"[规范匹配] 第 {attempt}/{MAX_RETRIES} 次尝试调用模型")

            raw = await model_client.get_model_generate_invoke(
                trace_id="reference_match",
                messages=messages,
                function_name="reference_review"
            )

            logger.debug(f"[规范匹配] 模型输出: {raw[:200]}...")
            data = extract_first_json(raw)
            findings = MatchResults.model_validate(data)
            raw_result = [x.model_dump() for x in findings.items]

            logger.info(f"[规范匹配] 初步匹配成功，返回 {len(raw_result)} 个结果")
            break

        except Exception as e:
            last_err = e
            error_type = type(e).__name__
            logger.warning(f"[规范匹配] 第 {attempt} 次尝试失败: {error_type}: {str(e)[:100]}")

            if not _is_retryable_error(e):
                logger.error(f"[规范匹配] 遇到不可重试的错误: {error_type}")
                raise RuntimeError(_get_user_friendly_error(e)) from e

            if attempt < MAX_RETRIES:
                delay = min(BASE_DELAY * (2 ** (attempt - 1)), MAX_DELAY)
                import random
                jitter = random.uniform(0, 1)
                actual_delay = delay + jitter

                logger.info(f"[规范匹配] 等待 {actual_delay:.1f} 秒后重试...")
                await asyncio.sleep(actual_delay)
            else:
                logger.error(f"[规范匹配] 达到最大重试次数 {MAX_RETRIES}，最终失败")
                raise RuntimeError(_get_user_friendly_error(e)) from e

    if raw_result is None:
        raise RuntimeError(_get_user_friendly_error(last_err))

    # 第二步：对初步匹配结果进行验证/生成
    final_results = []
    
    # 解析 review_text
    try:
        review_items = json.loads(review_text) if isinstance(review_text, str) else review_text
        if not isinstance(review_items, list):
            review_items = [review_items]
    except json.JSONDecodeError:
        review_items = [review_text]

    # 解析 reference_text
    ref_candidates = reference_text if isinstance(reference_text, list) else [reference_text]

    for i, raw_item in enumerate(raw_result):
        review_item = raw_item.get("review_item", review_items[i] if i < len(review_items) else "")
        has_related = raw_item.get("has_related_file", False)
        has_exact = raw_item.get("has_exact_match", False)
        exact_info = raw_item.get("exact_match_info", "")
        same_name_current = raw_item.get("same_name_current", "")
        
        # 【校正逻辑】如果LLM判断has_exact_match=false，但本地比较发现名称和编号都相同（忽略括号差异），则校正为true
        if not has_exact and exact_info:
            review_name, review_number = _extract_regulation_info(review_item)
            exact_name, exact_number = _extract_regulation_info(exact_info)
            if (review_name and exact_name and
                _normalize_text(review_name) == _normalize_text(exact_name) and
                review_number and exact_number and
                _normalize_text(review_number) == _normalize_text(exact_number)):
                logger.info(f"[规范匹配校正] review_item='{review_item}' 名称和编号都相同，校正has_exact_match为true")
                has_exact = True
        
        # 【第一步】检查向量搜索候选中的匹配情况
        # ref_candidates 是 List[List[str]]，需要获取当前项对应的候选列表
        current_candidates = ref_candidates[i] if i < len(ref_candidates) else []
        review_name, review_number = _extract_regulation_info(review_item)

        if review_name and review_number and current_candidates:
            normalized_review_name = _normalize_text(review_name)
            normalized_review_number = _normalize_text(review_number)

            # 先检查是否有完全匹配（名称和编号都相同）
            for candidate in current_candidates:
                if isinstance(candidate, str):
                    candidate_name, candidate_number = _extract_regulation_info(candidate)
                    if (candidate_name and candidate_number and
                        _normalize_text(candidate_name) == normalized_review_name and
                        _normalize_text(candidate_number) == normalized_review_number):
                        # 向量库中找到精确匹配（名称和编号都相同）
                        logger.info(f"[规范匹配] 向量库中找到精确匹配: '{review_item}' -> '{candidate}'")
                        final_results.append({
                            "review_item": review_item,
                            "has_related_file": True,
                            "has_exact_match": True,
                            "exact_match_info": candidate,
                            "same_name_current": candidate
                        })
                        has_exact = True
                        break

            if has_exact:
                continue

            # 【关键】检查是否有编号相同但名称不同的情况（规范名称错误）
            for candidate in current_candidates:
                if isinstance(candidate, str):
                    candidate_name, candidate_number = _extract_regulation_info(candidate)
                    if (candidate_name and candidate_number and
                        _normalize_text(candidate_number) == normalized_review_number and
                        _normalize_text(candidate_name) != normalized_review_name):
                        # 编号相同但名称不同 - 判定为规范名称错误
                        logger.info(f"[规范匹配] 编号相同但名称不同: '{review_item}' -> '{candidate}'")
                        final_results.append({
                            "review_item": review_item,
                            "has_related_file": True,
                            "has_exact_match": False,
                            "exact_match_info": "",
                            "same_name_current": candidate,
                            "name_mismatch": True,  # 标记为名称不匹配
                            "correct_name": candidate_name  # 正确的名称
                        })
                        has_exact = True  # 标记为已处理，跳过后续逻辑
                        break

            if has_exact:
                continue
        
        # 如果有精确匹配（由LLM判断），直接接受
        if has_exact and exact_info:
            final_results.append({
                "review_item": review_item,
                "has_related_file": True,
                "has_exact_match": True,
                "exact_match_info": exact_info,
                "same_name_current": same_name_current
            })
            continue
        
        # 【第二步】如果没有精确匹配，但有相关文件，进行验证/生成
        # 使用当前项的候选列表（不是整个二维列表）
        if has_related or current_candidates:
            try:
                validation_result = await validate_and_generate_number(
                    review_item=review_item,
                    reference_candidates=current_candidates
                )
                
                if validation_result.validated_number:
                    # 【关键逻辑】检查生成的编号与原始编号是否属于同一规范家族
                    is_same_family = _is_same_regulation_family(
                        review_number or "",
                        validation_result.validated_number
                    )

                    # 【特殊处理】检查参考候选中是否有名称完全匹配的文件
                    # 如果名称相同但编号不同（如 GB 51-2001 vs GB 50021-2001），应接受生成的编号
                    has_same_name_in_candidates = False
                    for candidate in current_candidates:
                        if isinstance(candidate, str):
                            candidate_name, _ = _extract_regulation_info(candidate)
                            if (candidate_name and
                                _normalize_text(candidate_name) == _normalize_text(review_name)):
                                has_same_name_in_candidates = True
                                break

                    if not is_same_family and not has_same_name_in_candidates:
                        # 生成的编号与原始编号完全不同，且参考库中没有名称匹配的文件
                        # 说明参考库中找到的文件实际上不相关
                        logger.info(f"[规范匹配] '{review_item}' 生成的编号({validation_result.validated_number})"
                                  f"与原始编号({review_number})不属于同一规范家族，判定为无相关文件")
                        final_results.append({
                            "review_item": review_item,
                            "has_related_file": False,  # 【关键】标记为无相关文件
                            "has_exact_match": False,
                            "exact_match_info": "",
                            "same_name_current": ""
                        })
                        continue
                    
                    if validation_result.is_valid:
                        # 验证通过，原始编号正确
                        final_results.append({
                            "review_item": review_item,
                            "has_related_file": True,
                            "has_exact_match": True,
                            "exact_match_info": f"《{_extract_regulation_info(review_item)[0]}》（{validation_result.validated_number}）状态为现行",
                            "same_name_current": f"《{_extract_regulation_info(review_item)[0]}》（{validation_result.validated_number}）状态为现行"
                        })
                    else:
                        # 验证失败，生成了新编号
                        final_results.append({
                            "review_item": review_item,
                            "has_related_file": True,
                            "has_exact_match": False,
                            "exact_match_info": "",
                            "same_name_current": f"《{_extract_regulation_info(review_item)[0]}》（{validation_result.validated_number}）状态为现行"
                        })
                else:
                    # 无法确定，保持原结果
                    final_results.append({
                        "review_item": review_item,
                        "has_related_file": has_related,
                        "has_exact_match": False,
                        "exact_match_info": "",
                        "same_name_current": same_name_current
                    })
            except Exception as e:
                logger.error(f"[规范匹配] 验证/生成失败: {e}")
                # 保持原结果
                final_results.append(raw_item)
        else:
            # 无相关文件
            final_results.append({
                "review_item": review_item,
                "has_related_file": False,
                "has_exact_match": False,
                "exact_match_info": "",
                "same_name_current": ""
            })

    return json.dumps(final_results, ensure_ascii=False, indent=2)


# ===== 11) 示例 =====
if __name__ == "__main__":
    import asyncio

    reference_file = """
    《混凝土结构设计规范》（GB 50010-2010）状态为现行
    《混凝土结构设计规范》（GB 50010-2015）状态为废止
    《建筑施工组织设计规范》（GB/T 50502-2015）状态为废止
    《建筑施工组织设计规范》（GB/T 50502-2020）状态为现行
    《建筑抗震设计规范》（GB 50011-2001）状态为废止
    《建设工程安全生产管理条例》（国务院令第393号）状态为现行
    """

    review_file = """
    [
        "《混凝土结构设计规范》（GB 50010-2029）",
        "《建筑施工组织设计规范》（GB/T 50502-2015）",
        "《建筑抗震设计规范》（GB 50011-2001）",
        "《城市道路工程设计规范》（CJJ 37-2012）",
        "《建设工程安全生产管理条例》（国务院令第393号）"
    ]
    """

    result = asyncio.run(match_reference_files(reference_file, review_file))
    print("\n匹配结果（含验证和生成）：")
    print(result)
