"""Unit tests for ExecutionStatusObserver (job_id-only status, no ws_id).

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

from __future__ import annotations

import asyncio
from unittest.mock import AsyncMock, MagicMock

import pytest

from mcp_proxy_adapter.client.jsonrpc_client.execution_status_observer import (
    ExecutionStatusObserver,
)
from mcp_proxy_adapter.client.jsonrpc_client.operation_state import (
    ClientOperationState,
)
from mcp_proxy_adapter.core.delivery.contracts import DeliveryMode


def _make_state() -> ClientOperationState:
    return ClientOperationState(
        job_id="job-123",
        ws_id="ws-456",
        delivery_mode=DeliveryMode.WS_WITH_STATUS,
        execution_status=None,
        delivery_status="pending",
        result=None,
        error=None,
    )


@pytest.mark.asyncio
async def test_fetch_once_calls_queue_get_job_status() -> None:
    transport = MagicMock()
    transport.queue_get_job_status = AsyncMock(
        return_value={"success": True, "status": "running", "progress": 50}
    )
    state = _make_state()
    observer = ExecutionStatusObserver(transport, "job-123", state)
    result = await observer.fetch_once()
    assert result == {"success": True, "status": "running", "progress": 50}
    transport.queue_get_job_status.assert_called_once_with("job-123")
    assert state.execution_status == "running"


@pytest.mark.asyncio
async def test_apply_execution_status_no_ws_id() -> None:
    transport = MagicMock()
    transport.queue_get_job_status = AsyncMock(
        return_value={
            "status": "completed",
            "ws_id": "stray-ws",
            "progress": 100,
        }
    )
    state = _make_state()
    initial_ws_id = state.ws_id
    observer = ExecutionStatusObserver(transport, "job-123", state)
    await observer.fetch_once()
    assert state.execution_status == "completed"
    assert state.ws_id == initial_ws_id


@pytest.mark.asyncio
async def test_run_until_terminal_stops() -> None:
    calls = []

    async def status_rpc(*args: object, **kwargs: object) -> dict:
        calls.append(1)
        if len(calls) >= 2:
            return {"status": "completed"}
        return {"status": "running"}

    transport = MagicMock()
    transport.queue_get_job_status = AsyncMock(side_effect=status_rpc)
    state = _make_state()
    observer = ExecutionStatusObserver(transport, "job-123", state)
    await observer.run_until_terminal(
        poll_interval=0.01,
        terminal_statuses=frozenset({"completed", "failed", "stopped"}),
    )
    assert state.execution_status == "completed"
    assert len(calls) == 2


@pytest.mark.asyncio
async def test_cancel_stops_observer() -> None:
    transport = MagicMock()
    transport.queue_get_job_status = AsyncMock(return_value={"status": "running"})
    state = _make_state()
    observer = ExecutionStatusObserver(transport, "job-123", state)
    task = asyncio.create_task(
        observer.run_until_terminal(
            poll_interval=10.0,
            terminal_statuses=frozenset({"completed"}),
        )
    )
    await asyncio.sleep(0.05)
    task.cancel()
    with pytest.raises(asyncio.CancelledError):
        await task


def test_empty_job_id_raises() -> None:
    transport = MagicMock()
    state = _make_state()
    with pytest.raises(ValueError) as exc_info:
        ExecutionStatusObserver(transport, "  ", state)
    assert "non-empty" in str(exc_info.value)
