"""
Client tests for execute_async(command, params, on_result).

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

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

import pytest

from mcp_proxy_adapter.client.jsonrpc_client.client import JsonRpcClient


class TestExecuteAsyncClient:
    """Client execute_async behaviour (plan step 04)."""

    @pytest.mark.asyncio
    async def test_execute_async_invokes_on_result_with_completed(self) -> None:
        """execute_async returns accepted/deliver_id and callback called with job_completed."""
        client = JsonRpcClient(host="127.0.0.1", port=8080)
        received = []

        def on_result(payload: dict) -> None:
            received.append(payload)

        async def fake_rpc(method: str, params: dict) -> dict:
            return {
                "jsonrpc": "2.0",
                "result": {
                    "accepted": True,
                    "deliver_id": params["deliver_id"],
                    "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_job_via_websocket",
                new_callable=AsyncMock,
            ) as mock_wait:
                mock_wait.return_value = {
                    "event": "job_completed",
                    "job_id": "test-id",
                    "result": {"success": True, "data": {"message": "test"}},
                }
                result = await client.execute_async(
                    "echo", {"message": "test"}, on_result=on_result
                )
                await asyncio.sleep(0.15)

        assert result["accepted"] is True
        assert result["deliver_id"]
        assert len(received) == 1
        assert received[0].get("event") == "job_completed"
        assert "result" in received[0]

    @pytest.mark.asyncio
    async def test_execute_async_callback_receives_failed_on_error(self) -> None:
        """When command fails, callback is invoked with job_failed."""
        client = JsonRpcClient(host="127.0.0.1", port=8080)
        received = []

        def on_result(payload: dict) -> None:
            received.append(payload)

        async def fake_rpc(method: str, params: dict) -> dict:
            return {
                "jsonrpc": "2.0",
                "result": {
                    "accepted": True,
                    "deliver_id": params["deliver_id"],
                    "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_job_via_websocket",
                new_callable=AsyncMock,
            ) as mock_wait:
                mock_wait.return_value = {
                    "event": "job_failed",
                    "job_id": "test-id",
                    "error": "Command failed",
                    "result": {},
                }
                result = await client.execute_async(
                    "failing_cmd", {}, on_result=on_result
                )
                await asyncio.sleep(0.15)

        assert result["accepted"] is True
        assert len(received) == 1
        assert received[0].get("event") == "job_failed"
        assert received[0].get("error") == "Command failed"

    @pytest.mark.asyncio
    async def test_execute_async_returns_immediately(self) -> None:
        """Method returns within 1s even if command is long-running."""
        client = JsonRpcClient(host="127.0.0.1", port=8080)
        callback = MagicMock()

        async def fake_rpc(method: str, params: dict) -> dict:
            return {
                "jsonrpc": "2.0",
                "result": {
                    "accepted": True,
                    "deliver_id": params["deliver_id"],
                    "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_job_via_websocket",
                new_callable=AsyncMock,
            ) as mock_wait:

                async def slow_wait(*args: object, **kwargs: object) -> dict:
                    await asyncio.sleep(5.0)
                    return {"event": "job_completed", "result": {}}

                mock_wait.side_effect = slow_wait

                t0 = time.monotonic()
                result = await client.execute_async(
                    "long_command", {}, on_result=callback
                )
                elapsed = time.monotonic() - t0

        assert result["accepted"] is True
        assert elapsed < 1.0

    @pytest.mark.skip(reason="timeout callback payload not yet implemented")
    @pytest.mark.asyncio
    async def test_execute_async_timeout(self) -> None:
        """When no push within timeout, callback receives timeout payload (if implemented)."""
        pass
