# -*- coding: utf-8 -*-
"""
配风计划审查 - MCP 工具模块

通过 MCP 客户端查询外部数据源，供配风计划审查智能体（数据一致性审查）调用。

MCP 服务端提供的工具：
- query_monthly_mining_plan:      月度采掘计划查询
- query_working_face_procedure:    工作面作业规程查询
- query_gas_identification_report: 瓦斯等级鉴定报告查询
- query_ventilation_report:        测风报表查询

工具列表：
- query_mining_plan:        查询煤矿月度采掘计划
- query_face_procedure:     查询工作面作业规程
- query_gas_report:         查询瓦斯等级鉴定报告
- query_wind_report:        查询测风报表
"""

import json
from fastmcp import Client
from tools.mcp_logger import log_mcp_call


# ============================================================
# MCP 客户端辅助函数
# ============================================================

def _get_mcp_url() -> str:
    """获取 MCP 服务 URL。

    从项目根目录 .env 读取 MCP_BASE_URL 配置，拼接 /mcp 路径。
    """
    from dotenv import dotenv_values
    from pathlib import Path as _Path

    _env_file = _Path(__file__).parent.parent / ".env"
    _cfg = dotenv_values(str(_env_file))
    mcp_url = _cfg.get("MCP_BASE_URL", "http://localhost:8100").rstrip("/")
    return f"{mcp_url}/mcp"


async def _call_mcp_tool(tool_name: str, arguments: dict) -> str:
    """通过 FastMCP 异步客户端调用远程 MCP 工具。

    Args:
        tool_name: MCP 工具名称
        arguments: 工具参数字典

    Returns:
        JSON 字符串格式的查询结果
    """
    mcp_url = _get_mcp_url()
    try:
        client = Client(mcp_url)
        async with client:
            result = await client.call_tool(tool_name, arguments)
            raw = result.content[0].text
            log_mcp_call(tool_name, arguments, raw)
            return raw
    except Exception as e:
        log_mcp_call(tool_name, arguments, "", error=str(e))
        import traceback
        traceback.print_exc()
        return json.dumps(
            {"error": f"MCP调用失败: {e}", "tool": tool_name, "args": arguments},
            ensure_ascii=False,
        )


# ============================================================
# 配风计划审查 MCP 工具
# ============================================================

def _normalize_plan_month(raw: str) -> str:
    """规范化计划月份为 yyyy-MM 格式（匹配 MCP 服务端要求）。

    支持的输入格式：
    - "2026-6" / "2026-06" → "2026-06"
    - "2026年6月" → "2026-06"
    - "2026/6" → "2026-06"
    - "2026.6" → "2026-06"

    Args:
        raw: LLM 从 PDF 中提取的原始月份字符串

    Returns:
        str: yyyy-MM 格式的计划月份
    """
    import re
    raw = raw.strip().replace(" ", "")
    # 匹配 "2026年6月" 格式
    m = re.match(r"(\d{4})\s*年\s*(\d{1,2})\s*月?", raw)
    if m:
        return f"{m.group(1)}-{int(m.group(2)):02d}"
    # 匹配 "2026-6" / "2026-06" / "2026/6" / "2026.6" 格式
    m = re.match(r"(\d{4})\s*[-/\.]\s*(\d{1,2})", raw)
    if m:
        return f"{m.group(1)}-{int(m.group(2)):02d}"
    # 无法识别则原样返回（MCP 服务端做模糊匹配）
    return raw


# ============================================================
# 煤矿名称模糊重试机制
# ============================================================

def _generate_name_variations(mine_name: str, max_variations: int = 3) -> list:
    """生成煤矿名称的渐进放宽变体列表，用于 MCP 查询重试。

    典型场景：智能体从 PDF 中提取的矿名与数据库中的矿名存在细微差异，
    如 "合阳县金桥煤炭有限责任公司"（PDF 提取） vs "合阳县金桥煤炭有限公司"（DB 记录）。

    变体优先级（从最精确到最宽松）：
    Level 0: 原始名称
    Level 1: 替换「有限责任公司」→「有限公司」（最常见的不匹配模式）
    Level 2: 去除所有企业后缀（有限责任/股份/有限/公司）
    Level 3: 进一步去除行业词汇（煤炭/煤业/矿业/煤矿），仅保留地名+核心名

    Args:
        mine_name: 原始煤矿名称
        max_variations: 最大变体数（不含原始名称），默认 3

    Returns:
        list[str]: 名称变体列表，第一个元素始终是原始名称
    """
    import re

    variations = [mine_name.strip()]
    if not mine_name or len(mine_name.strip()) < 3:
        return variations

    name = mine_name.strip()

    # ── Level 1: 替换「有限责任公司」为「有限公司」──
    if "有限责任公司" in name:
        v = name.replace("有限责任公司", "有限公司")
        if v != name and v not in variations:
            variations.append(v)

    if len(variations) > max_variations:
        return variations[:max_variations + 1]

    # ── Level 2: 去除常见企业后缀 ──
    # 按长度降序匹配，避免短后缀先匹配导致长后缀残留
    entity_suffixes = [
        "股份有限公司", "有限责任公司",
        "股份公司", "有限公司",
        "集团公司", "总公司",
        "公司",
    ]
    v2 = name
    for suffix in entity_suffixes:
        if v2.endswith(suffix):
            v2 = v2[:-len(suffix)]
            break
    if v2 != name and v2 not in variations:
        variations.append(v2)

    if len(variations) > max_variations:
        return variations[:max_variations + 1]

    # ── Level 3: 进一步去除行业/类型词汇 ──
    industry_words = ["煤炭", "煤业", "矿业", "煤矿", "煤电", "煤化"]
    v3 = v2
    for w in industry_words:
        v3 = v3.replace(w, "")
    v3 = v3.strip()
    if v3 and v3 != name and v3 not in variations:
        variations.append(v3)

    return variations[:max_variations + 1]


def _has_data(result: str) -> bool:
    """检测 MCP 返回结果是否包含有效数据（非错误、非空）。

    判断依据：
    - 空字符串/纯空白 → 无数据
    - 包含 {"error": ...} → 无数据（MCP 调用异常）
    - JSON 对象所有值均为空/None/[] → 无数据
    - JSON 数组为空 → 无数据
    - 有效 JSON 且有实质内容 → 有数据
    - 非 JSON 长文本 → 可能有数据（保守判定为有数据）

    Args:
        result: MCP 工具返回的原始字符串

    Returns:
        bool: True 表示结果中包含有效数据
    """
    if not result or not result.strip():
        return False

    stripped = result.strip()

    try:
        data = json.loads(stripped)
    except (json.JSONDecodeError, TypeError):
        # 非 JSON 响应：若长度 > 50 字符则保守判定为有数据
        return len(stripped) > 50

    # 显式错误响应
    if isinstance(data, dict):
        if data.get("error"):
            return False
        # 检查是否有任何实质内容
        has_content = any(
            v is not None and v != [] and v != {} and v != ""
            for v in data.values()
        )
        return has_content

    if isinstance(data, list):
        return len(data) > 0

    # 其他类型（字符串、数字等）视为有数据
    return True


async def _query_mine_with_retry(
    tool_name: str,
    mine_name: str,
    extra_args: dict = None,
    max_retries: int = 3,
) -> str:
    """带名称放宽重试的 MCP 查询包装器。

    首次使用原始名称查询；若返回空结果，则逐步放宽煤矿名称并重试，
    最多重试 max_retries 次（即总共最多查询 1 + max_retries 次）。

    Args:
        tool_name: MCP 工具名称
        mine_name: 煤矿名称
        extra_args: 除 mine_name 外的额外参数（如 plan_month, year 等）
        max_retries: 最大重试次数，默认 3

    Returns:
        str: MCP 工具返回的 JSON 字符串（首次查到数据即返回）
    """
    names_to_try = _generate_name_variations(mine_name, max_retries)
    last_result = None

    for i, name in enumerate(names_to_try):
        args = {"mine_name": name}
        if extra_args:
            args.update(extra_args)

        if i == 0:
            print(f"[MCP Retry] 首次查询，mine_name='{name}'")
        else:
            print(f"[MCP Retry] 第 {i} 次重试，放宽名称: '{name}'")

        result = await _call_mcp_tool(tool_name, args)
        last_result = result

        if _has_data(result):
            if i > 0:
                print(f"[MCP Retry] ✓ 重试成功！使用放宽名称 '{name}' 查到数据")
            return result
        else:
            print(f"[MCP Retry] ✗ 名称 '{name}' 未查到数据，"
                  f"结果预览: {str(result)[:150]}")

    # 所有重试均未查到数据
    print(f"[MCP Retry] {len(names_to_try)} 次尝试均未查到有效数据，返回最后一次结果")
    return last_result if last_result else json.dumps(
        {"error": f"查询无数据: 已尝试 {len(names_to_try)} 个名称变体，均未匹配到数据",
         "names_tried": names_to_try},
        ensure_ascii=False,
    )


async def query_mining_plan(mine_name: str, plan_month: str) -> str:
    """查询煤矿月度采掘计划。

    调用远程 MCP 工具 query_monthly_mining_plan，按照煤矿名称模糊匹配 +
    计划月份精确查询，返回：
    - 主表信息（矿井基本信息、计划编制日期等）
    - 生产准备任务列表
    - 巷道掘进明细列表（含巷道名称、设计长度、支护方式等）

    用于配风计划审查「用风地点完整性」检查：比对采掘计划中的工作面清单
    与配风计划中列出的用风地点是否一致。

    内置名称放宽重试：若首次查询无数据，依次尝试去除企业后缀、行业词汇后重试（最多 3 次）。

    Args:
        mine_name: 煤矿企业名称（模糊匹配），如 "合阳县金桥煤炭有限公司"
        plan_month: 计划月份，格式 yyyy-M 或 yyyy-MM，如 "2026-6" 或 "2026-06"（自动规范化为 yyyy-MM）

    Returns:
        JSON格式的采掘计划数据
    """
    plan_month = _normalize_plan_month(plan_month)
    return await _query_mine_with_retry(
        "query_monthly_mining_plan",
        mine_name,
        extra_args={"plan_month": plan_month},
    )


async def query_face_procedure(mine_name: str) -> str:
    """查询工作面作业规程。

    调用远程 MCP 工具 query_working_face_procedure，按照矿井名称模糊匹配，
    返回最近创建的一条作业规程记录（按 create_time 降序取第一条）。
    包含工作面参数：最大控顶距、最小控顶距、平均采高、工作面长度等。

    用于配风计划审查「工作面参数一致性」检查：比对作业规程中的参数
    与配风计划中该地点列出的参数是否一致。

    内置名称放宽重试：若首次查询无数据，依次尝试去除企业后缀、行业词汇后重试（最多 3 次）。

    Args:
        mine_name: 矿井名称（模糊匹配），如 "合阳县金桥煤炭有限公司"

    Returns:
        JSON格式的作业规程数据
    """
    return await _query_mine_with_retry(
        "query_working_face_procedure",
        mine_name,
    )


async def query_gas_report(mine_name: str, year: int) -> str:
    """查询瓦斯等级鉴定报告。

    调用远程 MCP 工具 query_gas_identification_report，按照矿井名称模糊匹配 +
    鉴定年度精确查询，关联查询：
    - 主表：矿井瓦斯等级、鉴定日期、审批意见等
    - 明细表：各工作面瓦斯参数（绝对瓦斯涌出量、相对瓦斯涌出量、
      绝对二氧化碳涌出量、相对二氧化碳涌出量等）

    用于配风计划审查「瓦斯与二氧化碳数据一致性」检查：
    校验配风计划中各用风地点的瓦斯/CO2 涌出量是否与鉴定报告一致。

    内置名称放宽重试：若首次查询无数据，依次尝试去除企业后缀、行业词汇后重试（最多 3 次）。

    Args:
        mine_name: 矿井名称（模糊匹配），如 "合阳县金桥煤炭有限公司"
        year: 鉴定年度（精确匹配），如 2026

    Returns:
        JSON格式的瓦斯等级鉴定数据
    """
    # 确保 year 是 int 类型
    year_int = int(year) if not isinstance(year, int) else year
    return await _query_mine_with_retry(
        "query_gas_identification_report",
        mine_name,
        extra_args={"year": year_int},
    )


async def query_wind_report(mine_name: str, report_month: str) -> str:
    """查询测风报表。

    调用远程 MCP 工具 query_ventilation_report，按照煤矿名称模糊匹配 +
    报表月份精确查询，关联查询：
    - 主表：报表基本信息（编制日期、审核人等）
    - 风井风机表：各风井风机运行参数
    - 测点表：各测风点数据（风速、风量、温度、瓦斯浓度等）

    用于配风计划审查「风速与温度数据匹配」检查：
    根据当月温度监测数据，核验配风计划中温度系数选择是否合理。

    内置名称放宽重试：若首次查询无数据，依次尝试去除企业后缀、行业词汇后重试（最多 3 次）。

    Args:
        mine_name: 煤矿名称（模糊匹配），如 "合阳县金桥煤炭有限公司"
        report_month: 报表月份，格式 yyyy-MM，如 "2026-06"（自动规范化为 yyyy-MM）

    Returns:
        JSON格式的测风报表数据
    """
    report_month = _normalize_plan_month(report_month)
    return await _query_mine_with_retry(
        "query_ventilation_report",
        mine_name,
        extra_args={"report_month": report_month},
    )