"""MCP Server - 将 Agent 能力暴露为 MCP 工具

基于 FastAPI 实现，支持 Streamable HTTP 传输。

U13: 重构为路由工厂 ``create_mcp_router()``，可挂载到主 FastAPI app
的 ``/api/v1/mcp/`` 前缀下，复用主 app 的认证中间件（JWT + API Key）。
旧的独立 ``MCPServer`` 类保留作为向后兼容（独立 port 8080 部署），
但在 ``_create_app()`` 时发出 DeprecationWarning。
"""

from __future__ import annotations

import logging
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, Request

from agentkit.server.auth.dependencies import require_permission
from agentkit.server.auth.permissions import Permission
from agentkit.tools.base import Tool

logger = logging.getLogger(__name__)

# MCP 端点要求至少 member 权限（CHAT）。API Key 与 JWT 均可。
# 认证由主 app 的 AuthMiddleware 处理，这里只做权限校验。
_mcp_member_auth = require_permission(Permission.CHAT)

# ponytail: 危险工具黑名单 — 这些工具依赖 chat confirmation 流程（WebSocket），
# 通过 MCP 暴露会绕过用户确认机制。黑名单需手动维护，新增危险工具需同步更新。
# 天花板：若工具名动态生成或别名注册，黑名单可能漏判。
# 升级路径：Tool 基类增加 requires_confirmation 元数据字段，按属性自动过滤。
# U5c: 与 publisher.py 的 _DANGEROUS_TOOL_NAMES 保持一致（单一真相源）。
_MCP_BLOCKED_TOOLS: frozenset[str] = frozenset(
    {
        "terminal",  # 终端执行（与 ShellTool 协同）— 危险命令需 confirmation
        "shell",  # ShellTool — 危险命令需 confirmation
        "file_write",  # 文件写入
        "file_read",  # 文件读取（可能泄露敏感配置）
        "file_delete",  # 文件删除
    }
)


def _serialize_tool(tool: Tool) -> dict[str, Any]:
    """将 Tool 序列化为 MCP 协议响应字典。"""
    return {
        "name": tool.name,
        "description": tool.description,
        "inputSchema": tool.input_schema or {},
    }


def create_mcp_router(
    tool_registry: Any = None,
    published_tools_getter: Any = None,
) -> APIRouter:
    """构造 MCP 路由，挂载到主 app 的 ``/api/v1/mcp/`` 前缀下。

    所有端点要求至少 member 权限（Permission.CHAT）。认证由主 app
    的 AuthMiddleware 处理（JWT + API Key 双轨）。

    Args:
        tool_registry: ToolRegistry 实例。若为 None，所有工具相关
            端点返回空列表或错误。
        published_tools_getter: U14 可选回调，返回通过发布端点注册的
            ``list[Tool]``。这些工具会与 ``tool_registry`` 中的工具合并
            后一起对外暴露。向后兼容：默认 None 时行为与 U13 一致。

    Returns:
        APIRouter，包含以下路由：
        - GET    /tools/list    — 列出 MCP 工具
        - POST   /tools/call    — 调用 MCP 工具
        - GET    /health        — 健康检查
        - POST   /              — JSON-RPC 2.0 端点（MCP 协议兼容）
    """
    router = APIRouter(tags=["mcp"])

    def _all_tools() -> list[Tool]:
        """合并 ToolRegistry 与已发布工具，过滤危险工具。"""
        tools: list[Tool] = []
        if tool_registry is not None:
            tools.extend(tool_registry.list_tools())
        if published_tools_getter is not None:
            try:
                tools.extend(published_tools_getter())
            except Exception:
                logger.exception("published_tools_getter failed")
        # 过滤危险工具 — 防止绕过 chat confirmation 流程
        return [t for t in tools if t.name not in _MCP_BLOCKED_TOOLS]

    def _find_tool(name: str) -> Tool | None:
        """按名查找工具（先 registry 后已发布），危险工具返回 None。"""
        if name in _MCP_BLOCKED_TOOLS:
            logger.warning(f"MCP tool '{name}' blocked — requires chat confirmation flow")
            return None
        if tool_registry is not None:
            try:
                return tool_registry.get(name)
            except Exception:
                pass
        if published_tools_getter is not None:
            for t in published_tools_getter():
                if t.name == name:
                    return t
        return None

    @router.get("/tools/list")
    async def list_tools(_user: dict = Depends(_mcp_member_auth)) -> dict[str, Any]:
        """列出所有可用的 MCP 工具。"""
        tools = _all_tools()
        return {"tools": [_serialize_tool(t) for t in tools]}

    @router.post("/tools/call")
    async def call_tool(
        request: dict,
        _user: dict = Depends(_mcp_member_auth),
    ) -> dict[str, Any]:
        """调用指定的 MCP 工具。"""
        tool_name = request.get("name")
        arguments = request.get("arguments", {})

        if not tool_name:
            raise HTTPException(
                status_code=400,
                detail="Tool not specified or registry not configured",
            )

        tool = _find_tool(tool_name)
        if tool is None:
            raise HTTPException(status_code=404, detail=f"Tool '{tool_name}' not found")

        try:
            result = await tool.safe_execute(**arguments)
            return {"content": [{"type": "text", "text": str(result)}]}
        except Exception as e:
            logger.exception(f"MCP tool '{tool_name}' execution failed")
            return {"isError": True, "content": [{"type": "text", "text": str(e)}]}

    @router.get("/health")
    async def health(_user: dict = Depends(_mcp_member_auth)) -> dict[str, str]:
        """MCP 健康检查。"""
        return {"status": "ok"}

    @router.post("/")
    async def jsonrpc_endpoint(
        request: Request,
        _user: dict = Depends(_mcp_member_auth),
    ) -> dict[str, Any]:
        """JSON-RPC 2.0 端点 — MCP 协议兼容。

        支持 methods: initialize, tools/list, tools/call。
        """
        try:
            body = await request.json()
        except Exception:
            return {
                "jsonrpc": "2.0",
                "error": {"code": -32700, "message": "Parse error"},
                "id": None,
            }

        method = body.get("method", "")
        params = body.get("params", {})
        req_id = body.get("id")

        if method == "initialize":
            result = {
                "protocolVersion": "2024-11-05",
                "capabilities": {"tools": {}},
                "serverInfo": {"name": "agentkit-mcp-server", "version": "2.0.0"},
            }
        elif method == "tools/list":
            tools = _all_tools()
            result = {"tools": [_serialize_tool(t) for t in tools]}
        elif method == "tools/call":
            tool_name = params.get("name", "")
            arguments = params.get("arguments", {})

            if not tool_name:
                result = {
                    "isError": True,
                    "content": [{"type": "text", "text": "Tool not found"}],
                }
            elif tool_name in _MCP_BLOCKED_TOOLS:
                # 显式黑名单检查 — 与 legacy JSON-RPC 一致，提供清晰审计反馈
                logger.warning(f"MCP tool '{tool_name}' blocked — requires chat confirmation flow")
                result = {
                    "isError": True,
                    "content": [{"type": "text", "text": f"Tool '{tool_name}' is blocked via MCP"}],
                }
            else:
                tool = _find_tool(tool_name)
                if tool is None:
                    result = {
                        "isError": True,
                        "content": [{"type": "text", "text": "Tool not found"}],
                    }
                else:
                    try:
                        tool_result = await tool.safe_execute(**arguments)
                        result = {"content": [{"type": "text", "text": str(tool_result)}]}
                    except Exception as e:
                        result = {
                            "isError": True,
                            "content": [{"type": "text", "text": str(e)}],
                        }
        else:
            return {
                "jsonrpc": "2.0",
                "error": {"code": -32601, "message": f"Method not found: {method}"},
                "id": req_id,
            }

        return {"jsonrpc": "2.0", "result": result, "id": req_id}

    return router


# Backward-compat: legacy MCPServer class
class MCPServer:
    """[DEPRECATED] 独立 MCP Server — 使用 create_mcp_router() 替代。

    保留用于向后兼容（独立 port 8080 部署，零认证 — 仅适合隔离网络）。
    将在下一版本移除。新部署应通过 :func:`create_mcp_router` 挂载到主 app
    的 ``/api/v1/mcp/`` 前缀下，复用主 app 的 JWT + API Key 认证。
    """

    def __init__(self, tool_registry: Any = None, host: str = "0.0.0.0", port: int = 8080):
        self._tool_registry = tool_registry
        self._host = host
        self._port = port
        self._app = None

    def _create_app(self):
        """创建 FastAPI 应用（无认证 — 仅向后兼容）。

        发出 :class:`DeprecationWarning` 提示迁移到 ``create_mcp_router()``。
        """
        import warnings

        warnings.warn(
            "MCPServer 独立 app 无认证保护，建议迁移到 create_mcp_router() "
            "并挂载到主 app 的 /api/v1/mcp/。详见 U13。",
            DeprecationWarning,
            stacklevel=2,
        )

        try:
            from fastapi import FastAPI
        except ImportError:
            raise ImportError("MCP Server requires fastapi: pip install fischer-agentkit[mcp]")

        app = FastAPI(title="Fischer AgentKit MCP Server")

        @app.get("/tools/list")
        async def list_tools():
            if self._tool_registry is None:
                return {"tools": []}
            tools = self._tool_registry.list_tools()
            # 过滤危险工具（与 create_mcp_router 一致）
            tools = [t for t in tools if t.name not in _MCP_BLOCKED_TOOLS]
            return {"tools": [_serialize_tool(t) for t in tools]}

        @app.post("/tools/call")
        async def call_tool(request: dict):
            tool_name = request.get("name")
            arguments = request.get("arguments", {})

            if not tool_name or self._tool_registry is None:
                return {"error": "Tool not specified or registry not configured"}

            if tool_name in _MCP_BLOCKED_TOOLS:
                return {"error": f"Tool '{tool_name}' is blocked via MCP"}

            try:
                tool = self._tool_registry.get(tool_name)
                result = await tool.safe_execute(**arguments)
                return {"content": [{"type": "text", "text": str(result)}]}
            except Exception as e:
                return {"isError": True, "content": [{"type": "text", "text": str(e)}]}

        @app.get("/health")
        async def health():
            return {"status": "ok"}

        @app.post("/")
        async def jsonrpc_endpoint(request: Request):
            """JSON-RPC 2.0 endpoint for MCP protocol compatibility.

            Handles requests from HTTPTransport which sends JSON-RPC format.
            """
            try:
                body = await request.json()
            except Exception:
                return {
                    "jsonrpc": "2.0",
                    "error": {"code": -32700, "message": "Parse error"},
                    "id": None,
                }

            method = body.get("method", "")
            params = body.get("params", {})
            req_id = body.get("id")

            if method == "initialize":
                result = {
                    "protocolVersion": "2024-11-05",
                    "capabilities": {"tools": {}},
                    "serverInfo": {"name": "agentkit-mcp-server", "version": "2.0.0"},
                }
            elif method == "tools/list":
                if self._tool_registry is None:
                    result = {"tools": []}
                else:
                    tools = self._tool_registry.list_tools()
                    result = {
                        "tools": [
                            _serialize_tool(t) for t in tools if t.name not in _MCP_BLOCKED_TOOLS
                        ]
                    }
            elif method == "tools/call":
                tool_name = params.get("name", "")
                arguments = params.get("arguments", {})

                if not tool_name or self._tool_registry is None:
                    result = {
                        "isError": True,
                        "content": [{"type": "text", "text": "Tool not found"}],
                    }
                elif tool_name in _MCP_BLOCKED_TOOLS:
                    result = {
                        "isError": True,
                        "content": [
                            {"type": "text", "text": f"Tool '{tool_name}' is blocked via MCP"}
                        ],
                    }
                else:
                    try:
                        tool = self._tool_registry.get(tool_name)
                        tool_result = await tool.safe_execute(**arguments)
                        result = {"content": [{"type": "text", "text": str(tool_result)}]}
                    except Exception as e:
                        result = {"isError": True, "content": [{"type": "text", "text": str(e)}]}
            else:
                return {
                    "jsonrpc": "2.0",
                    "error": {"code": -32601, "message": f"Method not found: {method}"},
                    "id": req_id,
                }

            response = {"jsonrpc": "2.0", "result": result, "id": req_id}
            return response

        return app

    async def start(self):
        """启动 MCP Server"""
        self._app = self._create_app()

        try:
            import uvicorn

            config = uvicorn.Config(self._app, host=self._host, port=self._port, log_level="info")
            server = uvicorn.Server(config)
            await server.serve()
        except ImportError:
            raise ImportError("MCP Server requires uvicorn: pip install uvicorn")

    def get_app(self):
        """获取 FastAPI 应用实例（用于测试或嵌入）"""
        if self._app is None:
            self._app = self._create_app()
        return self._app
