from __future__ import annotations

import json
import urllib.error
import urllib.request
from typing import Any

from .models import McpToolDescriptor, McpToolResult


class McpClient:
    def __init__(self, endpoint: str, token: str = "") -> None:
        self.endpoint = endpoint
        self.token = token
        self._request_id = 1

    def status(self) -> Any:
        request = urllib.request.Request(self.endpoint, headers=self._headers(), method="GET")
        return self._send(request)

    def list_tools(self) -> list[McpToolDescriptor]:
        result = self._rpc("tools/list", {})
        tools = result.get("tools") if isinstance(result, dict) else None
        return tools if isinstance(tools, list) else []

    def call_tool(self, name: str, arguments: dict[str, Any]) -> McpToolResult:
        return self._rpc("tools/call", {"name": name, "arguments": arguments})

    def _rpc(self, method: str, params: dict[str, Any]) -> Any:
        payload = {
            "jsonrpc": "2.0",
            "id": self._request_id,
            "method": method,
            "params": params,
        }
        self._request_id += 1
        data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        request = urllib.request.Request(self.endpoint, data=data, headers=self._headers(), method="POST")
        response = self._send(request)
        if not isinstance(response, dict):
            raise RuntimeError("MCP returned invalid JSON-RPC payload")
        if response.get("error"):
            error = response["error"]
            if isinstance(error, dict):
                raise RuntimeError(str(error.get("message") or "MCP call failed"))
            raise RuntimeError(str(error))
        if "result" not in response:
            raise RuntimeError("MCP returned no result")
        return response["result"]

    def _send(self, request: urllib.request.Request) -> Any:
        try:
            with urllib.request.urlopen(request, timeout=45) as response:
                raw = response.read().decode("utf-8")
                return json.loads(raw) if raw else {}
        except urllib.error.HTTPError as error:
            raw = error.read().decode("utf-8", errors="replace")
            try:
                payload = json.loads(raw)
            except json.JSONDecodeError:
                payload = {}
            message = ""
            if isinstance(payload, dict):
                rpc_error = payload.get("error")
                if isinstance(rpc_error, dict):
                    message = str(rpc_error.get("message") or "")
                message = message or str(payload.get("message") or "")
            raise RuntimeError(message or f"MCP HTTP {error.code}") from error
        except urllib.error.URLError as error:
            raise RuntimeError(f"MCP connection failed: {error.reason}") from error

    def _headers(self) -> dict[str, str]:
        headers = {"Content-Type": "application/json"}
        token = self.token.strip()
        if token:
            headers["Authorization"] = f"Bearer {token}"
            headers["X-MCP-Token"] = token
        return headers
