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

import json
from typing import List, Literal

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


# ===== 1) 定义结构 =====
RiskLevel = Literal["无风险", "高风险"]


class TimelinessResult(BaseModel):
    issue_point: str = Field(..., description="时效性判定类型")
    location: str = Field(..., description="审查的规范原文，与输入完全一致")
    suggestion: str = Field(..., description="建议")
    reason: str = Field(..., description="原因")
    risk_level: RiskLevel = Field(..., description='风险水平，只能是 "无风险" / "高风险"')


class TimelinessResults(BaseModel):
    items: List[TimelinessResult]


# ===== 2) SYSTEM Prompt =====
SYSTEM = """
你是【规范时效性判定助手】。

【任务】
根据规范匹配结果，判定每个审查规范的时效性问题类型。

【重要说明（必须严格遵守）】
- 如果有具体的文件将回答中的"XXX"替换为具体文件，如果没有具体文件回答内容中不要出现"XXX"字样

【输出要求】
- 为每个审查规范输出一个判定结果
- 确保输出数量与输入一致
- review_item 必须与输入完全一致
- 严格按照判定规则的优先级顺序进行判断
"""

HUMAN = """
请根据以下规范匹配结果，判定每个审查规范的时效性问题类型：

【判定规则（按优先级从高到低）】

**重要提示**：比较规范编号时，忽略括号格式差异（半角()和全角（）视为相同）。例如 "GB/T 5224-2014" 和 "GB/T 5224-2014" 是相同的编号。

1. **无参考规范**（无风险）
   - 条件：has_related_file = false
   - 原因：在参考规范库中完全找不到相关文件
   - 建议：当前引用未在参考规范库中发现，建议人工核实其有效性

2. **规范名称错误**（高风险）
   - 条件：name_mismatch = true（编号相同但名称不同）
   - 原因：规范编号正确，但规范名称错误。审查引用的是《错误名称》（编号），参考库中应为《正确名称》（编号）
   - 建议：建议将规范名称更正为《正确名称》（编号）
   - **重要**：必须从 correct_name 字段获取正确的规范名称

3. **规范编号错误**（高风险）
   - 条件：has_related_file = true 且 has_exact_match = false 且 name_mismatch 不存在或不为true
   - 原因：与参考文件XXX编号不一致（注意：仅当编号实质性不同时才算不一致，忽略括号格式差异）
   - 建议：建议核实并更正为参考库中的正确编号XXX

4. **规范编号正确**（无风险）
   - 条件：has_exact_match = true 且 exact_match_info 中状态为"现行"
   - 原因：与参考文件XXX名称编号一致，且文件状态为现行
   - 建议：引用规范为现行有效版本，无需调整

5. **引用已废止的规范**（高风险）
   - 条件：has_exact_match = true 且 exact_match_info 中状态为"废止" 且 same_name_current 为空
   - 原因：参考文件显示XXX已废止，且无明确替代版本
   - 建议：建议删除该引用或咨询最新替代规范

6. **引用已被替代的规范**（高风险）
   - 条件：has_exact_match = true 且 exact_match_info 中状态为"废止" 且 same_name_current 不为空
   - 原因：参考文件显示《规范名称》（原编号）已废止，存在现行版本《规范名称》（新编号）
   - 建议：建议更新为现行版本《规范名称》（新编号），并核实其适用性
   - **重要**：
     - 必须从 same_name_current 字段中提取具体的现行版本编号
     - 例如 same_name_current="《预应力混凝土用钢绞线》（GB/T 5224-2023）状态为现行"，则建议应为"建议更新为现行版本《预应力混凝土用钢绞线》（GB/T 5224-2023），并核实其适用性"
     - 严禁在建议中出现"XXX"字样，必须替换为实际的规范名称和编号

【规范匹配结果】
{match_results}

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

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

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

# ===== 5) LLM Client (通用模型底座) =====
model_client = generate_model_client


# ===== 6) 提取第一个 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 花括号未闭合")


# ===== 辅助函数：标准化文本 =====
def _normalize_text(text: str) -> str:
    """标准化文本，统一括号格式用于比较"""
    if not text:
        return text
    text = text.replace('（', '(').replace('）', ')')
    text = ' '.join(text.split())
    return text.strip()


def _extract_number_from_location(location: str) -> str:
    """从location字段提取规范编号"""
    import re
    match = re.search(r'[（(]([^)）]+)[）)]', location)
    return match.group(1).strip() if match else ""


# ===== 7) 核心方法 =====
async def determine_timeliness_issue(match_results: str) -> str:
    """
    根据规范匹配结果判定时效性问题类型
    
    Args:
        match_results: match_reference_files 的返回结果（JSON字符串）
        
    Returns:
        时效性判定结果的JSON字符串
    """
    format_instructions = parser.get_format_instructions()

    # 构建消息
    messages = prompt.format_messages(
        match_results=match_results,
        format_instructions=format_instructions
    )

    last_err = None

    for _ in range(2):
        try:
            # 使用 function_name 从 model_setting.yaml 加载模型配置（时效性审查）
            raw = await model_client.get_model_generate_invoke(
                trace_id="timeliness_determine",
                messages=messages,
                function_name="timeliness_review"
            )
            print(f"[时效性判定] 模型输出: {raw}...")
            data = extract_first_json(raw)
            findings = TimelinessResults.model_validate(data)
            result = [x.model_dump() for x in findings.items]
            
            # 【强制校正】处理LLM误判：如果判定为"规范编号错误"但编号实质相同，则校正为"规范编号正确"
            result = _correct_misjudgment(result, match_results)
            
            return json.dumps(result, ensure_ascii=False, indent=2)
        except (Exception, ValidationError, json.JSONDecodeError) as e:
            last_err = e

    raise RuntimeError(f"时效性判定失败：{last_err}") from last_err


def _correct_misjudgment(results: list, match_results: str) -> list:
    """
    校正LLM的误判：检查"规范编号错误"是否实际为编号相同（仅括号格式不同）
    """
    import json
    import re
    
    try:
        match_data = json.loads(match_results)
        match_items = match_data if isinstance(match_data, list) else match_data.get('items', [])
        
        for i, item in enumerate(results):
            issue_point = item.get('issue_point', '')
            location = item.get('location', '')
            reason = item.get('reason', '')
            
            # 只处理"规范编号错误"的情况
            if '规范编号错误' not in issue_point:
                continue
                
            # 从location提取审查项编号
            review_number = _extract_number_from_location(location)
            if not review_number:
                continue
            
            # 从reason或match_items中提取参考文件编号
            ref_number = ''
            reason_match = re.search(r'（([^）]+)）', reason)
            if reason_match:
                ref_number = reason_match.group(1).strip()
            
            # 如果编号实质相同（忽略括号差异），校正为"规范编号正确"
            if review_number and ref_number and _normalize_text(review_number) == _normalize_text(ref_number):
                print(f"[校正] 误判检测: '{location}' 编号实质相同，校正为'规范编号正确'")
                item['issue_point'] = '规范编号正确'
                item['suggestion'] = '引用规范为现行有效版本，无需调整'
                item['reason'] = f'与参考文件{location}名称编号一致，且文件状态为现行'
                item['risk_level'] = '无风险'
    except Exception as e:
        print(f"[校正] 校正过程出错: {e}")
    
    return results


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

    # 模拟 match_reference_files 的返回结果
    match_results = json.dumps([
        {
            "review_item": "《混凝土结构设计规范》（GB 50010-2010）",
            "has_related_file": True,
            "has_exact_match": True,
            "exact_match_info": "《混凝土结构设计规范》（GB 50010-2010）状态为现行",
            "same_name_current": "《混凝土结构设计规范》（GB 50010-2010）状态为现行"
        },
        {
            "review_item": "《建筑施工组织设计规范》（GB/T 50502-2015）",
            "has_related_file": True,
            "has_exact_match": True,
            "exact_match_info": "《建筑施工组织设计规范》（GB/T 50502-2015）状态为废止",
            "same_name_current": "《建筑施工组织设计规范》（GB/T 50502-2020）状态为现行"
        },
        {
            "review_item": "《建筑抗震设计规范》（GB 50011-2001）",
            "has_related_file": True,
            "has_exact_match": True,
            "exact_match_info": "《建筑抗震设计规范》（GB 50011-2001）状态为废止",
            "same_name_current": ""
        },
        {
            "review_item": "《城市道路工程设计规范》（CJJ 37-2012）",
            "has_related_file": False,
            "has_exact_match": False,
            "exact_match_info": "",
            "same_name_current": ""
        }
    ], ensure_ascii=False)

    result = asyncio.run(determine_timeliness_issue(match_results))
    print("\n时效性判定结果：")
    print(result)
