from __future__ import annotations

import json
from importlib import import_module
from typing import Any

from ace.integrations.mcp.handlers import MCPHandlers
from ace.integrations.mcp.models import (
    AskRequest,
    LearnFeedbackRequest,
    LearnSampleRequest,
    SkillbookGetRequest,
    SkillbookLoadRequest,
    SkillbookSaveRequest,
)
from ace.integrations.mcp.errors import map_error_to_mcp

_MCP_INSTALL_HINT = (
    "ACE MCP support is optional. Install it with "
    '`pip install "ace-framework[mcp]"` or `uv add "ace-framework[mcp]"`.'
)


def _load_mcp_types():
    try:
        return import_module("mcp.types")
    except ModuleNotFoundError as exc:
        if (exc.name or "").split(".")[0] == "mcp":
            raise RuntimeError(_MCP_INSTALL_HINT) from exc
        raise


def _mcp_schema(model: Any) -> dict[str, Any]:
    """Return an MCP-friendly JSON schema for a Pydantic model.

    Some MCP clients (e.g. the Inspector) don't resolve ``$defs``/``$ref``
    correctly and reject valid input.  This helper inlines all ``$ref``
    pointers so the schema is self-contained.

    We keep ``additionalProperties: false`` in the published schema so
    clients know that extra fields will be rejected by validation.
    """
    schema = model.model_json_schema()
    defs = schema.pop("$defs", {})

    def _resolve(obj: Any) -> Any:
        if isinstance(obj, dict):
            if "$ref" in obj:
                ref_name = obj["$ref"].rsplit("/", 1)[-1]
                return _resolve(defs.get(ref_name, {}))
            return {k: _resolve(v) for k, v in obj.items()}
        if isinstance(obj, list):
            return [_resolve(item) for item in obj]
        return obj

    return _resolve(schema)


# ── Tool dispatch table ────────────────────────────────────────────

_TOOL_DISPATCH: dict[str, tuple[type, str]] = {
    "ace.ask": (AskRequest, "handle_ask"),
    "ace.learn.sample": (LearnSampleRequest, "handle_learn_sample"),
    "ace.learn.feedback": (LearnFeedbackRequest, "handle_learn_feedback"),
    "ace.skillbook.get": (SkillbookGetRequest, "handle_skillbook_get"),
    "ace.skillbook.save": (SkillbookSaveRequest, "handle_skillbook_save"),
    "ace.skillbook.load": (SkillbookLoadRequest, "handle_skillbook_load"),
}


def register_tools(server: Any, handlers: MCPHandlers) -> None:
    types = _load_mcp_types()

    @server.list_tools()
    async def handle_list_tools():
        return [
            types.Tool(
                name="ace.ask",
                description="Ask a question and get a response from ACE.",
                inputSchema=_mcp_schema(AskRequest),
            ),
            types.Tool(
                name="ace.learn.sample",
                description="Provide sample questions/answers for ACE to learn from.",
                inputSchema=_mcp_schema(LearnSampleRequest),
            ),
            types.Tool(
                name="ace.learn.feedback",
                description="Provide feedback on an ACE answer.",
                inputSchema=_mcp_schema(LearnFeedbackRequest),
            ),
            types.Tool(
                name="ace.skillbook.get",
                description="Get statistics and skills from the active skillbook.",
                inputSchema=_mcp_schema(SkillbookGetRequest),
            ),
            types.Tool(
                name="ace.skillbook.save",
                description="Save the active skillbook to disk.",
                inputSchema=_mcp_schema(SkillbookSaveRequest),
            ),
            types.Tool(
                name="ace.skillbook.load",
                description="Load a skillbook from disk into the session.",
                inputSchema=_mcp_schema(SkillbookLoadRequest),
            ),
        ]

    @server.call_tool()
    async def handle_call_tool(name: str, arguments: dict | None):
        args = arguments or {}
        try:
            entry = _TOOL_DISPATCH.get(name)
            if entry is None:
                raise ValueError(f"Unknown tool: {name}")

            request_cls, handler_method = entry
            req = request_cls(**args)
            resp = await getattr(handlers, handler_method)(req)
            return types.CallToolResult(
                content=[types.TextContent(type="text", text=resp.model_dump_json())],
            )

        except Exception as e:
            mcp_err = map_error_to_mcp(e)
            return types.CallToolResult(
                isError=True,
                content=[types.TextContent(type="text", text=json.dumps(mcp_err))],
            )
