"""Client execute_async orchestration state tests (fakes for RPC and WS).

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

from __future__ import annotations

import asyncio
from unittest.mock import AsyncMock, patch

import pytest

from mcp_proxy_adapter.client.jsonrpc_client.client import JsonRpcClient


@pytest.mark.asyncio
async def test_orchestration_accepted_then_callback_with_fake_ws() -> None:
    """execute_async: accepted response then single callback (orchestration state)."""
    client = JsonRpcClient(host="127.0.0.1", port=8080)
    call_order: list[str] = []

    def on_result(payload: dict) -> None:
        call_order.append("callback")
        assert payload.get("event") in ("job_completed", "delivery_completed")

    async def fake_rpc(method: str, params: dict) -> dict:
        call_order.append("rpc")
        return {
            "jsonrpc": "2.0",
            "result": {
                "accepted": True,
                "ws_id": "fake-ws-id",
                "delivery": {"mode": "ws", "terminal_only": True},
                "message": "OK",
            },
            "id": 1,
        }

    with patch.object(
        client, "jsonrpc_call", new_callable=AsyncMock, side_effect=fake_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(*args: object, **kwargs: object) -> dict:
                call_order.append("ws_wait")
                await asyncio.sleep(0.05)
                return {
                    "event": "delivery_completed",
                    "ws_id": "fake-ws-id",
                    "result": {},
                }

            mock_wait.side_effect = delayed

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

    assert result["accepted"] is True
    assert result["ws_id"] == "fake-ws-id"
    assert call_order == ["rpc", "ws_wait", "callback"]


@pytest.mark.asyncio
async def test_orchestration_no_callback_before_accepted() -> None:
    """Callback is not invoked before acceptance (fake delays RPC)."""
    client = JsonRpcClient(host="127.0.0.1", port=8080)
    call_order: list[str] = []

    def on_result(payload: dict) -> None:
        call_order.append("callback")

    async def slow_rpc(method: str, params: dict) -> dict:
        call_order.append("rpc")
        await asyncio.sleep(0.1)
        return {
            "jsonrpc": "2.0",
            "result": {
                "accepted": True,
                "ws_id": "ws-1",
                "delivery": {"mode": "ws", "terminal_only": True},
                "message": "OK",
            },
            "id": 1,
        }

    async def delayed_ws(*args: object, **kwargs: object) -> dict:
        await asyncio.sleep(0.15)
        return {"event": "delivery_completed", "ws_id": "ws-1", "result": {}}

    with patch.object(
        client, "jsonrpc_call", new_callable=AsyncMock, side_effect=slow_rpc
    ):
        with patch(
            "mcp_proxy_adapter.client.jsonrpc_client.command_api.wait_for_terminal_delivery_via_websocket",
            new_callable=AsyncMock,
            side_effect=delayed_ws,
        ):
            result = await client.execute_async(
                "echo", {"message": "x"}, on_result=on_result
            )

    assert result["accepted"] is True
    assert "rpc" in call_order
    assert "callback" in call_order
    assert call_order.index("rpc") < call_order.index("callback")
