"""Tests for the A2A protocol and MCP bridge."""

from __future__ import annotations

import pytest

from a2a_mesh.exceptions import JsonRpcError, ProtocolError
from a2a_mesh.protocol.a2a import (
    A2AClient,
    build_jsonrpc_error,
    build_jsonrpc_response,
)
from a2a_mesh.protocol.mcp import MCPBridge, MCPToolDefinition


class TestJsonRpcHelpers:
    """Tests for JSON-RPC helper functions."""

    def test_build_success_response(self) -> None:
        resp = build_jsonrpc_response(1, {"data": "hello"})
        assert resp["jsonrpc"] == "2.0"
        assert resp["id"] == 1
        assert resp["result"]["data"] == "hello"

    def test_build_error_response(self) -> None:
        resp = build_jsonrpc_error(1, -32600, "Invalid request")
        assert resp["jsonrpc"] == "2.0"
        assert resp["id"] == 1
        assert resp["error"]["code"] == -32600
        assert resp["error"]["message"] == "Invalid request"

    def test_build_error_null_id(self) -> None:
        resp = build_jsonrpc_error(None, -32700, "Parse error")
        assert resp["id"] is None


class TestA2AClient:
    """Tests for A2AClient."""

    def test_initialization(self) -> None:
        client = A2AClient("http://localhost:9001")
        assert client.base_url == "http://localhost:9001"
        assert client.timeout == 30.0

    def test_trailing_slash_stripped(self) -> None:
        client = A2AClient("http://localhost:9001/")
        assert client.base_url == "http://localhost:9001"

    def test_parse_response_success(self) -> None:
        client = A2AClient("http://localhost:9001")
        result = client._parse_response({
            "jsonrpc": "2.0",
            "id": 1,
            "result": {"status": "ok"},
        })
        assert result["status"] == "ok"

    def test_parse_response_error(self) -> None:
        client = A2AClient("http://localhost:9001")
        with pytest.raises(JsonRpcError):
            client._parse_response({
                "jsonrpc": "2.0",
                "id": 1,
                "error": {"code": -32600, "message": "Bad request"},
            })

    def test_parse_response_missing_result(self) -> None:
        client = A2AClient("http://localhost:9001")
        with pytest.raises(ProtocolError, match="missing result"):
            client._parse_response({"jsonrpc": "2.0", "id": 1})

    def test_request_id_increments(self) -> None:
        client = A2AClient("http://localhost:9001")
        id1 = client._next_id()
        id2 = client._next_id()
        assert id2 == id1 + 1

    @pytest.mark.asyncio
    async def test_close(self) -> None:
        client = A2AClient("http://localhost:9001")
        await client.close()  # should not raise


class TestMCPBridge:
    """Tests for MCPBridge."""

    def test_register_server(self) -> None:
        bridge = MCPBridge()
        bridge.register_server("tools", "http://localhost:3000")
        assert "tools" in bridge.servers
        assert bridge.servers["tools"] == "http://localhost:3000"

    def test_trailing_slash_stripped(self) -> None:
        bridge = MCPBridge()
        bridge.register_server("tools", "http://localhost:3000/")
        assert bridge.servers["tools"] == "http://localhost:3000"

    @pytest.mark.asyncio
    async def test_discover_unregistered_server_raises(self) -> None:
        bridge = MCPBridge()
        with pytest.raises(ProtocolError, match="not registered"):
            await bridge.discover_tools("nonexistent")

    @pytest.mark.asyncio
    async def test_call_unregistered_tool_raises(self) -> None:
        bridge = MCPBridge()
        with pytest.raises(ProtocolError, match="not registered"):
            await bridge.call_tool("nonexistent_tool")

    def test_tool_definition(self) -> None:
        tool = MCPToolDefinition(
            name="web_search",
            description="Search the web",
            input_schema={"type": "object"},
            server_url="http://localhost:3000",
        )
        assert tool.name == "web_search"
        assert tool.server_url == "http://localhost:3000"

    @pytest.mark.asyncio
    async def test_close(self) -> None:
        bridge = MCPBridge()
        await bridge.close()  # should not raise
