import logging
from typing import Any

from callite.client.rpc_client import RPCClient
from callite.mcp.bridge import _format_result, _make_prompt_handler, _make_tool_handler
from callite.mcp.schema import mcp_schema_to_parameters

try:
    from mcp.server.fastmcp import FastMCP
except ImportError:
    FastMCP = None  # type: ignore


logger = logging.getLogger(__name__)


class MCPHTTPProxy:
    """MCP HTTP server that exposes a single callite RPCClient over Streamable HTTP.

    Discovers the remote service's tools, prompts, and resources via the
    callite discovery protocol (``__describe__``, ``list_resources``,
    ``list_resource_templates``) and registers them on a FastMCP instance.
    Tool names are NOT prefixed with the service name.

    Example::

        client = RPCClient("redis://localhost:6379", "my_service")
        proxy = MCPHTTPProxy(client, host="0.0.0.0", port=8080)
        proxy.run()
    """

    def __init__(
        self,
        client: RPCClient,
        *,
        host: str = "127.0.0.1",
        port: int = 8000,
        name: str = "callite-http-proxy",
    ):
        """
        Args:
            client: An already-constructed RPCClient connected to the target service.
            host: Host address for the HTTP server. Defaults to 127.0.0.1.
            port: TCP port for the HTTP server. Defaults to 8000.
            name: MCP server display name passed to FastMCP.
        """
        if FastMCP is None:
            raise ImportError(
                "MCPHTTPProxy requires the 'mcp' package. "
                "Install it with: pip install callite[mcp]"
            )

        self._client = client
        self._host = host
        self._port = port
        self._mcp = FastMCP(name, host=host, port=port)

    def _discover(self) -> None:
        """Fetch tool/prompt/resource metadata from the RPCClient and register them."""
        try:
            description = self._client.execute('__describe__')
            tools = description.get('tools', [])
            prompts = description.get('prompts', [])
            logger.info(
                "Discovered service '%s': %d tools, %d prompts",
                description.get('service', '?'),
                len(tools),
                len(prompts),
            )
            for tool_def in tools:
                self._register_tool(tool_def)
            for prompt_def in prompts:
                self._register_prompt(prompt_def)
        except Exception:
            logger.exception("Failed to call __describe__ on client")

        try:
            resources = self._client.execute('list_resources')
            for resource_def in resources or []:
                self._register_resource(resource_def)
        except Exception:
            logger.debug("Client does not support list_resources")

        try:
            templates = self._client.execute('list_resource_templates')
            for template_def in templates or []:
                self._register_resource_template(template_def)
        except Exception:
            logger.debug("Client does not support list_resource_templates")

    def _register_tool(self, tool_def: dict) -> None:
        method_name = tool_def['name']
        description = tool_def.get('description', '')
        parameters = tool_def.get('parameters', {})

        if not parameters and tool_def.get('input_schema'):
            parameters = mcp_schema_to_parameters(tool_def['input_schema'])

        handler = _make_tool_handler(self._client, method_name, parameters, description)
        self._mcp.tool(name=method_name, description=description)(handler)
        logger.debug("Registered MCP tool: %s", method_name)

    def _register_prompt(self, prompt_def: dict) -> None:
        prompt_name = prompt_def['name']
        description = prompt_def.get('description', '')
        arguments = prompt_def.get('arguments', [])
        rpc_name = f"__prompt_{prompt_name}"

        handler = _make_prompt_handler(self._client, rpc_name, arguments, description)
        self._mcp.prompt(name=prompt_name, description=description)(handler)
        logger.debug("Registered MCP prompt: %s", prompt_name)

    def _register_resource(self, resource_def: dict) -> None:
        uri = resource_def.get('uri', '')
        method_name = resource_def.get('name', '')
        description = resource_def.get('description', '')

        if not uri or not method_name:
            return

        def make_handler(c: RPCClient, m: str, u: str) -> Any:
            def handler() -> str:
                result = c.execute('read_resource', u)
                contents = result.get('contents', []) if isinstance(result, dict) else []
                if contents:
                    return contents[0].get('text', _format_result(result))
                return _format_result(result)
            handler.__name__ = f"resource_{m}"
            handler.__doc__ = description
            return handler

        fn = make_handler(self._client, method_name, uri)
        self._mcp.resource(uri, description=description)(fn)
        logger.debug("Registered MCP resource: %s", uri)

    def _register_resource_template(self, template_def: dict) -> None:
        uri_template = template_def.get('uriTemplate', '')
        method_name = template_def.get('name', '')
        description = template_def.get('description', '')

        if not uri_template or not method_name:
            return

        def make_handler(c: RPCClient, u_template: str) -> Any:
            def handler(uri: str) -> str:
                result = c.execute('read_resource', uri)
                contents = result.get('contents', []) if isinstance(result, dict) else []
                if contents:
                    return contents[0].get('text', _format_result(result))
                return _format_result(result)
            handler.__name__ = f"template_{method_name}"
            handler.__doc__ = description
            return handler

        fn = make_handler(self._client, uri_template)
        self._mcp.resource(uri_template, description=description)(fn)
        logger.debug("Registered MCP resource template: %s", uri_template)

    def run(self) -> None:
        """Discover remote capabilities, register them, and start the MCP HTTP server.

        Blocks until the server is stopped. Serves at /mcp using Streamable HTTP.
        """
        self._discover()
        self._mcp.run(transport="streamable-http")

    def run_forever(self) -> None:
        """Alias for run() — matches RPCServer.run_forever() naming convention."""
        self.run()
