"""MCP Publisher — 将 Skill / 专家团队发布为 MCP 工具

U14: 支持把已注册的 Skill 或专家团队封装成 ``Tool``，注册到
``PublisherRegistry`` 后即可通过 MCP 端点（``/api/v1/mcp/tools/list``、
``/tools/call``、JSON-RPC）对外暴露，供外部系统调用。

设计要点（lazy MVP）：
- ``SkillMCPAdapter`` / ``TeamMCPAdapter`` 通过可选的 ``executor``
  回调执行真正的技能/团队逻辑；未配置时返回错误 dict，不阻塞发布。
- 危险工具（terminal/shell/file_*）默认拒绝发布，需显式 ``allow_dangerous=True``
  才能放行——防止把高危能力意外暴露给外部系统。
"""

from __future__ import annotations

import logging
from typing import Any, Awaitable, Callable

from agentkit.skills.base import Skill
from agentkit.tools.base import Tool

logger = logging.getLogger(__name__)

# 危险工具名集合——这些工具若被发布到 MCP，外部系统可直接操作终端/文件系统，
# 故默认拒绝发布，需管理员显式 opt-in。ponytail: 集合较小，硬编码即可。
_DANGEROUS_TOOL_NAMES: frozenset[str] = frozenset(
    {"terminal", "shell", "file_write", "file_read", "file_delete"}
)

# 执行器签名：(skill_or_team_name, input_text) -> 结果 dict
SkillExecutor = Callable[[str, str], Awaitable[dict[str, Any]]]


class PublisherRegistry:
    """已发布 MCP 工具的内存注册表。

    进程内注册表——重启后丢失（MVP 不持久化，足够当前场景）。
    按工具名去重，重复注册抛 ``ValueError``。
    """

    def __init__(self) -> None:
        self._tools: dict[str, Tool] = {}

    def register(self, tool: Tool) -> None:
        """注册一个已发布工具。同名工具已存在则抛 ValueError。"""
        if tool.name in self._tools:
            raise ValueError(f"Tool '{tool.name}' already published")
        self._tools[tool.name] = tool
        logger.info(f"MCP tool published: {tool.name}")

    def unregister(self, name: str) -> bool:
        """注销已发布工具。成功返回 True，不存在返回 False。"""
        if name in self._tools:
            del self._tools[name]
            logger.info(f"MCP tool unpublished: {name}")
            return True
        return False

    def list_published(self) -> list[Tool]:
        """返回所有已发布工具（按注册顺序）。"""
        return list(self._tools.values())

    def get(self, name: str) -> Tool | None:
        """按名获取已发布工具，不存在返回 None。"""
        return self._tools.get(name)


class SkillMCPAdapter(Tool):
    """将 Skill 封装为 MCP 工具。

    工具名格式：``skill_{skill.name}``。
    真正的技能执行通过 ``executor`` 回调完成；未配置时 execute 返回错误信息。
    """

    def __init__(
        self,
        skill: Skill,
        executor: SkillExecutor | None = None,
    ) -> None:
        super().__init__(
            name=f"skill_{skill.name}",
            description=skill.config.description or f"Skill: {skill.name}",
            input_schema={
                "type": "object",
                "properties": {"input": {"type": "string", "description": "技能输入"}},
                "required": ["input"],
            },
            tags=["skill", skill.name],
        )
        self._skill = skill
        self._executor = executor

    async def execute(self, **kwargs: Any) -> dict[str, Any]:
        """调用 executor 执行技能；未配置或异常时返回错误 dict。"""
        input_text = kwargs.get("input", "")
        if self._executor is None:
            return {"error": f"skill '{self._skill.name}' executor not configured"}
        try:
            return await self._executor(self._skill.name, input_text)
        except Exception as e:
            return {"error": f"skill execution failed: {e}"}


class TeamMCPAdapter(Tool):
    """将专家团队封装为 MCP 工具。

    工具名格式：``team_{team_name}``。
    """

    def __init__(
        self,
        team_name: str,
        executor: SkillExecutor | None = None,
        description: str | None = None,
    ) -> None:
        super().__init__(
            name=f"team_{team_name}",
            description=description or f"Expert team: {team_name}",
            input_schema={
                "type": "object",
                "properties": {"input": {"type": "string", "description": "团队任务输入"}},
                "required": ["input"],
            },
            tags=["team", team_name],
        )
        self._team_name = team_name
        self._executor = executor

    async def execute(self, **kwargs: Any) -> dict[str, Any]:
        """调用 executor 执行团队任务；未配置或异常时返回错误 dict。"""
        input_text = kwargs.get("input", "")
        if self._executor is None:
            return {"error": f"team '{self._team_name}' executor not configured"}
        try:
            return await self._executor(self._team_name, input_text)
        except Exception as e:
            return {"error": f"team execution failed: {e}"}


def check_dangerous_publish(skill: Skill, allow_dangerous: bool) -> None:
    """检查 Skill 绑定的工具是否包含危险工具。

    若包含危险工具且未显式 ``allow_dangerous=True``，抛 ``ValueError``。
    """
    dangerous = [t.name for t in skill.tools if t.name in _DANGEROUS_TOOL_NAMES]
    if dangerous and not allow_dangerous:
        raise ValueError(
            f"Skill '{skill.name}' contains dangerous tools {dangerous}; "
            "publish requires allow_dangerous=True"
        )
