"""MCP HTTP client — communicates with MCP servers.

Provides async methods for invoking tools on MCP servers via HTTP transport.
Supports timeout, retry, and error handling.
"""

from __future__ import annotations

from dataclasses import dataclass, field

import httpx
import structlog

from proxym.observability import log_call
from proxym.mcp.registry import MCPServerSpec, MCPTransport

logger = structlog.get_logger()


@dataclass
class MCPToolResult:
    """Result of an MCP tool invocation."""

    success: bool
    content: str = ""
    error: str = ""
    server_name: str = ""
    tool_name: str = ""
    elapsed_ms: float = 0.0
    metadata: dict = field(default_factory=dict)


class MCPClient:
    """HTTP client for MCP server communication.

    Parameters
    ----------
    timeout : float
        Request timeout in seconds.
    max_retries : int
        Number of retries on transient failures.
    """

    def __init__(self, timeout: float = 30.0, max_retries: int = 1) -> None:
        self.timeout = timeout
        self.max_retries = max_retries

    @log_call(level="info")
    async def invoke_tool(
        self,
        server: MCPServerSpec,
        tool_name: str,
        arguments: dict | None = None,
    ) -> MCPToolResult:
        """Invoke a tool on an MCP server.

        Parameters
        ----------
        server : MCPServerSpec
            Target MCP server.
        tool_name : str
            Name of the tool to invoke.
        arguments : dict | None
            Tool arguments.
        """
        if server.transport != MCPTransport.HTTP:
            return MCPToolResult(
                success=False,
                error=f"Unsupported transport: {server.transport.value}",
                server_name=server.name,
                tool_name=tool_name,
            )

        payload = {
            "jsonrpc": "2.0",
            "method": "tools/call",
            "params": {
                "name": tool_name,
                "arguments": arguments or {},
            },
            "id": 1,
        }

        for attempt in range(self.max_retries + 1):
            result = await self._try_invoke(server, tool_name, payload, attempt)
            if result is not None:
                return result

        return MCPToolResult(
            success=False,
            error=f"Failed after {self.max_retries + 1} attempts",
            server_name=server.name,
            tool_name=tool_name,
        )

    async def _try_invoke(
        self,
        server: MCPServerSpec,
        tool_name: str,
        payload: dict,
        attempt: int,
    ) -> MCPToolResult | None:
        """Single attempt to invoke a tool. Returns None to signal retry."""
        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                import time
                t0 = time.monotonic()
                resp = await client.post(server.url, json=payload)
                elapsed = (time.monotonic() - t0) * 1000

                if resp.status_code == 200:
                    return self._parse_response(
                        resp.json(), server.name, tool_name, elapsed,
                    )
                logger.warning("mcp_tool_error", server=server.name,
                               tool=tool_name,
                               error=f"HTTP {resp.status_code}",
                               attempt=attempt)
        except httpx.TimeoutException:
            logger.warning("mcp_tool_timeout", server=server.name,
                           tool=tool_name, attempt=attempt)
        except Exception as e:
            logger.warning("mcp_tool_exception", server=server.name,
                           tool=tool_name, error=str(e)[:200], attempt=attempt)
        return None

    @staticmethod
    def _parse_response(
        data: dict, server_name: str, tool_name: str, elapsed_ms: float,
    ) -> MCPToolResult:
        """Parse a successful JSON-RPC response into MCPToolResult."""
        result = data.get("result", {})
        content = ""
        if isinstance(result, dict):
            content_field = result.get("content", [])
            if isinstance(content_field, list) and content_field:
                content = content_field[0].get("text", "")
            elif isinstance(content_field, str):
                content = content_field
        elif isinstance(result, str):
            content = result

        return MCPToolResult(
            success=True,
            content=content,
            server_name=server_name,
            tool_name=tool_name,
            elapsed_ms=elapsed_ms,
            metadata=data.get("result", {}),
        )
