#!/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 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
   - 否则返回 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


# ===== 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}")
        
        # 调用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:
            logger.info(f"[规范匹配] 第 {attempt}/{MAX_RETRIES} 次尝试调用模型 qwen3_30b")

            raw = await model_client.get_model_generate_invoke(
                trace_id="reference_match",
                messages=messages,
                model_name="qwen3_30b"
            )

            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", "")
        
        # 如果有精确匹配，直接接受
        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 ref_candidates:
            try:
                validation_result = await validate_and_generate_number(
                    review_item=review_item,
                    reference_candidates=ref_candidates
                )
                
                if validation_result.validated_number:
                    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)
