import platform
from dataclasses import dataclass, field

import mcp

from astrbot.api import FunctionTool
from astrbot.core.agent.run_context import ContextWrapper
from astrbot.core.agent.tool import ToolExecResult
from astrbot.core.astr_agent_context import AstrAgentContext, AstrMessageEvent
from astrbot.core.computer.computer_client import get_booter, get_local_booter
from astrbot.core.message.message_event_result import MessageChain

from ..registry import builtin_tool
from .util import check_admin_permission, workspace_root

_OS_NAME = platform.system()
_SANDBOX_PYTHON_TOOL_CONFIG = {
    "provider_settings.computer_use_runtime": "sandbox",
}
_LOCAL_PYTHON_TOOL_CONFIG = {
    "provider_settings.computer_use_runtime": "local",
}

param_schema = {
    "type": "object",
    "properties": {
        "code": {
            "type": "string",
            "description": "The Python code to execute.",
        },
        "silent": {
            "type": "boolean",
            "description": "Whether to suppress the output of the code execution.",
            "default": False,
        },
        "timeout": {
            "type": "integer",
            "description": "Optional timeout in seconds for code execution.",
            "default": 30,
        },
    },
    "required": ["code"],
}


async def handle_result(result: dict, event: AstrMessageEvent) -> ToolExecResult:
    data = result.get("data", {})
    output = data.get("output", {})
    error = data.get("error", "")
    images: list[dict] = output.get("images", [])
    text: str = output.get("text", "")

    resp = mcp.types.CallToolResult(content=[])

    if error:
        resp.content.append(mcp.types.TextContent(type="text", text=f"error: {error}"))

    if images:
        for img in images:
            resp.content.append(
                mcp.types.ImageContent(
                    type="image", data=img["image/png"], mimeType="image/png"
                )
            )

            if event.get_platform_name() == "webchat":
                await event.send(message=MessageChain().base64_image(img["image/png"]))
    if text:
        resp.content.append(mcp.types.TextContent(type="text", text=text))

    if not resp.content:
        resp.content.append(mcp.types.TextContent(type="text", text="No output."))

    return resp


@builtin_tool(config=_SANDBOX_PYTHON_TOOL_CONFIG)
@dataclass
class PythonTool(FunctionTool):
    name: str = "astrbot_execute_ipython"
    description: str = f"Run codes in an IPython shell. Current OS: {_OS_NAME}."
    parameters: dict = field(default_factory=lambda: param_schema)

    async def call(
        self,
        context: ContextWrapper[AstrAgentContext],
        code: str,
        silent: bool = False,
        timeout: int = 30,
    ) -> ToolExecResult:
        if permission_error := check_admin_permission(context, "Python execution"):
            return permission_error
        sb = await get_booter(
            context.context.context,
            context.context.event.unified_msg_origin,
        )
        effective_timeout = (
            min(timeout, context.tool_call_timeout)
            if timeout > 0
            else context.tool_call_timeout
        )
        try:
            result = await sb.python.exec(
                code,
                timeout=effective_timeout,
                silent=silent,
            )
            return await handle_result(result, context.context.event)
        except Exception as e:
            return f"Error executing code: {str(e)}"


@builtin_tool(config=_LOCAL_PYTHON_TOOL_CONFIG)
@dataclass
class LocalPythonTool(FunctionTool):
    name: str = "astrbot_execute_python"
    description: str = (
        f"Execute codes in a Python environment. Current OS: {_OS_NAME}. "
        "Use system-compatible commands."
    )

    parameters: dict = field(default_factory=lambda: param_schema)

    async def call(
        self,
        context: ContextWrapper[AstrAgentContext],
        code: str,
        silent: bool = False,
        timeout: int = 30,
    ) -> ToolExecResult:
        if permission_error := check_admin_permission(context, "Python execution"):
            return permission_error
        sb = get_local_booter()
        effective_timeout = (
            min(timeout, context.tool_call_timeout)
            if timeout > 0
            else context.tool_call_timeout
        )
        try:
            current_workspace_root = workspace_root(
                context.context.event.unified_msg_origin
            )
            current_workspace_root.mkdir(parents=True, exist_ok=True)
            result = await sb.python.exec(
                code,
                timeout=effective_timeout,
                silent=silent,
                cwd=str(current_workspace_root),
            )
            return await handle_result(result, context.context.event)
        except Exception as e:
            return f"Error executing code: {str(e)}"
