"""Poll execution status by job_id only; updates ClientOperationState.

Does not use WebSocket or ws_id. When delivery.status_observable is false or
job_id is None, caller must not start this observer.

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

from __future__ import annotations

import asyncio
from typing import Any, cast

from mcp_proxy_adapter.client.jsonrpc_client.operation_state import ClientOperationState
from mcp_proxy_adapter.client.jsonrpc_client.transport import JsonRpcTransport


class ExecutionStatusObserver:
    """Fetches queue/execution status using job_id only; never uses ws_id."""

    def __init__(
        self,
        transport: JsonRpcTransport,
        job_id: str,
        state: ClientOperationState,
    ) -> None:
        """Store transport, job_id, state; validate job_id non-empty."""
        if not isinstance(job_id, str) or not job_id.strip():
            raise ValueError("job_id must be non-empty")
        self._transport = transport
        self._job_id = job_id.strip()
        self._state = state

    async def _call_status_rpc(self) -> dict[str, Any]:
        """Dispatch to queue_get_job_status or job_status per frozen rule."""
        qn = getattr(self._transport, "queue_get_job_status", None)
        if callable(qn):
            return cast("dict[str, Any]", await qn(self._job_id))
        jn = getattr(self._transport, "job_status", None)
        if callable(jn):
            return cast("dict[str, Any]", await jn(self._job_id))
        raise RuntimeError("transport has no queue_get_job_status or job_status")

    async def fetch_once(self) -> dict[str, Any]:
        """Call status RPC, apply execution fields to state, return raw response."""
        raw = await self._call_status_rpc()
        clean = dict(raw)
        clean.pop("ws_id", None)
        clean.pop("deliver_id", None)
        clean.pop("subscription_type", None)
        self._state.apply_execution_status(clean)
        return raw

    async def run_until_terminal(
        self,
        *,
        poll_interval: float,
        terminal_statuses: frozenset[str],
    ) -> None:
        """Poll until state.execution_status in terminal_statuses or cancelled."""
        while True:
            await self.fetch_once()
            if self._state.execution_status in terminal_statuses:
                return
            await asyncio.sleep(poll_interval)
