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

import json
import asyncio
import re
from typing import List, Literal, Optional

from pydantic import BaseModel, Field, ValidationError
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
from langchain_openai import ChatOpenAI

# 导入多模型编号生成器和验证器
try:
    from .reference_number_generator import (
        validate_reference_number,
        generate_reference_number,
        ModelVoteResult,
        ValidationResult,
        _extract_reference_number
    )
except ImportError:
    from reference_number_generator import (
        validate_reference_number,
        generate_reference_number,
        ModelVoteResult,
        ValidationResult,
        _extract_reference_number
    )


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


class PunctuationIssueResult(BaseModel):
    issue_point: str = Field(..., description="问题点描述")
    location: str = Field(..., description="审查内容，与输入完全一致")
    suggestion: str = Field(..., description="修改建议，可执行动作")
    reason: str = Field(..., description="问题的原因分析")
    risk_level: RiskLevel = Field(..., description='风险水平，只能是 "无风险" / "中风险"')


class PunctuationIssueResults(BaseModel):
    items: List[PunctuationIssueResult]


# ===== 2) SYSTEM Prompt =====
SYSTEM = """
你是【编制依据格式问题分析专家】。

【任务】
根据标点符号检查结果，生成详细的问题分析报告。

【重要说明（必须严格遵守）】
- location 字段必须与输入的 original_text 完全一致（一字不差）
- 根据 title_mark_status 和 bracket_status 的值判断问题类型
- 提供具体的修改建议和原因分析

【编号说明】
编号可以是多种形式：
- 标准编号：GB50010-2010、GB/T50502等
- 法规编号：令第393号、第37号令、国务院令第493号等
- 只要编号与规范名称匹配且真实存在，即为有效

【输出要求】
- 为每个检查结果输出一个详细的问题分析
- 确保输出数量与输入一致
- location 必须与 original_text 完全一致
- 严格按照判定规则生成内容
"""

HUMAN = """
请根据以下标点符号检查结果，生成详细的问题分析报告：

【判定规则】

首先明确 bracket_status 的三种状态含义：
- bracket_status = true：有编号且被括号完整包裹
- bracket_status = false：有编号但未被括号包裹
- bracket_status = null：没有编号

【问题类型判定】

当 title_mark_status = true 且 bracket_status = true：
- issue_point：编制依据格式正确
- reason：规范名称和编号的标点符号使用规范
- suggestion：无
- risk_level：无风险

当 title_mark_status = true 且 bracket_status = null：
- issue_point：编制依据格式错误
- reason：缺少规范编号
- suggestion：补充规范编号，正确格式：《规范名称》（编号）
- risk_level：中风险

当 title_mark_status = true 且 invalid_number_format = true：
- issue_point：编制依据格式错误
- reason：格式错误！当前编号为纯数字
- suggestion：规范编号应为英文加数字或中文加数字，而不是纯数字
- risk_level：中风险

当 title_mark_status = true 且 bracket_status = false 且 invalid_number_format 不存在或为 false：
- issue_point：编制依据格式错误
- reason：格式错误！正确格式：《规范名称》（编号）
- suggestion：将规范编号用括号包裹，正确格式：《规范名称》（编号）
- risk_level：中风险

当 title_mark_status != true 时：
- issue_point：编制依据格式错误
- reason：格式错误！正确格式：《规范名称》（编号）
- suggestion：将规范名称用书名号《》包裹，正确格式：《规范名称》（编号）
- risk_level：中风险

【标点符号检查结果】
{check_results}

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

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

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

# ===== 5) LLM =====
llm = ChatOpenAI(
    model="qwen3-30b",
    base_url="http://192.168.91.253:8003/v1",
    api_key="sk-123456",
    temperature=0,
)


# ===== 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 花括号未闭合")


# ===== 6.5) 辅助函数：提取规范名称 =====
def _extract_regulation_name(text: str) -> str:
    """从原文中提取规范名称（书名号内的内容）"""
    match = re.search(r'《([^《》]+)》', text)
    if match:
        return match.group(1).strip()
    return text.strip()


# ===== 6.6) 辅助函数：提取现有编号 =====
def _extract_existing_number(text: str) -> Optional[str]:
    """
    从原文中提取现有编号（智能处理嵌套括号）n    
    规则：
    1. 找到书名号后的第一个配对括号
    2. 验证括号内容是否符合编号格式（英文+数字 或 中文+数字）
    3. 返回有效的编号内容
    """
    # 统一括号类型
    text_normalized = text.replace('(', '（').replace(')', '）')
    
    # 找到书名号后的文本
    last_title_end = max(text_normalized.rfind('《'), text_normalized.rfind('》'))
    if last_title_end == -1:
        return None
    
    text_after_title = text_normalized[last_title_end + 1:]
    
    # 使用栈找到第一个配对的括号
    stack = []
    for i, char in enumerate(text_after_title):
        if char == '（':
            stack.append(i)
        elif char == '）':
            if stack:
                start = stack.pop()
                if len(stack) == 0:  # 最外层配对
                    content = text_after_title[start + 1:i].strip()
                    # 验证是否为有效编号格式
                    if _is_valid_number_format(content):
                        return content
    
    # 如果没有找到有效的配对括号，尝试提取第一个左括号后的内容
    first_left = text_after_title.find('（')
    if first_left != -1:
        # 找到第一个右括号的位置，限制提取范围
        first_right = text_after_title.find('）', first_left + 1)
        next_left = text_after_title.find('（', first_left + 1)
        
        # 确定提取的结束位置：右括号或下一个左括号，取先出现的
        end_pos = None
        if first_right != -1 and next_left != -1:
            end_pos = min(first_right, next_left)
        elif first_right != -1:
            end_pos = first_right
        elif next_left != -1:
            end_pos = next_left
        
        if end_pos is not None:
            content = text_after_title[first_left + 1:end_pos].strip()
        else:
            content = text_after_title[first_left + 1:].strip()
            
        if _is_valid_number_format(content):
            return content
    
    return None


def _is_valid_number_format(content: str) -> bool:
    """
    验证内容是否符合编号格式：
    1. 接受版本号格式（如"2024版"、"2023年"）- 企业内部文件常用
    2. 接受标准编号格式（英文+数字，如GB50010-2010）
    3. 接受法规编号格式（中文+数字，如令第393号）
    """
    if not content:
        return False
    
    # 检查是否包含英文字母
    has_english = bool(re.search(r'[a-zA-Z]', content))
    # 检查是否包含中文字符
    has_chinese = bool(re.search(r'[\u4e00-\u9fff]', content))
    # 检查是否包含数字
    has_digit = bool(re.search(r'\d', content))
    
    # 情况1: 标准编号格式（英文+数字）
    if has_english and has_digit:
        return True
    
    # 情况2: 法规编号格式（中文+数字）
    if has_chinese and has_digit:
        chinese_pattern = r'^[\d\s]*[\u4e00-\u9fff]+[\d\s]*[\u4e00-\u9fff]*$'
        if re.search(chinese_pattern, content):
            return True
    
    # 情况3: 版本号格式（年份+版/年，企业内部文件常用）
    version_pattern = r'^\d{4}\s*[版年修订]+[版本]?$'
    if re.match(version_pattern, content):
        return True
    
    # 情况4: 版本号格式（V/VERSION + 数字）
    v_version_pattern = r'^[vV][\d\.]+|[Vv]ersion\s*[\d\.]+$'
    if re.match(v_version_pattern, content):
        return True
    
    # 有效编号：必须有数字，且满足上述任一格式
    return has_digit and (has_english or has_chinese)


# ===== 6.7) 辅助函数：生成建议（新流程） =====
async def _generate_suggestion_with_validation(
    original_text: str,
    issue_type: str,
    base_suggestion: str
) -> str:
    """
    使用新流程生成编号建议：
    1. 首先验证现有编号是否正确
    2. 如果验证通过，接受该编号
    3. 如果验证失败，调用5个模型生成正确编号
    """
    try:
        regulation_name = _extract_regulation_name(original_text)
        existing_number = _extract_existing_number(original_text)
        
        # 第一步：如果存在编号，先验证是否正确
        if existing_number and existing_number.strip():
            validation_result = await validate_reference_number(
                regulation_name=regulation_name,
                existing_number=existing_number
            )
            
            if validation_result and validation_result.is_valid:
                # 验证通过，接受该编号（不再计较是否为字母+数字格式）
                if issue_type == "missing_bracket":
                    return f"将规范编号用括号包裹，正确格式：《{regulation_name}》（{existing_number}）"
                else:
                    return f"补充规范编号，正确格式：《{regulation_name}》（{existing_number}）"
        
        # 第二步：验证失败或没有编号，调用5个模型生成
        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:
            # 有可信的AI生成结果
            ai_number = vote_result.generated_number
            
            if issue_type == "missing_bracket":
                return f"将规范编号用括号包裹，正确格式：《{regulation_name}》（{ai_number}）"
            else:
                return f"补充规范编号，正确格式：《{regulation_name}》（{ai_number}）"
        else:
            # AI生成失败，返回基础建议
            return base_suggestion
            
    except Exception:
        # 异常时返回基础建议
        return base_suggestion


# ===== 7) 核心方法 =====
async def process_punctuation_results(check_results: str, enterprise_items: list = None) -> str:
    """
    根据标点符号检查结果生成详细的问题分析报告
    
    新流程：
    1. 首先验证现有编号是否正确（调用模型验证）
    2. 如果验证通过，接受该编号
    3. 如果验证失败，调用5个模型生成正确编号
    
    Args:
        check_results: 标点符号检查结果的JSON字符串
        enterprise_items: 企业内部文件列表（在标准库中未找到匹配的文件）
    """
    # 首先解析检查结果
    try:
        check_data = json.loads(check_results)
        if not isinstance(check_data, list):
            check_data = [check_data]
    except json.JSONDecodeError:
        check_data = []
    
    # 企业内部文件集合（用于快速查找）
    enterprise_set = set(enterprise_items or [])
    
    # 为每个检查结果生成问题分析
    results = []
    for item in check_data:
        original_text = item.get("original_text", "")
        title_status = item.get("title_mark_status", False)
        bracket_status = item.get("bracket_status")
        
        # 根据状态生成问题分析
        issue_point = "编制依据格式错误"
        risk_level = "中风险"
        
        # 判断问题类型并生成建议
        invalid_number_format = item.get("invalid_number_format", False)
        invalid_number_content = item.get("invalid_number_content", "")
        
        if title_status is not True:
            # 书名号问题
            reason = "格式错误！正确格式：《规范名称》（编号）"
            suggestion = "将规范名称用书名号《》包裹，正确格式：《规范名称》（编号）"
        elif bracket_status is None:
            # 缺少编号
            # 【修改】判断是否为企业内部文件
            if original_text in enterprise_set:
                # 企业内部文件可以没有编号，不是格式问题
                issue_point = "编制依据格式正确（企业内部文件）"
                reason = "企业内部文件，无标准规范编号"
                suggestion = "无"
                risk_level = "无风险"
            else:
                # 标准规范文件缺少编号
                reason = "缺少规范编号"
                base_suggestion = "补充规范编号，正确格式：《规范名称》（编号）"
                # 调用新流程生成建议（验证+生成）
                suggestion = await _generate_suggestion_with_validation(
                    original_text, "missing_number", base_suggestion
                )
        elif invalid_number_format:
            # 括号内有编号，但格式不正确（纯数字）
            reason = f"格式错误！当前编号为纯数字"
            suggestion = f"规范编号应为英文加数字或中文加数字，而不是纯数字"
        elif bracket_status is False:
            # 有编号但无括号
            reason = "格式错误！正确格式：《规范名称》（编号）"
            base_suggestion = "将规范编号用括号包裹，正确格式：《规范名称》（编号）"
            # 调用新流程生成建议（验证+生成）
            suggestion = await _generate_suggestion_with_validation(
                original_text, "missing_bracket", base_suggestion
            )
        else:
            # 格式正确：《文件名》（文件编号）
            # 编制依据格式检查只检查格式，不检查编号内容正确性
            # 编号是否正确（如是否已废止）由时效性检查处理
            issue_point = "编制依据格式正确"
            reason = "规范名称和编号的标点符号使用规范"
            suggestion = "无"
            risk_level = "无风险"
        
        # 构建结果
        result_item = {
            "issue_point": issue_point,
            "location": original_text,
            "suggestion": suggestion,
            "reason": reason,
            "risk_level": risk_level
        }
        results.append(result_item)
    
    # 返回JSON格式结果
    return json.dumps(results, ensure_ascii=False, indent=2)


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

    async def main():
        # 模拟标点符号检查结果
        check_results = json.dumps([
            {
                "original_text": "《建设工程安全生产管理条例》（国务院令第393号）",
                "title_mark_status": True,
                "bracket_status": True
            },
            {
                "original_text": "《混凝土结构设计规范》GB50010-2010",
                "title_mark_status": True,
                "bracket_status": False
            },
            {
                "original_text": "《起重机械安全规程》",
                "title_mark_status": True,
                "bracket_status": None
            }
        ], ensure_ascii=False)

        result = await process_punctuation_results(check_results)
        print("\n问题分析结果：")
        print(result)

    asyncio.run(main())
