#!/usr/bin/env python3
"""
lzwcai-mcpskills-generate-reports MCP Server

把 docx 模板渲染引擎封装成 MCP 工具，提供三个工具：
- generate_report:      数据 + 模板路径 -> 渲染输出 docx
- scan_template:        扫描模板占位符 / for / if 块
- validate_report_data: 校验数据契约（不渲染）

stdio 模式运行；所有日志走 stderr，stdout 留给 MCP 协议。
"""
import os
import json
import logging

import anyio

import mcp.types as types
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server

try:
    from .utils.logger_config import setup_system_logging, get_logger
    from . import generate, scan_template, validate, normalize
except ImportError:
    from lzwcai_mcpskills_generate_reports.utils.logger_config import (
        setup_system_logging, get_logger,
    )
    from lzwcai_mcpskills_generate_reports import (
        generate, scan_template, validate, normalize,
    )

# 初始化日志系统
setup_system_logging(app_name="lzwcai_mcpskills_generate_reports", log_level=logging.DEBUG)
logger = get_logger(__name__)

# 初始化 MCP Server
server = Server("lzwcai_mcpskills_generate_reports")


def _load_data(data):
    """把 data 归一化为 dict：支持 dict 或 JSON 文件路径字符串。"""
    if isinstance(data, str):
        if not os.path.isfile(data):
            raise FileNotFoundError(f"数据文件不存在: {data}")
        with open(data, "r", encoding="utf-8") as f:
            data = json.load(f)
    if not isinstance(data, dict):
        raise TypeError(
            f"data 必须是对象或 JSON 文件路径字符串，实际类型: {type(data).__name__}"
        )
    return data


# ── 工具定义 ──────────────────────────────────────────────
_DATA_DESC = "报价数据：可以是 JSON 对象，或指向 JSON 文件的路径字符串"

TOOL_DEFS = [
    types.Tool(
        name="generate_report",
        description=(
            "用 docx 模板 + 结构化数据渲染生成报价文档 docx，返回输出文件绝对路径。"
            "模板由调用方提供，本工具不内置模板。"
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "template": {
                    "type": "string",
                    "description": "模板 docx 文件路径（必填）",
                },
                "data": {
                    "type": ["object", "string"],
                    "description": _DATA_DESC + "（必填）",
                },
                "out": {
                    "type": "string",
                    "description": "输出 docx 文件路径（必填）",
                },
                "style_ref": {
                    "type": "string",
                    "description": "用户上传的样式参考 docx 路径（可选），会把其 theme/字体套到结果文档",
                },
            },
            "required": ["template", "data", "out"],
        },
    ),
    types.Tool(
        name="scan_template",
        description=(
            "扫描 docx 模板中的 Jinja2 占位符，返回需要外部提供的顶层变量列表和 for/if 块结构。"
            "用于在渲染前了解模板需要哪些数据字段。"
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "template": {
                    "type": "string",
                    "description": "模板 docx 文件路径（必填）",
                },
            },
            "required": ["template"],
        },
    ),
    types.Tool(
        name="validate_report_data",
        description=(
            "校验报价数据是否符合契约（必填字段、equipments/quote_items/features/params 结构），"
            "不渲染文档。返回校验结果和错误列表。"
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "data": {
                    "type": ["object", "string"],
                    "description": _DATA_DESC + "（必填）",
                },
            },
            "required": ["data"],
        },
    ),
]


@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    """列出所有可用工具"""
    logger.info(f"收到 ListTools 请求，返回 {len(TOOL_DEFS)} 个工具")
    return TOOL_DEFS


# ── 工具实现（同步函数，放线程池执行）──────────────────────
def _do_generate_report(arguments: dict) -> dict:
    template = arguments["template"]
    data = arguments["data"]
    out = arguments["out"]
    style_ref = arguments.get("style_ref")

    data = _load_data(data)
    out_path = generate(data=data, template=template, out_path=out, style_ref=style_ref)
    return {"output": out_path}


def _do_scan_template(arguments: dict) -> dict:
    return scan_template(arguments["template"])


def _do_validate(arguments: dict) -> dict:
    data = _load_data(arguments["data"])
    norm = normalize(data)
    errors = validate(norm)
    return {"valid": not errors, "errors": errors}


_HANDLERS = {
    "generate_report": _do_generate_report,
    "scan_template": _do_scan_template,
    "validate_report_data": _do_validate,
}


@server.call_tool()
async def handle_call_tool(
    name: str,
    arguments: dict | None,
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
    """调用工具"""
    logger.info(
        f"收到 CallTool 请求: name={name}, "
        f"arguments={json.dumps(arguments, ensure_ascii=False) if arguments else 'None'}"
    )

    handler = _HANDLERS.get(name)
    if handler is None:
        logger.error(f"未找到工具: {name}")
        raise ValueError(f"未知工具: {name}")

    args = arguments or {}
    try:
        # 渲染/扫描是同步阻塞的 CPU/IO 任务，放线程池避免霸占 event loop
        result = await anyio.to_thread.run_sync(handler, args)
        logger.info(f"工具执行成功: {name}")
    except Exception as e:
        logger.error(f"工具执行失败: {name}: {e}", exc_info=True)
        result = {"error": str(e), "tool_name": name}

    return [
        types.TextContent(
            type="text",
            text=json.dumps(result, ensure_ascii=False, indent=2),
        )
    ]


async def run_server():
    """运行 MCP Server (stdio 模式)"""
    async with stdio_server() as streams:
        await server.run(
            streams[0],
            streams[1],
            InitializationOptions(
                server_name="lzwcai_mcpskills_generate_reports",
                server_version="0.1.0",
                capabilities=server.get_capabilities(
                    notification_options=NotificationOptions(),
                    experimental_capabilities={},
                ),
            ),
        )


def main():
    """主入口"""
    logger.info("=" * 50)
    logger.info("lzwcai-mcpskills-generate-reports MCP Server 启动")
    logger.info("=" * 50)
    logger.info("开始运行 MCP Server (stdio 模式)")
    anyio.run(run_server)


if __name__ == "__main__":
    main()
