"""MCP Tools Integration Module.

Provides functionality to connect to the rossum-mcp server and convert MCP tools
to Anthropic tool format for use with the Claude API.
"""

from __future__ import annotations

import logging
import os
from contextlib import asynccontextmanager
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Literal

from anthropic.types import ToolParam
from fastmcp import Client
from fastmcp.client.transports import StdioTransport

if TYPE_CHECKING:
    from collections.abc import AsyncIterator

    from mcp.types import Tool as MCPTool

logger = logging.getLogger(__name__)


@dataclass
class MCPConnection:
    """Holds the MCP client and provides tool operations."""

    client: Client
    _tools: list[MCPTool] | None = None

    async def get_tools(self) -> list[MCPTool]:
        """Get the list of available MCP tools (cached)."""
        if self._tools is None:
            self._tools = await self.client.list_tools()
        return self._tools

    async def call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> Any:
        """Call an MCP tool by name with the given arguments.

        Args:
            name: The name of the tool to call.
            arguments: Optional dictionary of arguments to pass to the tool.

        Returns:
            The result of the tool call.
        """
        logger.info(f"Calling MCP tool {name}")

        result = await self.client.call_tool(name, arguments or {})
        # Prefer structured_content (raw dict) over data (parsed pydantic model)
        # because FastMCP's json_schema_to_type has a bug where nested dict fields
        # like config: dict[str, Any] become empty dataclasses, losing all data.
        if result.structured_content is not None:
            return result.structured_content
        if result.data is not None:
            return result.data
        if result.content:
            text_parts = [str(block.text) for block in result.content if hasattr(block, "text") and block.text]
            if len(text_parts) == 1:
                return text_parts[0]
            return "\n".join(text_parts) if text_parts else None
        return None


def create_mcp_transport(
    rossum_api_token: str, rossum_api_base_url: str, mcp_mode: Literal["read-only", "read-write"] = "read-only"
) -> StdioTransport:
    """Create a StdioTransport for the rossum-mcp server.

    Args:
        rossum_api_token: Rossum API token for authentication.
        rossum_api_base_url: Rossum API base URL.

    Returns:
        Configured StdioTransport for the rossum-mcp server.
    """
    return StdioTransport(
        command="rossum-mcp",
        args=[],
        env={
            **os.environ,
            "ROSSUM_API_BASE_URL": rossum_api_base_url.rstrip("/"),
            "ROSSUM_API_TOKEN": rossum_api_token,
            "ROSSUM_MCP_MODE": mcp_mode,
        },
    )


@asynccontextmanager
async def connect_mcp_server(
    rossum_api_token: str, rossum_api_base_url: str, mcp_mode: Literal["read-only", "read-write"] = "read-only"
) -> AsyncIterator[MCPConnection]:
    """Connect to the rossum-mcp server and yield an MCPConnection.

    This context manager handles the lifecycle of the MCP client connection.
    Tools are cached after the first retrieval for efficiency.

    Args:
        rossum_api_token: Rossum API token for authentication.
        rossum_api_base_url: Rossum API base URL.

    Yields:
        MCPConnection with the connected client.
    """
    transport = create_mcp_transport(
        rossum_api_token=rossum_api_token, rossum_api_base_url=rossum_api_base_url, mcp_mode=mcp_mode
    )

    client = Client(transport)
    async with client:
        yield MCPConnection(client=client)


def mcp_tools_to_anthropic_format(mcp_tools: list[MCPTool]) -> list[ToolParam]:
    """Convert MCP tools to Anthropic tool format."""
    return [
        ToolParam(name=mcp_tool.name, description=mcp_tool.description or "", input_schema=mcp_tool.inputSchema)
        for mcp_tool in mcp_tools
    ]
