"""Orchestration tests for execute_async (contract-driven WS and modes).

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

import asyncio
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from mcp_proxy_adapter.client.jsonrpc_client.client import JsonRpcClient
class TestMainControllerExecuteAsync:
    """Tests for execute_async orchestration ordering and mode branching."""

    @pytest.mark.asyncio
    async def test_execute_async_subscribes_after_rpc(self) -> None:
        """execute_async awaits jsonrpc_call before invoking wait_for_terminal_delivery_via_websocket."""
        client = JsonRpcClient(host="127.0.0.1", port=8080)
        callback = MagicMock()
        ws_id = "ws-1"
        job_id = "job-1"

        # Track call order
        call_order: list[str] = []

        async def fake_rpc(method: str, params: dict) -> dict:
            call_order.append("jsonrpc_call")
            assert method == "execute_async"
            assert "deliver_id" not in params
            assert "command" in params
            assert "params" in params
            await asyncio.sleep(0.1)  # Simulate network delay
            return {
                "jsonrpc": "2.0",
                "result": {
                    "accepted": True,
                    "ws_id": ws_id,
                    "job_id": job_id,
                    "delivery": {"mode": "ws", "terminal_only": True},
                    "message": "Command accepted",
                },
                "id": 1,
            }

        with patch.object(
            client, "jsonrpc_call", new_callable=AsyncMock, side_effect=fake_rpc
        ) as mock_rpc:
            with patch(
                "mcp_proxy_adapter.client.jsonrpc_client.command_api.wait_for_terminal_delivery_via_websocket",
                new_callable=AsyncMock,
            ) as mock_wait:

                async def delayed_terminal(*args: object, **kwargs: object) -> dict:
                    call_order.append("wait_for_terminal_delivery_via_websocket")
                    await asyncio.sleep(0.1)
                    return {
                        "event": "delivery_completed",
                        "ws_id": ws_id,
                        "result": {"success": True, "data": {}},
                    }

                mock_wait.side_effect = delayed_terminal

                result = await client.execute_async(
                    "echo", {"message": "test"}, on_result=callback
                )

                # Verify RPC was called first
                assert call_order[0] == "jsonrpc_call"
                assert call_order[1] == "wait_for_terminal_delivery_via_websocket"

                # Verify RPC call details
                mock_rpc.assert_called_once_with(
                    "execute_async", {"command": "echo", "params": {"message": "test"}}
                )

                # Verify WS wait was called with correct args
                mock_wait.assert_called_once()
                call_args = mock_wait.call_args
                assert call_args[0][0] == client  # First arg is client
                assert call_args[0][1] == ws_id  # Second arg is ws_id

                # Verify result structure
                assert result["accepted"] is True
                assert result["ws_id"] == ws_id
                assert "deliver_id" not in result

                # Wait for callback
                await asyncio.sleep(0.3)
                callback.assert_called_once()

    @pytest.mark.asyncio
    async def test_no_ws_when_mode_poll(self) -> None:
        """execute_async with poll mode does not call wait_for_terminal_delivery_via_websocket."""
        client = JsonRpcClient(host="127.0.0.1", port=8080)
        callback = MagicMock()
        job_id = "job-1"

        with patch.object(client, "jsonrpc_call", new_callable=AsyncMock) as mock_rpc:
            mock_rpc.return_value = {
                "jsonrpc": "2.0",
                "result": {
                    "accepted": True,
                    "job_id": job_id,
                    "delivery": {"mode": "poll", "terminal_only": False},
                    "message": "Command accepted for polling",
                },
                "id": 1,
            }
            with patch(
                "mcp_proxy_adapter.client.jsonrpc_client.command_api.wait_for_terminal_delivery_via_websocket",
                new_callable=AsyncMock,
            ) as mock_wait:

                result = await client.execute_async(
                    "echo", {"message": "test"}, on_result=callback
                )

                # Verify WS wait was NOT called
                mock_wait.assert_not_called()

                # Verify result
                assert result["accepted"] is True
                assert result["job_id"] == job_id

                # Wait for background task to complete
                await asyncio.sleep(0.2)

                # Verify callback was called with synthetic terminal
                # Note: This requires s01 background task implementation.
                # Current implementation may not call callback for immediate mode yet.
                if callback.called:
                    callback.assert_called_once()
                    callback_payload = callback.call_args[0][0]
                    assert "event" in callback_payload or "result" in callback_payload

    @pytest.mark.asyncio
    async def test_ws_id_not_inferred_from_job_id(self) -> None:
        """execute_async raises ValueError when ws_id missing for WS mode."""
        client = JsonRpcClient(host="127.0.0.1", port=8080)
        callback = MagicMock()
        job_id = "job-1"

        with patch.object(client, "jsonrpc_call", new_callable=AsyncMock) as mock_rpc:
            # Acceptance with WS mode but missing ws_id
            mock_rpc.return_value = {
                "jsonrpc": "2.0",
                "result": {
                    "accepted": True,
                    "job_id": job_id,
                    # ws_id is missing
                    "delivery": {"mode": "ws", "terminal_only": True},
                    "message": "Command accepted",
                },
                "id": 1,
            }
            with patch(
                "mcp_proxy_adapter.client.jsonrpc_client.command_api.wait_for_terminal_delivery_via_websocket",
                new_callable=AsyncMock,
            ) as mock_wait:

                # Server contract: WS mode requires ws_id (validated in parse_acceptance_payload)
                with pytest.raises(ValueError, match="ws_id"):
                    await client.execute_async(
                        "echo", {"message": "test"}, on_result=callback
                    )

                # Verify WS wait was NOT called (error raised before WS subscription)
                mock_wait.assert_not_called()

                # Verify callback was NOT called
                callback.assert_not_called()
