#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
LLMContentClassifier 主入口类 + 便捷函数
"""

import asyncio
import json
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

from .models import ClassificationResult, SectionContent
from .config import ClassifierConfig
from .category_loaders import CategoryStandardLoader, SecondCategoryStandardLoader
from .embedding_client import EmbeddingClient
from .content_classifier import ContentClassifierClient
from .chunks_converter import ChunksConverter
from foundation.observability.logger.loggering import review_logger as logger


class LLMContentClassifier:
    """
    LLM 内容三级分类器（主入口类）

    封装完整的分类流程，提供简洁的接口供外部调用
    """

    def __init__(self, config: Optional[ClassifierConfig] = None):
        """
        初始化分类器

        Args:
            config: 配置对象，如果为 None 则使用默认配置
        """
        self.config = config or ClassifierConfig()
        logger.info(f"[LLMContentClassifier] 初始化完成，使用模型: {self.config.model}, thinking: {self.config.enable_thinking}")

        # 加载标准分类
        self.category_loader = CategoryStandardLoader(Path(self.config.category_table_path))

        # 加载二级分类标准（如果存在）
        self.second_category_loader = None
        if Path(self.config.second_category_path).exists():
            self.second_category_loader = SecondCategoryStandardLoader(Path(self.config.second_category_path))

        # 创建转换器
        self.converter = ChunksConverter(self.category_loader)

        # 并发控制信号量
        self.semaphore = asyncio.Semaphore(self.config.max_concurrent_requests)

        # Embedding 客户端（可选，通过 model_handler 统一管理）
        self.embedding_client = None
        if self.config.embedding_similarity_threshold > 0:
            try:
                self.embedding_client = EmbeddingClient()
                logger.debug("Embedding 客户端初始化成功（通过 model_handler）")
            except Exception as e:
                logger.warning(f"Embedding 客户端初始化失败: {e}，将继续不使用 Embedding 功能")

    def _create_embedding_client(self) -> EmbeddingClient:
        """创建 Embedding 客户端（统一通过 model_handler 获取配置）"""
        return EmbeddingClient()

    async def classify_chunks(
        self,
        chunks: List[Dict[str, Any]],
        progress_callback: Optional[callable] = None
    ) -> List[Dict[str, Any]]:
        """
        对 chunks 进行三级分类

        Args:
            chunks: 文档分块列表，每个 chunk 需包含:
                - chapter_classification: 一级分类代码
                - secondary_category_code: 二级分类代码
                - secondary_category_cn: 二级分类中文名
                - review_chunk_content 或 content: 内容文本
            progress_callback: 进度回调函数 (completed, total, section_name, success) -> None，支持 async

        Returns:
            List[Dict]: 更新后的 chunks 列表，每个 chunk 新增字段:
                - tertiary_category_code: 三级分类代码
                - tertiary_category_cn: 三级分类名称
                - tertiary_classification_details: 行级分类详情列表
        """
        logger.info(f"【三级分类】输入 {len(chunks)} 个内容块")

        # 步骤1: 将 chunks 转换为 SectionContent 列表
        sections = self.converter.chunks_to_sections(chunks)
        total_lines = sum(len(s.lines) for s in sections)
        total_standards = sum(len(s.category_standards) if s.category_standards else 0 for s in sections)

        logger.info(f"【三级分类】按二级标题分组后: {len(sections)} 个段落, 总计 {total_lines} 行, {total_standards} 个三级标准待匹配")
        logger.info(f"【三级分类】并发度: {self.config.max_concurrent_requests}, 模型: {self.config.model}, thinking: {self.config.enable_thinking}")

        # 计算总 LLM 调用次数（考虑分块）
        MAX_LINES_PER_CHUNK = 150
        total_llm_calls = sum((len(s.lines) + MAX_LINES_PER_CHUNK - 1) // MAX_LINES_PER_CHUNK for s in sections)
        logger.info(f"【三级分类】预计 LLM 调用次数: {total_llm_calls} 次 (每150行分一块)")

        # 打印每个段落的详情（前10个）
        for i, section in enumerate(sections[:10]):
            std_count = len(section.category_standards) if section.category_standards else 0
            chunks_needed = (len(section.lines) + MAX_LINES_PER_CHUNK - 1) // MAX_LINES_PER_CHUNK
            chunk_info = f"分{chunks_needed}块" if chunks_needed > 1 else "1块"
            logger.info(f"【三级分类】段落 {i+1}/{len(sections)}: '{section.section_name}' - {len(section.lines)} 行, {std_count} 个标准, {chunk_info}")
        if len(sections) > 10:
            remaining_calls = sum((len(s.lines) + MAX_LINES_PER_CHUNK - 1) // MAX_LINES_PER_CHUNK for s in sections[10:])
            logger.info(f"【三级分类】... 还有 {len(sections) - 10} 个段落, 预计 {remaining_calls} 次调用")

        if not sections:
            logger.info("没有有效的段落需要分类")
            return chunks

        # 步骤2: 创建分类客户端
        classifier = ContentClassifierClient(
            model=self.config.model,
            semaphore=self.semaphore,
            embedding_client=self.embedding_client,
            second_category_loader=self.second_category_loader,
            enable_thinking=self.config.enable_thinking
        )

        # 步骤3: 并发分类所有段落
        results_map: Dict[str, ClassificationResult] = {}

        async def classify_with_progress(section: SectionContent, idx: int, total: int):
            result = await classifier.classify_content(section)
            results_map[section.section_key] = result

            if progress_callback:
                ret = progress_callback(idx + 1, total, section.section_name, not result.error)
                if asyncio.iscoroutine(ret):
                    await ret
            else:
                status = "成功" if not result.error else f"失败: {result.error[:30]}"
                logger.debug(f"[{idx + 1}/{total}] {section.section_name}: {status}")

            return result

        tasks = [
            classify_with_progress(section, idx, len(sections))
            for idx, section in enumerate(sections)
        ]
        await asyncio.gather(*tasks)

        # 步骤4: 将分类结果转换回 chunks 格式，按 chunk_ranges 过滤确保每个 chunk 只拿自己行范围内的详情
        updated_chunks = []

        # 建立 chunk_id -> (section_key, g_start, g_end) 映射，来自 sections 的 chunk_ranges
        chunk_range_map: Dict[str, Tuple[str, int, int]] = {}
        for section in sections:
            for (cid, g_start, g_end) in section.chunk_ranges:
                chunk_range_map[cid] = (section.section_key, g_start, g_end)

        # 为每个原始 chunk 单独分配其行范围内的分类详情
        for chunk in chunks:
            updated_chunk = dict(chunk)
            first_code = chunk.get("chapter_classification", "") or chunk.get("first_code", "")
            second_code = chunk.get("secondary_category_code", "") or chunk.get("second_code", "")

            # 从 chunk_range_map 获取该 chunk 的行范围（同时拿到正确的 section_key）
            chunk_id = chunk.get("chunk_id") or chunk.get("id") or str(id(chunk))
            range_info = chunk_range_map.get(chunk_id)

            if range_info:
                # 优先使用 chunk_range_map 中记录的 section_key（经过名称匹配的正确 key）
                section_key = range_info[0]
            else:
                # 降级：从 chunk 字段重建（可能在 second_code="none" 时查不到）
                section_key = f"{first_code}->{second_code}"

            result = results_map.get(section_key)

            if result:
                updated_chunk["first_code"] = first_code
                updated_chunk["second_code"] = second_code

                # 收集全部有效三级分类（非 no_standard）
                all_tertiary = [
                    {
                        "third_category_name": c.third_category_name,
                        "third_category_code": c.third_category_code,
                        "third_seq": c.third_seq,
                        "start_line": c.start_line,
                        "end_line": c.end_line,
                        "content": c.content
                    }
                    for c in result.classified_contents
                    if c.third_category_code != "no_standard"
                ]

                if range_info:
                    # 过滤：只保留与该 chunk 行范围有交集的详情
                    _, g_start, g_end = range_info
                    filtered = [
                        t for t in all_tertiary
                        if t["start_line"] <= g_end and t["end_line"] >= g_start
                    ]
                else:
                    # 无法定位行范围（可能是单 chunk 分组），保留全部
                    filtered = all_tertiary

                # 去重：按 (third_category_code, start_line, end_line) 三元组去重
                seen = set()
                deduped = []
                for t in filtered:
                    key = (t["third_category_code"], t["start_line"], t["end_line"])
                    if key not in seen:
                        seen.add(key)
                        deduped.append(t)
                updated_chunk["tertiary_classification_details"] = deduped

                # 向后兼容：设置第一个三级分类为主分类
                tertiary_details = updated_chunk["tertiary_classification_details"]
                if tertiary_details:
                    updated_chunk["tertiary_category_code"] = tertiary_details[0]["third_category_code"]
                    updated_chunk["tertiary_category_cn"] = tertiary_details[0]["third_category_name"]

            updated_chunks.append(updated_chunk)

        logger.info(f"三级分类完成！共处理 {len(updated_chunks)} 个 chunks")
        return updated_chunks


# ==================== 便捷函数 ====================

async def classify_chunks(
    chunks: List[Dict[str, Any]],
    config: Optional[ClassifierConfig] = None,
    progress_callback: Optional[callable] = None
) -> List[Dict[str, Any]]:
    """
    对 chunks 进行三级分类的便捷函数

    Args:
        chunks: 文档分块列表
        config: 配置对象（可选）
        progress_callback: 进度回调函数

    Returns:
        List[Dict]: 更新后的 chunks 列表

    使用示例:
        from llm_content_classifier_v2 import classify_chunks

        # 使用默认配置
        updated_chunks = await classify_chunks(chunks)

        # 使用自定义配置
        config = ClassifierConfig(
            model="qwen3.5-122b-a10b",
            embedding_similarity_threshold=0.85
        )
        updated_chunks = await classify_chunks(chunks, config=config)
    """
    classifier = LLMContentClassifier(config)
    return await classifier.classify_chunks(chunks, progress_callback)


def classify_chunks_sync(
    chunks: List[Dict[str, Any]],
    config: Optional[ClassifierConfig] = None
) -> List[Dict[str, Any]]:
    """
    同步版本的分类函数（阻塞调用）

    Args:
        chunks: 文档分块列表
        config: 配置对象（可选）

    Returns:
        List[Dict]: 更新后的 chunks 列表
    """
    try:
        loop = asyncio.get_running_loop()
    except RuntimeError:
        # 没有运行中的事件循环
        return asyncio.run(classify_chunks(chunks, config))

    # 已有事件循环，创建任务
    import concurrent.futures
    with concurrent.futures.ThreadPoolExecutor() as executor:
        future = executor.submit(
            asyncio.run,
            classify_chunks(chunks, config)
        )
        return future.result()


# ==================== 快速测试入口 ====================

if __name__ == "__main__":
    import io
    import sys
    from datetime import datetime

    # 修复 Windows 终端 UTF-8 输出
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")

    TEST_JSON_PATH = Path(r"temp\construction_review\final_result\4148f6019f89e061b15679666f646893-1773993108.json")
    OUTPUT_DIR = Path(r"temp\construction_review\llm_content_classifier_v2")
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    def _sep(title: str = "", width: int = 70):
        print(f"\n{'=' * width}\n  {title}\n{'=' * width}" if title else "─" * width)

    def _load_chunks_from_json(json_path: Path) -> List[Dict[str, Any]]:
        with open(json_path, encoding="utf-8") as f:
            data = json.load(f)
        if "document_result" in data:
            return data["document_result"]["structured_content"]["chunks"]
        return data["data"]["document_result"]["structured_content"]["chunks"]

    # ── 加载数据 ──────────────────────────────────────────────
    _sep("加载测试数据")
    if not TEST_JSON_PATH.exists():
        print(f"[ERROR] 文件不存在: {TEST_JSON_PATH}")
        sys.exit(1)

    raw_chunks = _load_chunks_from_json(TEST_JSON_PATH)
    print(f"原始 chunks 数: {len(raw_chunks)}")

    # ── 运行完整分类流程 ───────────────────────────────────────
    _sep("运行三级分类（LLMContentClassifier）")
    config = ClassifierConfig()
    print(f"模型: {config.model}")
    print(f"Embedding 模型: {config.embedding_model}")
    print(f"相似度阈值: {config.embedding_similarity_threshold}")

    classifier = LLMContentClassifier(config)
    updated_chunks = asyncio.run(classifier.classify_chunks(raw_chunks))

    # ── 保存结果 ──────────────────────────────────────────────
    _sep("保存结果")
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    result_file = OUTPUT_DIR / f"result_{ts}.json"
    with open(result_file, "w", encoding="utf-8") as f:
        json.dump(updated_chunks, f, ensure_ascii=False, indent=2)
    print(f"完整结果已保存: {result_file}")

    # ── 控制台汇总展示 ────────────────────────────────────────
    _sep("分类结果汇总")

    # 按 section_label 聚合三级分类详情
    section_map: Dict[str, List[Dict]] = {}
    for chunk in updated_chunks:
        label = chunk.get("section_label") or chunk.get("chunk_id", "unknown")
        details = chunk.get("tertiary_classification_details", [])
        if label not in section_map:
            section_map[label] = []
        for d in details:
            key = d["third_category_code"]
            if not any(x["third_category_code"] == key for x in section_map[label]):
                section_map[label].append(d)

    total_third = 0
    for label, details in section_map.items():
        print(f"\n[{label}]  三级分类数={len(details)}")
        for d in details:
            line_range = f"L{d.get('start_line', '?')}-{d.get('end_line', '?')}"
            preview = (d.get("content") or "")[:50].replace("\n", " ")
            print(f"  ├ {d['third_category_name']}({d['third_category_code']})  {line_range}  {preview}...")
        total_third += len(details)

    _sep()
    print(f"处理 chunks: {len(updated_chunks)}  |  识别三级分类: {total_third}  |  结果目录: {OUTPUT_DIR}")
