"""
Contract tests: JsonRpcClient execute_async RPC params and ws_id binding (T03/T05).

Author: Vasiliy Zdanovskiy
email: vasilyvz@gmail.com
"""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from mcp_proxy_adapter.client.jsonrpc_client.client import JsonRpcClient


class TestJsonRpcClientExecuteAsyncContract:
    """execute_async must not send deliver_id; must use server ws_id for wait."""

    @pytest.mark.asyncio
    async def test_rpc_params_exclude_deliver_id_key(self) -> None:
        """RPC params passed to jsonrpc_call must not contain deliver_id."""
        client = JsonRpcClient(host="127.0.0.1", port=8080)
        captured: list[dict] = []

        async def spy_rpc(method: str, params: dict) -> dict:
            captured.append(params)
            return {
                "jsonrpc": "2.0",
                "result": {
                    "accepted": True,
                    "job_id": None,
                    "ws_id": "ws-ok",
                    "delivery": {"mode": "ws", "terminal_only": True},
                    "message": "OK",
                },
                "id": 1,
            }

        with patch.object(
            client, "jsonrpc_call", new_callable=AsyncMock, side_effect=spy_rpc
        ):
            with patch(
                "mcp_proxy_adapter.client.jsonrpc_client.command_api.wait_for_terminal_delivery_via_websocket",
                new_callable=AsyncMock,
                return_value={
                    "event": "delivery_completed",
                    "ws_id": "ws-ok",
                    "result": {},
                },
            ):
                await client.execute_async(
                    "echo", {"message": "x"}, on_result=MagicMock()
                )

        assert len(captured) >= 1
        assert "deliver_id" not in captured[0]

    @pytest.mark.asyncio
    async def test_wait_receives_server_ws_id(self) -> None:
        """wait_for_terminal_delivery_via_websocket must be called with server-returned ws_id."""
        client = JsonRpcClient(host="127.0.0.1", port=8080)
        ws_from_server = "ws-from-server"

        with patch.object(client, "jsonrpc_call", new_callable=AsyncMock) as mock_rpc:
            mock_rpc.return_value = {
                "jsonrpc": "2.0",
                "result": {
                    "accepted": True,
                    "job_id": None,
                    "ws_id": ws_from_server,
                    "delivery": {"mode": "ws", "terminal_only": True},
                    "message": "OK",
                },
                "id": 1,
            }
            with patch(
                "mcp_proxy_adapter.client.jsonrpc_client.command_api.wait_for_terminal_delivery_via_websocket",
                new_callable=AsyncMock,
                return_value={
                    "event": "delivery_completed",
                    "ws_id": ws_from_server,
                    "result": {},
                },
            ) as mock_wait:
                await client.execute_async(
                    "echo", {"message": "hi"}, on_result=MagicMock()
                )

        assert mock_wait.await_count >= 1
        assert mock_wait.call_args[0][1] == ws_from_server

    @pytest.mark.asyncio
    async def test_legacy_only_deliver_id_maps_to_ws_id(self) -> None:
        """Legacy deliver_id is mapped to ws_id; client subscribes with that id."""
        client = JsonRpcClient(host="127.0.0.1", port=8080)

        with patch.object(client, "jsonrpc_call", new_callable=AsyncMock) as mock_rpc:
            mock_rpc.return_value = {
                "jsonrpc": "2.0",
                "result": {
                    "accepted": True,
                    "job_id": None,
                    "deliver_id": "legacy-only",
                    "delivery": {"mode": "ws", "terminal_only": True},
                    "message": "OK",
                },
                "id": 1,
            }
            with patch(
                "mcp_proxy_adapter.client.jsonrpc_client.command_api.wait_for_terminal_delivery_via_websocket",
                new_callable=AsyncMock,
                return_value={
                    "event": "delivery_completed",
                    "ws_id": "legacy-only",
                    "result": {},
                },
            ) as mock_wait:
                await client.execute_async(
                    "echo", {"message": "x"}, on_result=MagicMock()
                )

        mock_wait.assert_awaited()
        assert mock_wait.call_args[0][1] == "legacy-only"
