"""WardenClient — how the CLI / web / agent / CI reach the warden over the UDS.

If the warden is unreachable, the client **fails closed**: it raises rather than
running a test on its own, because executing outside the warden would defeat the
single entry point.
"""

from __future__ import annotations

import json
import socket
from pathlib import Path
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from .leases import RunRequest


class WardenUnreachable(RuntimeError):  # noqa: N818 — "...Unreachable" reads truer than "...Error"
    """The warden is down/unreachable — fail-closed; no run may start."""


class WardenClient:
    def __init__(self, socket_path: str | Path) -> None:
        self.socket_path = Path(socket_path)

    def _call(self, request: dict[str, Any]) -> dict[str, Any]:
        try:
            with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
                sock.settimeout(10)
                sock.connect(str(self.socket_path))
                sock.sendall((json.dumps(request) + "\n").encode("utf-8"))
                buf = b""
                while not buf.endswith(b"\n"):
                    chunk = sock.recv(4096)
                    if not chunk:
                        break
                    buf += chunk
        except OSError as exc:
            raise WardenUnreachable(
                f"test authority unreachable at {self.socket_path} — no runs can start (fail-closed)"
            ) from exc
        if not buf:
            raise WardenUnreachable("warden closed the connection without a response (fail-closed)")
        result: dict[str, Any] = json.loads(buf.decode("utf-8"))
        return result

    def ping(self) -> bool:
        try:
            return bool(self._call({"op": "ping"}).get("pong"))
        except WardenUnreachable:
            return False

    def run(self, request: RunRequest) -> dict[str, Any]:
        return self._call(
            {
                "op": "run",
                "run_id": request.run_id,
                "test_id": request.test_id,
                "config": request.config,
                "origin": request.origin,
                "who": request.who,
                "independent": request.independent,
            }
        )

    def status(self) -> dict[str, Any]:
        return self._call({"op": "status"})

    def cancel(self, run_id: str) -> dict[str, Any]:
        """Ask the warden to cancel a run (running lease or queued request)."""
        return self._call({"op": "cancel", "run_id": run_id})
