"""
轻量级完整性审查模块

特点：
- 目录审查：二级粒度（检查二级章节是否齐全）
- 完整性审查：三级粒度（基于分类结果，无LLM）
- 大纲审查：二级粒度（检查二级章节一致性）

完全依赖分类器输出的三级分类结果，无需LLM参与。
"""

import pandas as pd
import asyncio
from typing import Dict, List, Optional, Set, Tuple, Any
from dataclasses import dataclass, field
from collections import defaultdict
from pathlib import Path
import json

from foundation.observability.logger.loggering import review_logger as logger


@dataclass
class TertiaryItem:
    """三级分类项"""
    first_code: str
    second_code: str
    third_code: str
    first_cn: str
    second_cn: str
    third_cn: str
    third_focus: str
    first_seq: int = 0
    second_seq: int = 0
    third_seq: int = 0


@dataclass
class SecondaryItem:
    """二级分类项"""
    first_code: str
    second_code: str
    first_cn: str
    second_cn: str
    first_seq: int = 0
    second_seq: int = 0


@dataclass
class LightweightCompletenessResult:
    """轻量级完整性审查结果"""
    overall_status: str  # complete / partial / incomplete

    # 目录审查结果（二级）
    catalogue_check: Dict[str, Any]

    # 完整性审查结果（三级）
    tertiary_completeness: Dict[str, Any]

    # 大纲审查结果（二级）
    outline_check: Optional[Dict[str, Any]]

    # 建议（结构化，每条包含 issue_point / location / suggestion / reason / level）
    recommendations: List[Dict[str, Any]] = field(default_factory=list)


class TertiarySpecLoader:
    """三级标准加载器"""

    def __init__(self, csv_path: str):
        self.csv_path = csv_path
        self.tertiary_items: Dict[Tuple[str, str, str], TertiaryItem] = {}
        self.secondary_items: Dict[Tuple[str, str], SecondaryItem] = {}
        self.first_names: Dict[str, str] = {}  # 从CSV动态生成的一级分类名称映射
        self._load()
    
    def _load(self) -> None:
        """加载CSV文件"""
        try:
            # 尝试不同编码
            encodings = ['utf-8-sig', 'utf-16', 'gbk', 'utf-8']
            df = None
            
            for encoding in encodings:
                try:
                    df = pd.read_csv(self.csv_path, encoding=encoding, sep=None, engine='python')
                    break
                except UnicodeDecodeError:
                    continue
            
            if df is None:
                raise ValueError(f"无法读取CSV文件: {self.csv_path}")

            # 标准化列名
            df.columns = [c.strip().lower().replace(' ', '_') for c in df.columns]

            # 遍历构建标准树
            for _, row in df.iterrows():
                first_code = str(row.get('first_code', '')).strip()
                second_code = str(row.get('second_code', '')).strip()
                third_code = str(row.get('third_code', '')).strip()

                if not all([first_code, second_code, third_code]):
                    continue

                first_cn = str(row.get('first_name', '')).strip()
                second_cn = str(row.get('second_name', '')).strip()
                third_cn = str(row.get('third_name', '')).strip()
                third_focus = str(row.get('third_focus', '')).strip()

                # 读取序号字段
                try:
                    first_seq = int(row.get('first_seq', 0) or 0)
                except (ValueError, TypeError):
                    first_seq = 0
                try:
                    second_seq = int(row.get('second_seq', 0) or 0)
                except (ValueError, TypeError):
                    second_seq = 0
                try:
                    third_seq = int(row.get('third_seq', 0) or 0)
                except (ValueError, TypeError):
                    third_seq = 0

                # 动态构建一级分类名称映射
                if first_code and first_cn and first_code not in self.first_names:
                    self.first_names[first_code] = first_cn

                # 存储三级项
                key = (first_code, second_code, third_code)
                self.tertiary_items[key] = TertiaryItem(
                    first_code=first_code,
                    second_code=second_code,
                    third_code=third_code,
                    first_cn=first_cn or self.first_names.get(first_code, first_code),
                    second_cn=second_cn,
                    third_cn=third_cn,
                    third_focus=third_focus,
                    first_seq=first_seq,
                    second_seq=second_seq,
                    third_seq=third_seq
                )

                # 存储二级项
                sec_key = (first_code, second_code)
                if sec_key not in self.secondary_items:
                    self.secondary_items[sec_key] = SecondaryItem(
                        first_code=first_code,
                        second_code=second_code,
                        first_cn=first_cn or self.first_names.get(first_code, first_code),
                        second_cn=second_cn,
                        first_seq=first_seq,
                        second_seq=second_seq
                    )
        
        except Exception as e:
            raise ValueError(f"加载标准文件失败: {e}")
    
    def get_tertiary_items(self) -> Dict[Tuple[str, str, str], TertiaryItem]:
        """获取所有三级分类项"""
        return self.tertiary_items
    
    def get_secondary_items(self) -> Dict[Tuple[str, str], SecondaryItem]:
        """获取所有二级分类项"""
        return self.secondary_items
    
    def get_tertiary_by_secondary(self, first_code: str, second_code: str) -> List[TertiaryItem]:
        """获取指定二级分类下的所有三级分类"""
        result = []
        for key, item in self.tertiary_items.items():
            if key[0] == first_code and key[1] == second_code:
                result.append(item)
        return result
    
    def get_secondary_names(self) -> Dict[Tuple[str, str], str]:
        """获取二级分类名称映射"""
        return {k: v.second_cn for k, v in self.secondary_items.items()}

    def get_first_names(self) -> Dict[str, str]:
        """获取一级分类名称映射（从CSV动态生成）"""
        return self.first_names


class LightweightCompletenessChecker:
    """轻量级完整性检查器"""

    def __init__(self, standard_csv_path: str, model_client=None, prompt_loader=None):
        """
        初始化检查器

        Args:
            standard_csv_path: StandardCategoryTable.csv 文件路径
            model_client: 模型客户端（可选），用于生成智能建议
            prompt_loader: 提示词加载器（可选）
        """
        self.spec_loader = TertiarySpecLoader(standard_csv_path)
        self.tertiary_specs = self.spec_loader.get_tertiary_items()
        self.secondary_specs = self.spec_loader.get_secondary_items()
        self.secondary_names = self.spec_loader.get_secondary_names()

        # 大模型客户端和提示词加载器（用于生成智能建议）
        self.model_client = model_client
        self.prompt_loader = prompt_loader

        # 如果没有提供model_client，尝试从foundation导入
        if self.model_client is None:
            try:
                from foundation.ai.agent.generate.model_generate import generate_model_client
                self.model_client = generate_model_client
            except ImportError:
                logger.warning("无法导入generate_model_client，建议生成功能将使用简单拼接模式")
                self.model_client = None

        # 如果没有提供prompt_loader，尝试从当前模块导入
        if self.prompt_loader is None:
            try:
                from .utils.prompt_loader import prompt_loader
                self.prompt_loader = prompt_loader
            except ImportError:
                logger.warning("无法导入prompt_loader，建议生成功能将使用简单拼接模式")
                self.prompt_loader = None
    
    def _normalize_chapter_code(self, code: str) -> str:
        """将章节分类码大小写归一化为与CSV一致（如 'management' -> 'management'）"""
        if not code:
            return code
        for k in self.spec_loader.first_names:
            if k.lower() == code.lower():
                return k
        return code

    def _build_llm_prompt_for_recommendation(
        self,
        level: str,
        first_code: str,
        first_name: str,
        second_code: str = None,
        second_name: str = None,
        tertiary_items: List[TertiaryItem] = None,
        outline_title: str = None
    ) -> str:
        """
        构建用于LLM生成建议的prompt

        Args:
            level: 缺失级别（一级 / 二级 / 三级 / 一致性）
            first_code: 一级分类代码
            first_name: 一级分类名称
            second_code: 二级分类代码（可选）
            second_name: 二级分类名称（可选）
            tertiary_items: 缺失的三级分类项列表（可选）
            outline_title: 目录中的标题（用于一致性检查）

        Returns:
            str: 构建的prompt
        """
        # 构建问题上下文
        if level == "一级":
            context = f"""
【问题类型】一级章节缺失
【缺失章节】{first_name} ({first_code})
【问题描述】文档中缺少'{first_name}'整个章节，这是专项施工方案中必须包含的一级章节。"""
            # 获取该一级下的所有二级和三级信息作为参考
            related_specs = []
            for (fc, sc), sec_item in self.secondary_specs.items():
                if fc == first_code:
                    # 获取该二级下的所有三级
                    tertiary_list = self.spec_loader.get_tertiary_by_secondary(fc, sc)
                    tertiary_info = []
                    for t_item in tertiary_list:
                        tertiary_info.append(f"      - {t_item.third_cn}: {t_item.third_focus}")
                    related_specs.append(f"""
  【二级分类】{sec_item.second_cn}
    【包含的三级内容要点】
{chr(10).join(tertiary_info)}""")

            reference = f"""
【规范参考信息】
根据《桥梁公司危险性较大工程管理实施细则（2025版）》，'{first_name}'章节应包含以下内容：
{chr(10).join(related_specs)}
"""

        elif level == "二级":
            context = f"""
【问题类型】二级章节缺失
【所属一级】{first_name} ({first_code})
【缺失章节】{second_name} ({second_code})
【问题描述】'{first_name}'下缺少'{second_name}'二级章节。"""
            # 获取该二级下的所有三级信息
            tertiary_list = self.spec_loader.get_tertiary_by_secondary(first_code, second_code)
            tertiary_info = []
            for t_item in tertiary_list:
                tertiary_info.append(f"    - {t_item.third_cn}: {t_item.third_focus}")

            reference = f"""
【规范参考信息】
根据《桥梁公司危险性较大工程管理实施细则（2025版）》，'{second_name}'章节应包含以下三级内容要点：
{chr(10).join(tertiary_info)}
"""

        elif level == "三级":
            context = f"""
【问题类型】三级内容缺失
【所属一级】{first_name} ({first_code})
【所属二级】{second_name} ({second_code})
【缺失内容】"""
            missing_contents = []
            for item in tertiary_items or []:
                missing_contents.append(f"    - {item.third_cn}: {item.third_focus}")
            context += "\n" + "\n".join(missing_contents)

            reference = f"""
【规范参考信息】
以上缺失的内容要点是'{second_name}'章节下的标准内容要求，具体包括：
{chr(10).join([f'  - {t.third_cn}: 应包含{t.third_focus}' for t in (tertiary_items or [])])}
"""

        elif level == "一致性":
            context = f"""
【问题类型】目录与正文不一致
【涉及章节】{outline_title or second_name}
【问题描述】目录页列有该章节，但正文中未发现对应内容。"""
            reference = """
【规范参考信息】
根据文档一致性要求，目录中列出的章节应在正文中有对应的内容描述。若该章节确实不需要，应从目录中移除；若需要保留，则必须补充正文内容。
"""
        else:
            context = "【问题类型】未知"
            reference = ""

        prompt = f"""你是一位资深的工程施工方案审查专家。请根据以下问题上下文和规范参考信息，生成专业的补充建议。

{context}

{reference}

请用JSON格式输出，只包含以下字段：
- suggestion: 具体补充建议（详细可行，100-200字，包含具体应该补充的内容要点）

注意：
1. suggestion应该具体、可操作，引用规范中的具体内容要求
2. 使用专业的工程术语
3. 语气应该是指导性的，帮助编制人员理解需要补充什么内容
4. **必须符合现实逻辑**：建议内容应基于实际工程施工的可行性，不能提出不切实际、无法操作或与工程常识相悖的建议

JSON输出："""
        return prompt

    async def _generate_recommendation_with_llm(
        self,
        level: str,
        first_code: str,
        first_name: str,
        second_code: str = None,
        second_name: str = None,
        tertiary_items: List[TertiaryItem] = None,
        outline_title: str = None,
        timeout: int = 30,
        first_seq: int = 0,
        second_seq: int = 0
    ) -> Dict[str, str]:
        """
        使用大模型生成建议

        【修改】只生成 suggestion，issue_point 和 reason 由调用方简单拼接

        Returns:
            Dict[str, str]: 包含 suggestion 的字典，或None使用回退逻辑
        """
        if not self.model_client:
            return None

        try:
            prompt = self._build_llm_prompt_for_recommendation(
                level=level,
                first_code=first_code,
                first_name=first_name,
                second_code=second_code,
                second_name=second_name,
                tertiary_items=tertiary_items,
                outline_title=outline_title
            )

            # 调用大模型
            task_prompt_info = {
                "task_prompt": prompt,
                "task_name": f"completeness_suggestion_{level}"
            }

            # 生成唯一trace_id
            import uuid
            trace_id = f"completeness_llm_{uuid.uuid4().hex[:8]}"

            # 使用 function_name 从 model_setting.yaml 加载模型配置
            model_response = await self.model_client.get_model_generate_invoke(
                trace_id=trace_id,
                task_prompt_info=task_prompt_info,
                timeout=timeout,
                function_name="completeness_review_generate"
            )

            # 解析模型返回的JSON
            try:
                response_text = model_response.strip()
                if "```json" in response_text:
                    json_str = response_text.split("```json")[1].split("```")[0].strip()
                elif "```" in response_text:
                    json_str = response_text.split("```")[1].split("```")[0].strip()
                else:
                    json_str = response_text

                result = json.loads(json_str)
                # 只返回 suggestion，issue_point 和 reason 由调用方处理
                return {
                    "suggestion": result.get("suggestion", "")
                }
            except (json.JSONDecodeError, IndexError) as e:
                logger.warning(f"LLM建议生成结果解析失败: {e}")
                return None

        except Exception as e:
            logger.warning(f"LLM建议生成失败: {e}")
            return None

    async def check(
        self,
        chunks: List[Dict],
        outline: Optional[List[Dict]] = None,
        chapter_classification: Optional[str] = None
    ) -> LightweightCompletenessResult:
        """
        执行轻量级完整性检查

        Args:
            chunks: 文档分块列表，每个chunk需包含tertiary_category_code
            outline: 目录结构（可选）
            chapter_classification: 一级章节分类代码（可选），如果提供则只检查该章节下的分类

        Returns:
            LightweightCompletenessResult
        """
        # 归一化章节分类码，消除大小写差异（如 'management' -> 'management'）
        if chapter_classification:
            chapter_classification = self._normalize_chapter_code(chapter_classification)

        # 1. 提取正文实际分类（来自 chunks），并同步归一化一级分类码
        actual_secondary = self._extract_secondary_from_chunks(chunks)
        actual_tertiary = self._extract_tertiary_from_chunks(chunks)

        # 将 actual 中的一级分类码同步归一化（解决 'management' vs 'management' 等问题）
        actual_secondary = {
            (self._normalize_chapter_code(c1), c2) for c1, c2 in actual_secondary
        }
        actual_tertiary = {
            (self._normalize_chapter_code(c1), c2, c3) for c1, c2, c3 in actual_tertiary
        }

        # 2. 从目录页提取一级/二级（带原始标题）
        outline_first: Set[str] = set()
        outline_secondary: Dict[Tuple[str, str], str] = {}
        if outline:
            outline_first, outline_secondary = self._extract_from_outline(outline)

        # 3. 目录结构审查（一级+二级）：目录页 vs 标准
        catalogue_result = self._check_catalogue(
            outline_first, outline_secondary, chapter_classification
        )

        # 4. 正文完整性审查（三级）：正文 vs 标准
        tertiary_result = self._check_tertiary_completeness(actual_tertiary, chapter_classification)

        # 5. 一致性审查（二级）：目录页标题 vs 正文内容
        outline_result = None
        if outline:
            outline_result = self._check_outline(actual_secondary, outline_secondary)

        # 6. 计算总体状态
        overall_status = self._calc_overall_status(tertiary_result)

        # 7. 生成分级建议
        actual_first = {cat1 for cat1, _ in actual_secondary}
        recommendations = await self._generate_recommendations(
            tertiary_result, catalogue_result, outline_result,
            actual_first, actual_secondary, actual_tertiary,
            chapter_classification
        )

        return LightweightCompletenessResult(
            overall_status=overall_status,
            catalogue_check=catalogue_result,
            tertiary_completeness=tertiary_result,
            outline_check=outline_result,
            recommendations=recommendations
        )
    
    def _extract_secondary_from_chunks(self, chunks: List[Dict]) -> Set[Tuple[str, str]]:
        """从chunks提取实际存在的二级分类（支持 metadata 嵌套格式）"""
        actual = set()
        for chunk in chunks:
            # 支持 metadata 嵌套格式和直接字段格式
            metadata = chunk.get("metadata", {})
            cat1 = (metadata.get("chapter_classification") or
                    chunk.get("chapter_classification") or
                    chunk.get("first_code"))
            cat2 = (metadata.get("secondary_category_code") or
                    chunk.get("secondary_category_code") or
                    chunk.get("second_code"))
            if cat1 and cat2:
                actual.add((cat1, cat2))
        return actual

    def _extract_tertiary_from_chunks(self, chunks: List[Dict]) -> Set[Tuple[str, str, str]]:
        """
        从chunks提取实际存在的三级分类

        支持三种数据格式：
        1. 传统格式：每个 chunk 有 tertiary_category_code 字段（单分类）
        2. 增强格式：每个 chunk 有 tertiary_classification_details 字段（行级多分类）
        3. metadata 嵌套格式：字段在 metadata 子对象中
        """
        actual = set()

        for chunk in chunks:
            # 支持 metadata 嵌套格式和直接字段格式
            metadata = chunk.get("metadata", {})
            cat1 = (metadata.get("chapter_classification") or
                    chunk.get("chapter_classification") or
                    chunk.get("first_code"))
            cat2 = (metadata.get("secondary_category_code") or
                    chunk.get("secondary_category_code") or
                    chunk.get("second_code"))

            if not cat1 or not cat2:
                continue

            # 优先从行级分类详情提取（增强格式）
            # 支持 metadata 嵌套和直接字段两种格式
            details = (metadata.get("tertiary_classification_details") or
                       chunk.get("tertiary_classification_details"))
            if details and isinstance(details, list):
                for detail in details:
                    if isinstance(detail, dict):
                        cat3 = detail.get("third_category_code") or detail.get("tertiary_category_code")
                        if cat3:
                            actual.add((cat1, cat2, cat3))

            # 回退到传统单分类格式
            if not details:
                cat3 = (metadata.get("tertiary_category_code") or
                        chunk.get("tertiary_category_code") or
                        chunk.get("third_code"))
                if cat3:
                    actual.add((cat1, cat2, cat3))

        return actual

    def _extract_from_outline(
        self, outline: List[Dict]
    ) -> Tuple[Set[str], Dict[Tuple[str, str], str]]:
        """
        从目录页提取一级分类集合与二级分类映射（含原始标题）

        Returns:
            outline_first:     {first_code, ...}
            outline_secondary: {(first_code, second_code): outline_title, ...}
        """
        outline_first: Set[str] = set()
        outline_secondary: Dict[Tuple[str, str], str] = {}

        if not isinstance(outline, list):
            return outline_first, outline_secondary

        for item in outline:
            if not isinstance(item, dict):
                continue
            cat1 = item.get("chapter_classification")
            cat2 = item.get("secondary_category_code")
            title = item.get("title", "")
            if cat1:
                outline_first.add(cat1)
            if cat1 and cat2 and (cat1, cat2) not in outline_secondary:
                outline_secondary[(cat1, cat2)] = title

        return outline_first, outline_secondary

    def _check_catalogue(self, outline_first: Set[str],
                         outline_secondary: Dict[Tuple[str, str], str],
                         chapter_classification: Optional[str] = None) -> Dict[str, Any]:
        """
        目录结构审查（一级 + 二级粒度）
        检查目录页是否列出了标准要求的所有一级和二级章节

        Args:
            outline_first:     从目录页提取的一级分类集合
            outline_secondary: 从目录页提取的二级分类映射 {(cat1,cat2): title}
            chapter_classification: 若提供则只检查该一级章节范围
        """
        outline_second_keys = set(outline_secondary.keys())

        # 确定检查范围
        if chapter_classification:
            required_first = (
                {chapter_classification}
                if any(k[0] == chapter_classification for k in self.secondary_specs)
                else set()
            )
            required_second = {
                (c1, c2) for (c1, c2) in self.secondary_specs
                if c1 == chapter_classification
            }
            actual_first = {c for c in outline_first if c == chapter_classification}
            actual_second_keys = {
                (c1, c2) for (c1, c2) in outline_second_keys if c1 == chapter_classification
            }
        else:
            required_first = {k[0] for k in self.secondary_specs}
            required_second = set(self.secondary_specs.keys())
            actual_first = outline_first
            actual_second_keys = outline_second_keys

        # 一级差异
        missing_first = required_first - actual_first
        extra_first = actual_first - required_first

        # 二级差异
        missing_second = required_second - actual_second_keys
        extra_second = actual_second_keys - required_second

        # 一级缺失详情
        missing_first_details = []
        for c in sorted(missing_first):
            # 从任意该一级下的二级获取 first_seq
            first_seq = 0
            for (fc, sc), item in self.secondary_specs.items():
                if fc == c:
                    first_seq = item.first_seq
                    break
            missing_first_details.append({
                "first_code": c,
                "first_name": self.spec_loader.first_names.get(c, c),
                "first_seq": first_seq
            })

        # 二级缺失详情
        missing_second_details = []
        for cat1, cat2 in sorted(missing_second):
            item = self.secondary_specs.get((cat1, cat2))
            missing_second_details.append({
                "first_code": cat1,
                "first_name": item.first_cn if item else self.spec_loader.first_names.get(cat1, cat1),
                "first_seq": item.first_seq if item else 0,
                "secondary_code": cat2,
                "secondary_name": item.second_cn if item else "未知",
                "second_seq": item.second_seq if item else 0
            })

        # 二级多余详情（目录有但标准无）
        extra_second_details = []
        for cat1, cat2 in sorted(extra_second):
            item = self.secondary_specs.get((cat1, cat2))
            extra_second_details.append({
                "first_code": cat1,
                "first_name": self.spec_loader.first_names.get(cat1, cat1),
                "first_seq": item.first_seq if item else 0,
                "secondary_code": cat2,
                "secondary_name": item.second_cn if item else "未知",
                "second_seq": item.second_seq if item else 0,
                "outline_title": outline_secondary.get((cat1, cat2), "")
            })

        present_second = len(required_second & actual_second_keys)
        second_rate = present_second / len(required_second) * 100 if required_second else 0

        return {
            "level": "primary_and_secondary",
            "is_complete": len(missing_first) == 0 and len(missing_second) == 0,
            "first_level": {
                "total_required": len(required_first),
                "actual_present": len(actual_first & required_first),
                "missing_count": len(missing_first),
                "extra_count": len(extra_first),
                "missing": missing_first_details,
                "extra": [
                    {"first_code": c, "first_name": self.spec_loader.first_names.get(c, c)}
                    for c in sorted(extra_first)
                ]
            },
            "second_level": {
                "total_required": len(required_second),
                "actual_present": present_second,
                "missing_count": len(missing_second),
                "extra_count": len(extra_second),
                "completeness_rate": f"{second_rate:.1f}%",
                "missing": missing_second_details,
                "extra": extra_second_details
            }
        }
    
    def _check_tertiary_completeness(
        self,
        actual_tertiary: Set[Tuple[str, str, str]],
        chapter_classification: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        三级完整性审查（核心方法，无LLM）
        
        逻辑：
        1. 对比标准三级 vs 实际三级
        2. 找出缺失的三级（无内容）
        3. 按二级分组统计
        
        Args:
            actual_tertiary: 实际存在的三级分类集合
            chapter_classification: 如果提供，只检查该一级章节下的三级分类
        """
        # 如果指定了章节，只过滤该章节的标准分类
        if chapter_classification:
            required = {(cat1, cat2, cat3) for (cat1, cat2, cat3) in self.tertiary_specs.keys() if cat1 == chapter_classification}
            # 只保留同一章节的实际分类
            actual = {(cat1, cat2, cat3) for (cat1, cat2, cat3) in actual_tertiary if cat1 == chapter_classification}
        else:
            required = set(self.tertiary_specs.keys())
            actual = actual_tertiary
        
        present = required & actual
        missing = required - actual
        extra = actual - required
        
        # 缺失详情
        missing_details = []
        for cat1, cat2, cat3 in sorted(missing):
            item = self.tertiary_specs.get((cat1, cat2, cat3))
            if item:
                missing_details.append({
                    "first_code": cat1,
                    "first_name": item.first_cn,
                    "first_seq": item.first_seq,
                    "secondary_code": cat2,
                    "secondary_name": item.second_cn,
                    "second_seq": item.second_seq,
                    "tertiary_code": cat3,
                    "tertiary_name": item.third_cn,
                    "third_seq": item.third_seq,
                    "focus": item.third_focus
                })
        
        # 按二级分组统计
        secondary_stats = defaultdict(lambda: {
            "total": 0, "present": 0, "missing": 0
        })
        
        for cat1, cat2, cat3 in required:
            key = (cat1, cat2)
            secondary_stats[key]["total"] += 1
            if (cat1, cat2, cat3) in present:
                secondary_stats[key]["present"] += 1
            else:
                secondary_stats[key]["missing"] += 1
        
        # 计算完整率并构建统计列表
        secondary_stats_list = []
        for (cat1, cat2), stats in sorted(secondary_stats.items()):
            item = self.secondary_specs.get((cat1, cat2))
            completeness_rate = stats["present"] / stats["total"] * 100 if stats["total"] > 0 else 0
            secondary_stats_list.append({
                "first_code": cat1,
                "first_name": item.first_cn if item else self.spec_loader.first_names.get(cat1, cat1),
                "first_seq": item.first_seq if item else 0,
                "secondary_code": cat2,
                "secondary_name": item.second_cn if item else "未知",
                "second_seq": item.second_seq if item else 0,
                "total_tertiary": stats["total"],
                "present": stats["present"],
                "missing": stats["missing"],
                "completeness_rate": f"{completeness_rate:.1f}%"
            })
        
        # 总体完整率
        overall_rate = len(present) / len(required) * 100 if required else 0
        
        return {
            "level": "tertiary",
            "total": len(required),
            "present": len(present),
            "missing": len(missing),
            "extra": len(extra),
            "completeness_rate": f"{overall_rate:.1f}%",
            "missing_details": missing_details,
            "secondary_stats": secondary_stats_list
        }
    
    def _check_outline(
        self,
        actual_secondary: Set[Tuple[str, str]],
        outline_secondary: Dict[Tuple[str, str], str]
    ) -> Dict[str, Any]:
        """
        一致性审查（二级粒度）
        对比目录页标题与正文实际内容的二级分类是否吻合

        Args:
            actual_secondary:  从正文 chunks 提取的二级分类集合
            outline_secondary: 从目录页提取的二级分类映射 {(cat1,cat2): outline_title}
        """
        outline_keys = set(outline_secondary.keys())

        # 空章节：目录页列了，但正文无对应内容
        empty_sections = []
        for (cat1, cat2) in sorted(outline_keys - actual_secondary):
            item = self.secondary_specs.get((cat1, cat2))
            empty_sections.append({
                "first_code": cat1,
                "first_name": item.first_cn if item else self.spec_loader.first_names.get(cat1, cat1),
                "secondary_code": cat2,
                "secondary_name": item.second_cn if item else "未知",
                "outline_title": outline_secondary.get((cat1, cat2), "")  # 目录页原始标题
            })

        # 未归类内容：正文有内容，但目录页未列出
        unclassified_content = []
        for (cat1, cat2) in sorted(actual_secondary - outline_keys):
            item = self.secondary_specs.get((cat1, cat2))
            unclassified_content.append({
                "first_code": cat1,
                "first_name": item.first_cn if item else self.spec_loader.first_names.get(cat1, cat1),
                "secondary_code": cat2,
                "secondary_name": item.second_cn if item else "未知"
            })

        matched = outline_keys & actual_secondary
        match_rate = len(matched) / len(outline_keys) * 100 if outline_keys else 0

        return {
            "level": "secondary",
            "is_consistent": len(empty_sections) == 0 and len(unclassified_content) == 0,
            "outline_secondary_count": len(outline_keys),
            "content_secondary_count": len(actual_secondary),
            "matched_count": len(matched),
            "match_rate": f"{match_rate:.1f}%",
            "empty_sections": empty_sections,       # 目录有，正文无
            "unclassified_content": unclassified_content  # 正文有，目录无
        }
    
    def _calc_overall_status(self, tertiary_result: Dict) -> str:
        """计算总体状态"""
        rate_str = tertiary_result.get("completeness_rate", "0%").rstrip("%")
        try:
            rate = float(rate_str)
        except:
            rate = 0
        
        if rate >= 95:
            return "complete"
        elif rate >= 70:
            return "partial"
        else:
            return "incomplete"
    
    async def _generate_recommendations(
        self,
        tertiary_result: Dict,
        catalogue_result: Dict,
        outline_result: Optional[Dict],
        actual_first: Set[str],
        actual_secondary: Set[Tuple[str, str]],
        actual_tertiary: Set[Tuple[str, str, str]],
        chapter_classification: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """
        生成结构化分级改进建议。

        每条建议包含：
          level        : 缺失级别（一级 / 二级 / 三级 / 一致性）
          issue_point  : 问题摘要（含级别标识）
          location     : 问题定位路径
          suggestion   : 补充建议（使用LLM生成）
          reason       : 规范依据说明（使用LLM生成）
        """
        recommendations: List[Dict[str, Any]] = []

        # 确定需要检查的一级分类范围
        if chapter_classification:
            required_first = (
                {chapter_classification}
                if any(k[0] == chapter_classification for k in self.tertiary_specs)
                else set()
            )
        else:
            required_first = {k[0] for k in self.tertiary_specs}

        for first_code in sorted(required_first):
            first_name = self.spec_loader.first_names.get(first_code, first_code)
            # 获取一级序号
            first_seq = 0
            for (fc, sc), item in self.secondary_specs.items():
                if fc == first_code:
                    first_seq = item.first_seq
                    break

            # ── 一级缺失 ──────────────────────────────────────────────
            if first_code not in actual_first:
                # issue_point 和 reason 使用简单拼接
                issue_point = f"【一级章节缺失】'{first_name}'整个章节不存在"
                reason = f"依据《桥梁公司危险性较大工程管理实施细则（2025版）》规定，文档必须包含'{first_name}'一级章节，当前正文中未发现该章节任何内容"

                # 尝试使用LLM生成 suggestion
                llm_result = await self._generate_recommendation_with_llm(
                    level="一级",
                    first_code=first_code,
                    first_name=first_name,
                    first_seq=first_seq,
                    second_seq=0
                )

                if llm_result and llm_result.get("suggestion"):
                    suggestion = llm_result.get("suggestion")
                else:
                    # 回退到简单拼接
                    suggestion = f"请添加'{first_name}'章节及其下全部子章节内容"

                recommendations.append({
                    "level": "一级",
                    "issue_point": issue_point,
                    "location": first_name,
                    "suggestion": suggestion,
                    "reason": reason,
                    "first_seq": first_seq,
                })
                continue

            # ── 一级存在，检查二级 ─────────────────────────────────────
            required_second = sorted(
                (c1, c2) for (c1, c2) in self.secondary_specs if c1 == first_code
            )
            for (cat1, cat2) in required_second:
                sec_item = self.secondary_specs.get((cat1, cat2))
                second_name = sec_item.second_cn if sec_item else cat2
                second_seq = sec_item.second_seq if sec_item else 0

                # ── 二级缺失 ──────────────────────────────────────────
                if (cat1, cat2) not in actual_secondary:
                    # issue_point 和 reason 使用简单拼接
                    issue_point = f"【二级章节缺失】{first_name} > '{second_name}'整个章节不存在"
                    reason = f"依据《桥梁公司危险性较大工程管理实施细则（2025版）》规定，'{first_name}'下应包含'{second_name}'二级章节，当前正文中未发现该章节内容"

                    # 尝试使用LLM生成 suggestion
                    llm_result = await self._generate_recommendation_with_llm(
                        level="二级",
                        first_code=cat1,
                        first_name=first_name,
                        second_code=cat2,
                        second_name=second_name,
                        first_seq=first_seq,
                        second_seq=second_seq
                    )

                    if llm_result and llm_result.get("suggestion"):
                        suggestion = llm_result.get("suggestion")
                    else:
                        # 回退到简单拼接
                        suggestion = f"请在'{first_name}'下添加'{second_name}'章节内容"

                    recommendations.append({
                        "level": "二级",
                        "issue_point": issue_point,
                        "location": f"{first_name} > {second_name}",
                        "suggestion": suggestion,
                        "reason": reason,
                        "first_seq": first_seq,
                        "second_seq": second_seq,
                    })
                    continue

                # ── 二级存在，检查三级缺失 ────────────────────────────
                all_required_keys = sorted(
                    (c1, c2, c3)
                    for (c1, c2, c3) in self.tertiary_specs
                    if c1 == cat1 and c2 == cat2
                )
                missing_t_items = [
                    self.tertiary_specs[k]
                    for k in all_required_keys
                    if k not in actual_tertiary
                ]

                if not missing_t_items:
                    continue

                # issue_point 和 reason 使用简单拼接（三级缺失）
                # 尝试使用LLM批量生成 suggestion
                llm_result = await self._generate_recommendation_with_llm(
                    level="三级",
                    first_code=cat1,
                    first_name=first_name,
                    second_code=cat2,
                    second_name=second_name,
                    tertiary_items=missing_t_items,
                    first_seq=first_seq,
                    second_seq=second_seq
                )

                if llm_result and llm_result.get("suggestion"):
                    suggestion = llm_result.get("suggestion")
                else:
                    # 回退到简单拼接：列出所有缺失项
                    missing_names = "、".join([t.third_cn for t in missing_t_items[:5]])
                    if len(missing_t_items) > 5:
                        missing_names += f"等{len(missing_t_items)}项内容"
                    suggestion = f"请补充'{second_name}'下的{missing_names}"

                for t_item in missing_t_items:
                    recommendations.append({
                        "level": "三级",
                        "issue_point": f"【三级内容缺失】{first_name} > {second_name} > '{t_item.third_cn}'",
                        "location": f"{first_name} > {second_name}",
                        "suggestion": suggestion,
                        "reason": f"依据《桥梁公司危险性较大工程管理实施细则（2025版）》规定，'{second_name}'下应包含'{t_item.third_cn}'内容要点",
                        "first_seq": first_seq,
                        "second_seq": second_seq,
                        "third_seq": t_item.third_seq,
                    })

        # ── 一致性审查：目录有列但正文无内容 ─────────────────────────────
        if outline_result:
            for e in outline_result.get("empty_sections", []):
                f_name = e.get("first_name", "")
                sec_title = e.get("outline_title") or e.get("secondary_name", "")
                location = f"{f_name} > {sec_title}" if f_name else sec_title

                # issue_point 和 reason 使用简单拼接（一致性审查）
                issue_point = f"【目录正文不一致】'{location}'目录已列但正文无内容"
                reason = f"依据《桥梁公司危险性较大工程管理实施细则（2025版）》规定，目录应与正文保持一致。目录页列有'{sec_title}'章节，但正文中未发现对应内容"

                # 尝试使用LLM生成 suggestion
                llm_result = await self._generate_recommendation_with_llm(
                    level="一致性",
                    first_code="",
                    first_name=f_name,
                    second_name=sec_title,
                    outline_title=sec_title,
                    first_seq=0,
                    second_seq=0
                )

                if llm_result and llm_result.get("suggestion"):
                    suggestion = llm_result.get("suggestion")
                else:
                    # 回退到简单拼接
                    suggestion = f"请补充'{sec_title}'章节的正文内容，或从目录中移除该章节"

                recommendations.append({
                    "level": "一致性",
                    "issue_point": issue_point,
                    "location": location,
                    "suggestion": suggestion,
                    "reason": reason,
                })

        if not recommendations:
            recommendations.append({
                "level": "通过",
                "issue_point": "文档完整性良好",
                "location": "",
                "suggestion": "无需补充",
                "reason": "依据《桥梁公司危险性较大工程管理实施细则（2025版）》规定，文档已覆盖所有章节与内容要点",
            })

        return recommendations


# 便捷函数
async def check_completeness_lightweight(
    chunks: List[Dict],
    outline: Optional[List[Dict]] = None,
    standard_csv_path: Optional[str] = None,
    model_client=None,
    prompt_loader=None
) -> LightweightCompletenessResult:
    """
    轻量级完整性审查入口函数

    Args:
        chunks: 文档分块列表，每个chunk需包含tertiary_category_code
        outline: 目录结构（可选）
        standard_csv_path: 三级标准CSV文件路径，默认为doc_worker/config/StandardCategoryTable.csv
        model_client: 模型客户端（可选），用于生成智能建议
        prompt_loader: 提示词加载器（可选）

    Returns:
        LightweightCompletenessResult
    """
    if standard_csv_path is None:
        # 默认路径
        default_path = Path(__file__).parent.parent.parent.parent.parent / "doc_worker" / "config" / "StandardCategoryTable.csv"
        standard_csv_path = str(default_path)

    checker = LightweightCompletenessChecker(
        standard_csv_path,
        model_client=model_client,
        prompt_loader=prompt_loader
    )
    return await checker.check(chunks=chunks, outline=outline)


def result_to_dict(result: LightweightCompletenessResult) -> Dict[str, Any]:
    """将结果对象转换为字典"""
    return {
        "overall_status": result.overall_status,
        "catalogue_check": result.catalogue_check,
        "tertiary_completeness": result.tertiary_completeness,
        "outline_check": result.outline_check,
        "recommendations": result.recommendations
    }


# 向后兼容的别名
TertiarySpecLoader = TertiarySpecLoader
CompletenessResult = LightweightCompletenessResult
