"""Unit tests for the worldtree-sdk adapter (`ratatoskr.wt`) — slice-1 foundation.

Covers the two foundation surfaces (issue #20 cutover contract, slice 1):
  * `build_client` — construction wiring + injected-transport ownership (INV-CUT-1:
    the SDK must never close ratatoskr's transport).
  * `translate_error` — the § Error map DEFAULT (`ApiError` → `SessionApiFailed`)
    plus discriminated-`WorldtreeError` passthrough (INV-CUT-2).

No ratatoskr surface (CLI / web / TUI) is exercised here — that wiring lands in
slice 2. These tests hit no network (WorldtreeClient does no I/O at construction).
"""

from __future__ import annotations

from typing import Any, cast

import httpx
import pytest
import worldtree_sdk as wtsdk
from worldtree_sdk import ApiError, CancelResult, PadState, WorldtreeClient

from ratatoskr.sessions import (
    AgentNotAvailable as PersonaAgentNotAvailable,
)
from ratatoskr.sessions import (
    AgentNotFound,
    AuthoredHistoryUnavailable,
    AuthScopeDenied,
    BifrostBinding,
    BifrostConsumerKeyMissing,
    BifrostHandshakeFailed,
    InvalidCursor,
    PersonaNotConfigured,
    Tier3AgentNotFound,
    Tier3FieldNotMutable,
    Tier3LayerDeferred,
    Tier3QuotaExceeded,
    Tier3UserIdUnsupported,
)
from ratatoskr.sse_client import (
    AdminEvent,
    AgentNotAvailable,
    CancelAlreadyCompleted,
    CancelFailed,
    CancelTurnNotFound,
    MalformedSseData,
    MalformedSseId,
    SseConnectFailed,
    SseConnectionDropped,
    SseResponseTooLarge,
    TurnIdFlip,
    TurnLaunchUnavailable,
)
from ratatoskr.wt import (
    SessionApiFailed,
    build_client,
    cancel_turn,
    create_character,
    create_session,
    define_agent,
    delete_agent,
    delete_character,
    get_capabilities,
    get_character_state,
    get_me,
    get_persona_state,
    get_session_bifrost,
    get_session_messages,
    get_session_tools,
    list_agents,
    list_character_models,
    list_sessions,
    patch_agent,
    set_persona_state,
    stream_admin_events,
    stream_turn,
    translate_error,
    write_authored_history,
)


class _FakeSessions:
    """A stand-in for `WorldtreeClient.sessions` — records the last call and
    returns a canned result or raises a canned error. Lets the adapter's
    body-building + error-mapping be unit-tested without any SDK HTTP."""

    def __init__(
        self,
        *,
        result: Any = None,
        error: BaseException | None = None,
        events: list[Any] | None = None,
        stream_error: BaseException | None = None,
    ) -> None:
        self._result = result
        self._error = error
        self._events = events or []
        self._stream_error = stream_error
        self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = []

    async def _dispatch(self, name: str, *args: Any, **kwargs: Any) -> Any:
        self.calls.append((name, args, kwargs))
        if self._error is not None:
            raise self._error
        return self._result

    async def create(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("create", *args, **kwargs)

    async def list(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("list", *args, **kwargs)

    async def messages(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("messages", *args, **kwargs)

    async def tools(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("tools", *args, **kwargs)

    def stream_turn(self, *args: Any, **kwargs: Any) -> Any:
        self.calls.append(("stream_turn", args, kwargs))
        return self._astream()

    async def _astream(self) -> Any:
        for event in self._events:
            yield event
        if self._stream_error is not None:
            raise self._stream_error

    async def cancel_turn(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("cancel_turn", *args, **kwargs)

    async def set_persona_state(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("set_persona_state", *args, **kwargs)

    async def write_history(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("write_history", *args, **kwargs)


class _FakeClient:
    def __init__(self, sessions: _FakeSessions) -> None:
        self.sessions = sessions


def _wt(sessions: _FakeSessions) -> WorldtreeClient:
    """Cast the structural fake to the nominal client type the adapter is typed
    against — the route functions only touch `client.sessions.*`, which the fake
    provides. (No network; construction does no I/O.)"""
    return cast(WorldtreeClient, _FakeClient(sessions))


class TestBuildClient:
    async def test_constructs_worldtree_client(self) -> None:
        transport = httpx.AsyncClient()
        try:
            client = build_client(
                "https://wt.example:8081", api_key="ck-test", transport=transport
            )
            assert isinstance(client, WorldtreeClient)
            assert client.base_url == "https://wt.example:8081"
        finally:
            await transport.aclose()

    async def test_injected_transport_is_ratatoskr_owned(self) -> None:
        # INV-CUT-1 [hard]: aclose() on the SDK client must NOT close ratatoskr's
        # transport — ratatoskr owns the lifecycle exactly as it does today.
        transport = httpx.AsyncClient()
        client = build_client("https://wt.example", api_key="ck", transport=transport)
        await client.aclose()
        assert client.closed is True
        assert transport.is_closed is False
        await transport.aclose()

    async def test_admin_key_optional(self) -> None:
        transport = httpx.AsyncClient()
        try:
            # Absent admin_key → admin_auth=None; still constructs.
            without_admin = build_client(
                "https://wt.example", api_key="ck", transport=transport
            )
            assert isinstance(without_admin, WorldtreeClient)
            # Present admin_key → constructs (admin surface available in later slices).
            with_admin = build_client(
                "https://wt.example", api_key="ck", admin_key="ak", transport=transport
            )
            assert isinstance(with_admin, WorldtreeClient)
        finally:
            await transport.aclose()


class TestTranslateError:
    def test_apierror_maps_to_session_api_failed_default(self) -> None:
        exc = ApiError("some_code", "boom", status=500, body="raw-body")
        mapped = translate_error(exc)
        assert isinstance(mapped, SessionApiFailed)
        assert mapped.status == 500
        assert mapped.error_code == "some_code"
        assert mapped.body == "raw-body"

    def test_apierror_with_no_body_maps_cleanly(self) -> None:
        exc = ApiError("nope", "no body", status=404)
        mapped = translate_error(exc)
        assert isinstance(mapped, SessionApiFailed)
        assert mapped.status == 404
        assert mapped.error_code == "nope"
        assert mapped.body is None

    def test_discriminated_subclass_passes_through_unchanged(self) -> None:
        # Discriminated WorldtreeError subclasses are already the right semantic
        # type at the REST layer — translate_error passes them through by identity
        # (the stream routes re-wrap them; that is stream_turn's job, not this one).
        exc = wtsdk.AgentNotAvailable("agent_not_available", "gone", status=409)
        assert translate_error(exc) is exc

    def test_non_worldtree_error_passes_through_unchanged(self) -> None:
        exc = ValueError("unrelated")
        assert translate_error(exc) is exc


class TestCreateSession:
    async def test_happy_returns_sdk_dict_and_builds_body(self) -> None:
        info = {"session_id": "s-1", "agent_id": "mimir", "created_at": "t", "last_active": "t"}
        fake = _FakeSessions(result=info)
        client = _wt(fake)
        out = await create_session(client, "mimir", end_user_id="u-9")
        assert out is info  # open-world passthrough — no re-shaping
        name, args, kwargs = fake.calls[-1]
        assert name == "create"
        assert args[0] == {"agent_id": "mimir", "end_user_id": "u-9"}
        assert kwargs["consumer_key"] is None

    async def test_config_passthrough(self) -> None:
        fake = _FakeSessions(result={"session_id": "s"})
        await create_session(
            _wt(fake), "echo", config={"system_prompt": "be terse"}
        )
        assert fake.calls[-1][1][0] == {
            "agent_id": "echo",
            "config": {"system_prompt": "be terse"},
        }

    async def test_bifrost_bound_body_and_consumer_key(self) -> None:
        fake = _FakeSessions(result={"session_id": "s"})
        binding = BifrostBinding(endpoint_url="http://h:8391", scope=None)
        await create_session(
            _wt(fake), "sindra", bifrost=binding, consumer_key="ck-real"
        )
        _name, args, kwargs = fake.calls[-1]
        assert args[0] == {
            "agent_id": "sindra",
            "bifrost": {"endpoint_url": "http://h:8391", "scope": None},
        }
        # INV-CUT: the consumer key rides the SDK's per-request auth, NOT a header.
        assert kwargs["consumer_key"] == "ck-real"

    async def test_unbound_create_drops_consumer_key(self) -> None:
        # A consumer_key must NOT reach the SDK on an UNBOUND create — the SDK's
        # credential precedence would otherwise auth as the Bifrost consumer instead
        # of the default bearer (heid-bug-hunt Gróa#4 / Regin#4).
        fake = _FakeSessions(result={"session_id": "s"})
        await create_session(_wt(fake), "mimir", consumer_key="ck-should-be-dropped")
        assert fake.calls[-1][2]["consumer_key"] is None

    async def test_bifrost_without_consumer_key_rejected_pre_http(self) -> None:
        fake = _FakeSessions(result={"session_id": "s"})
        binding = BifrostBinding(endpoint_url="http://h:8391", scope=None)
        with pytest.raises(BifrostConsumerKeyMissing):
            await create_session(_wt(fake), "sindra", bifrost=binding)
        assert fake.calls == []  # never reached the SDK

    async def test_404_maps_to_agent_not_found(self) -> None:
        fake = _FakeSessions(error=ApiError("agent_not_found", "no", status=404))
        with pytest.raises(AgentNotFound) as ei:
            await create_session(_wt(fake), "ghost")
        assert ei.value.agent_id == "ghost"

    async def test_bound_502_maps_to_bifrost_handshake_failed(self) -> None:
        body = '{"detail": {"bifrost_error": "bifrost.auth_rejected"}}'
        fake = _FakeSessions(
            error=ApiError("bifrost_handshake_failed", "boom", status=502, body=body)
        )
        binding = BifrostBinding(endpoint_url="http://h:8391", scope=None)
        with pytest.raises(BifrostHandshakeFailed) as ei:
            await create_session(
                _wt(fake), "sindra", bifrost=binding, consumer_key="ck"
            )
        assert ei.value.bifrost_error == "bifrost.auth_rejected"

    async def test_unbound_502_stays_session_api_failed(self) -> None:
        fake = _FakeSessions(error=ApiError("upstream", "boom", status=502, body="x"))
        with pytest.raises(SessionApiFailed) as ei:
            await create_session(_wt(fake), "mimir")
        assert ei.value.status == 502

    async def test_default_error_maps_to_session_api_failed(self) -> None:
        fake = _FakeSessions(error=ApiError("weird", "boom", status=418, body="teapot"))
        with pytest.raises(SessionApiFailed) as ei:
            await create_session(_wt(fake), "mimir")
        assert ei.value.status == 418
        assert ei.value.error_code == "weird"


class TestListSessions:
    async def test_passes_params_and_returns_dict(self) -> None:
        page: dict[str, Any] = {"items": [], "next_cursor": None}
        fake = _FakeSessions(result=page)
        out = await list_sessions(_wt(fake), limit=10, cursor="c1", include_archived=True)
        assert out is page
        kwargs = fake.calls[-1][2]
        assert kwargs["limit"] == 10
        assert kwargs["cursor"] == "c1"
        assert kwargs["include_archived"] is True

    async def test_422_cursor_invalid_maps_to_invalid_cursor(self) -> None:
        fake = _FakeSessions(error=ApiError("cursor_invalid", "bad", status=422))
        with pytest.raises(InvalidCursor) as ei:
            await list_sessions(_wt(fake), cursor="bogus")
        assert ei.value.raw == "bogus"

    async def test_other_422_stays_session_api_failed(self) -> None:
        fake = _FakeSessions(error=ApiError("validation_failed", "x", status=422))
        with pytest.raises(SessionApiFailed):
            await list_sessions(_wt(fake))


class TestReadPassthroughs:
    async def test_messages_returns_dict(self) -> None:
        data = {"session_id": "s", "items": []}
        fake = _FakeSessions(result=data)
        assert await get_session_messages(_wt(fake), "s") is data
        assert fake.calls[-1][0] == "messages"

    async def test_tools_returns_dict(self) -> None:
        data = {"agent_id": "mimir", "builtin_tools": []}
        fake = _FakeSessions(result=data)
        assert await get_session_tools(_wt(fake), "s") is data
        assert fake.calls[-1][0] == "tools"

    async def test_messages_error_maps_to_session_api_failed(self) -> None:
        fake = _FakeSessions(error=ApiError("auth_revoked", "no", status=401))
        with pytest.raises(SessionApiFailed) as ei:
            await get_session_messages(_wt(fake), "s")
        assert ei.value.status == 401

    async def test_tools_error_maps_to_session_api_failed(self) -> None:
        fake = _FakeSessions(error=ApiError("auth_revoked", "no", status=401))
        with pytest.raises(SessionApiFailed) as ei:
            await get_session_tools(_wt(fake), "s")
        assert ei.value.status == 401


async def _drain(aiter: Any) -> list[Any]:
    out: list[Any] = []
    async for ev in aiter:
        out.append(ev)
    return out


class TestStreamTurn:
    async def test_yields_events_verbatim(self) -> None:
        e1, e2 = object(), object()
        fake = _FakeSessions(events=[e1, e2])
        got = await _drain(stream_turn(_wt(fake), "s-1", "hello"))
        assert got == [e1, e2]
        # memory_context defaults to None (forwarded verbatim; inert for every caller
        # that doesn't pin corpus context via the KB-recall bridge).
        assert fake.calls[-1] == ("stream_turn", ("s-1", "hello"), {"memory_context": None})

    async def test_forwards_memory_context_verbatim(self) -> None:
        mc = [{"kind": "corpus_reference", "text": "the pinned passage"}]
        fake = _FakeSessions(events=[])
        await _drain(stream_turn(_wt(fake), "s-1", "hi", memory_context=mc))
        assert fake.calls[-1] == ("stream_turn", ("s-1", "hi"), {"memory_context": mc})

    async def test_session_retired_maps_to_session_api_failed(self) -> None:
        fake = _FakeSessions(
            stream_error=wtsdk.SessionRetired("session_retired", "gone", status=410)
        )
        with pytest.raises(SessionApiFailed) as ei:
            await _drain(stream_turn(_wt(fake), "s", "hi"))
        assert ei.value.status == 410
        assert ei.value.error_code == "session_retired"

    async def test_agent_not_available_rewraps_to_ratatoskr(self) -> None:
        fake = _FakeSessions(
            stream_error=wtsdk.AgentNotAvailable("agent_not_available", "no agent", status=409)
        )
        with pytest.raises(AgentNotAvailable) as ei:
            await _drain(stream_turn(_wt(fake), "s", "hi"))
        assert ei.value.error_code == "agent_not_available"
        assert ei.value.status == 409

    async def test_turn_launch_unavailable_rewraps_to_ratatoskr(self) -> None:
        fake = _FakeSessions(
            stream_error=wtsdk.TurnLaunchUnavailable("not_ready", "busy", status=503)
        )
        with pytest.raises(TurnLaunchUnavailable) as ei:
            await _drain(stream_turn(_wt(fake), "s", "hi"))
        assert ei.value.retryable is True

    async def test_generic_connect_failed_maps_to_sse_connect_failed(self) -> None:
        fake = _FakeSessions(stream_error=wtsdk.ConnectFailed("connect_failed", "boom", status=500))
        with pytest.raises(SseConnectFailed) as ei:
            await _drain(stream_turn(_wt(fake), "s", "hi"))
        assert ei.value.status == 500

    async def test_connection_dropped_maps_and_carries_cursor(self) -> None:
        fake = _FakeSessions(stream_error=wtsdk.ConnectionDropped("12:3"))
        with pytest.raises(SseConnectionDropped) as ei:
            await _drain(stream_turn(_wt(fake), "s", "hi"))
        assert ei.value.last_seen_sse_id == "12:3"

    async def test_resume_error_maps_to_sse_connect_failed(self) -> None:
        fake = _FakeSessions(stream_error=wtsdk.ResumeError("resume_failed", "boom", status=412))
        with pytest.raises(SseConnectFailed) as ei:
            await _drain(stream_turn(_wt(fake), "s", "hi"))
        assert ei.value.status == 412

    async def test_malformed_sse_id_passes_through_as_ratatoskr(self) -> None:
        fake = _FakeSessions(stream_error=wtsdk.MalformedSseId("bad-id"))
        with pytest.raises(MalformedSseId) as ei:
            await _drain(stream_turn(_wt(fake), "s", "hi"))
        assert ei.value.raw == "bad-id"

    async def test_malformed_sse_data_passes_through_as_ratatoskr(self) -> None:
        fake = _FakeSessions(stream_error=wtsdk.MalformedSseData("not json"))
        with pytest.raises(MalformedSseData):
            await _drain(stream_turn(_wt(fake), "s", "hi"))

    async def test_turn_id_flip_carries_established_and_got(self) -> None:
        fake = _FakeSessions(stream_error=wtsdk.TurnIdFlip(5, 7))
        with pytest.raises(TurnIdFlip) as ei:
            await _drain(stream_turn(_wt(fake), "s", "hi"))
        assert (ei.value.established, ei.value.got) == (5, 7)

    async def test_response_too_large_maps_to_sse_response_too_large(self) -> None:
        # 1.2.0: an oversized SSE frame (ProtocolError, NOT ConnectionDropped) → a terminal
        # SseResponseTooLarge, never a resumable drop. Carries limit_bytes + scope.
        fake = _FakeSessions(
            stream_error=wtsdk.ResponseTooLarge(limit_bytes=540000, scope="sse_frame")
        )
        with pytest.raises(SseResponseTooLarge) as ei:
            await _drain(stream_turn(_wt(fake), "s", "hi"))
        assert ei.value.limit_bytes == 540000 and ei.value.scope == "sse_frame"

    async def test_undiscriminated_api_error_maps_to_session_api_failed(self) -> None:
        # INV-CUT-2 default: an undiscriminated ApiError surfacing from the stream
        # (not a discriminated stream error) → SessionApiFailed.
        fake = _FakeSessions(stream_error=ApiError("weird", "boom", status=500))
        with pytest.raises(SessionApiFailed) as ei:
            await _drain(stream_turn(_wt(fake), "s", "hi"))
        assert ei.value.status == 500


class TestCancelTurn:
    async def test_happy_returns_cancel_result(self) -> None:
        res = CancelResult(turn_id=42, cancelled=True, reason=None, partial_message_id=None)
        fake = _FakeSessions(result=res)
        out = await cancel_turn(_wt(fake), "s-1", 42, persist_partial=True)
        assert out is res
        assert fake.calls[-1] == ("cancel_turn", ("s-1", 42), {"persist_partial": True})

    async def test_late_cancel_race_is_a_result_not_an_error(self) -> None:
        # B-CAN-3: a 200 with cancelled=False is the benign late-cancel no-op.
        res = CancelResult(turn_id=42, cancelled=False, reason=None, partial_message_id=None)
        out = await cancel_turn(_wt(_FakeSessions(result=res)), "s", 42)
        assert out.cancelled is False

    async def test_turn_not_found_maps_to_ratatoskr(self) -> None:
        fake = _FakeSessions(
            error=wtsdk.CancelTurnNotFound(42, error_code="turn_not_found", message="gone")
        )
        with pytest.raises(CancelTurnNotFound) as ei:
            await cancel_turn(_wt(fake), "s", 42)
        assert ei.value.turn_id == 42

    async def test_turn_finished_maps_to_ratatoskr(self) -> None:
        fake = _FakeSessions(
            error=wtsdk.CancelAlreadyCompleted(42, error_code="turn_finished", message="done")
        )
        with pytest.raises(CancelAlreadyCompleted):
            await cancel_turn(_wt(fake), "s", 42)

    async def test_other_cancel_failure_maps_to_cancel_failed(self) -> None:
        fake = _FakeSessions(error=wtsdk.CancelFailed(42, error_code="boom", message="failed"))
        with pytest.raises(CancelFailed):
            await cancel_turn(_wt(fake), "s", 42)

    async def test_undiscriminated_api_error_maps_to_session_api_failed(self) -> None:
        # INV-CUT-2 default: an undiscriminated ApiError on the cancel route (not a
        # typed Cancel* race) → SessionApiFailed, never leaked as a bare ApiError.
        fake = _FakeSessions(error=ApiError("weird", "boom", status=500))
        with pytest.raises(SessionApiFailed) as ei:
            await cancel_turn(_wt(fake), "s", 42)
        assert ei.value.status == 500


class TestSetPersonaState:
    """slice-3: set_persona_state → SDK sessions.set_persona_state(PadState). The
    adapter builds the canonical PadState (the SDK owns the {"pad": {...}} wire
    shape); no error row beyond the § Error map default (SessionApiFailed)."""

    async def test_happy_builds_padstate_and_returns_none(self) -> None:
        fake = _FakeSessions(result=None)  # SDK resolves the 204 to None
        out = await set_persona_state(
            _wt(fake), "s-1", pleasure=0.4, arousal=0.1, dominance=-0.2
        )
        assert out is None
        name, args, _kwargs = fake.calls[-1]
        assert name == "set_persona_state"
        assert args[0] == "s-1"
        pad = args[1]
        assert isinstance(pad, PadState)
        assert (pad.pleasure, pad.arousal, pad.dominance) == (0.4, 0.1, -0.2)

    async def test_falsy_zero_pad_preserved(self) -> None:
        # A 0.0 axis must survive verbatim (not be dropped as falsy).
        fake = _FakeSessions(result=None)
        await set_persona_state(_wt(fake), "s", pleasure=0.0, arousal=0.0, dominance=0.0)
        pad = fake.calls[-1][1][1]
        assert (pad.pleasure, pad.arousal, pad.dominance) == (0.0, 0.0, 0.0)

    async def test_error_maps_to_session_api_failed(self) -> None:
        fake = _FakeSessions(error=ApiError("upstream", "boom", status=500, body="x"))
        with pytest.raises(SessionApiFailed) as ei:
            await set_persona_state(_wt(fake), "s", pleasure=0.0, arousal=0.0, dominance=0.0)
        assert ei.value.status == 500

    async def test_non_finite_pad_rejected_at_the_chokepoint(self) -> None:
        # The finite-PAD invariant is enforced at the adapter (not only the CLI): a
        # non-finite axis would serialize to null and corrupt the injection, so a
        # direct caller is rejected pre-SDK — never a leaked SDK ConfigurationError.
        fake = _FakeSessions(result=None)
        for bad in (float("nan"), float("inf"), float("-inf")):
            with pytest.raises(AssertionError):
                await set_persona_state(_wt(fake), "s", pleasure=bad, arousal=0.0, dominance=0.0)
        assert fake.calls == []  # never reached the SDK


class TestWriteAuthoredHistory:
    """slice-3: write_authored_history → SDK sessions.write_history. Builds the
    v1 authored-write entry (author="assistant", the only accepted author);
    404 → AuthoredHistoryUnavailable (hide-existence); else the default."""

    async def test_happy_builds_entry_and_returns_dict(self) -> None:
        ack = {"seq": 0, "phase": "seeded", "turn_id": "t1", "content_chars": 3}
        fake = _FakeSessions(result=ack)
        out = await write_authored_history(
            _wt(fake), "s-1", content="hi!", idempotency_key="k1"
        )
        assert out is ack  # open-world passthrough
        name, args, _kwargs = fake.calls[-1]
        assert name == "write_history"
        assert args[0] == "s-1"
        assert args[1] == {
            "author": "assistant",
            "content": "hi!",
            "idempotency_key": "k1",
        }

    async def test_404_maps_to_authored_history_unavailable(self) -> None:
        # Hide-existence: the ROUTE is the discriminator (never the body) — any 404
        # on write_history → AuthoredHistoryUnavailable, no capability-probe.
        fake = _FakeSessions(error=ApiError("session_not_found", "no", status=404))
        with pytest.raises(AuthoredHistoryUnavailable) as ei:
            await write_authored_history(_wt(fake), "s-1", content="hi", idempotency_key="k")
        assert ei.value.session_id == "s-1"

    async def test_other_error_maps_to_session_api_failed(self) -> None:
        # 409 generation_active (retryable) is NOT a hide-existence 404 → default.
        fake = _FakeSessions(error=ApiError("generation_active", "busy", status=409))
        with pytest.raises(SessionApiFailed) as ei:
            await write_authored_history(_wt(fake), "s", content="hi", idempotency_key="k")
        assert ei.value.status == 409


# ── slice-4: agents (Tier-3) adapter routes ──────────────────────────────────


class _FakeAgents:
    """Stand-in for `WorldtreeClient.agents` — records the last call and returns a
    canned result or raises a canned error. Same shape as `_FakeSessions`."""

    def __init__(self, *, result: Any = None, error: BaseException | None = None) -> None:
        self._result = result
        self._error = error
        self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = []

    async def _dispatch(self, name: str, *args: Any, **kwargs: Any) -> Any:
        self.calls.append((name, args, kwargs))
        if self._error is not None:
            raise self._error
        return self._result

    async def list(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("list", *args, **kwargs)

    async def persona_state(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("persona_state", *args, **kwargs)

    async def define(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("define", *args, **kwargs)

    async def patch(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("patch", *args, **kwargs)

    async def delete(self, *args: Any, **kwargs: Any) -> Any:
        return await self._dispatch("delete", *args, **kwargs)


class _FakeAgentsClient:
    def __init__(self, agents: _FakeAgents) -> None:
        self.agents = agents


def _wta(agents: _FakeAgents) -> WorldtreeClient:
    """Cast the structural agents-fake to the nominal client type (the agent route
    functions only touch `client.agents.*`)."""
    return cast(WorldtreeClient, _FakeAgentsClient(agents))


class TestListAgents:
    """slice-4: list_agents → SDK agents.list(); open-world array verbatim."""

    async def test_happy_returns_array_verbatim(self) -> None:
        data = [{"agent_id": "mimir", "name": "Mimir", "description": "k"}]
        fake = _FakeAgents(result=data)
        out = await list_agents(_wta(fake))
        assert out is data  # open-world passthrough, no AgentInfo normalization
        assert fake.calls[-1][0] == "list"

    async def test_error_maps_to_session_api_failed(self) -> None:
        fake = _FakeAgents(error=ApiError("upstream", "boom", status=500))
        with pytest.raises(SessionApiFailed) as ei:
            await list_agents(_wta(fake))
        assert ei.value.status == 500


class TestGetPersonaState:
    """slice-4: get_persona_state → SDK agents.persona_state(id); dict verbatim.
    404 sub-codes + 403 auth_scope_denied map by (status, error_code)."""

    async def test_happy_returns_dict_verbatim(self) -> None:
        snap = {"pad": {}, "dominant_emotion": "curiosity"}
        fake = _FakeAgents(result=snap)
        out = await get_persona_state(_wta(fake), "mimir")
        assert out is snap
        assert fake.calls[-1] == ("persona_state", ("mimir",), {})

    async def test_persona_not_configured(self) -> None:
        fake = _FakeAgents(error=ApiError("persona_not_configured", "no", status=404))
        with pytest.raises(PersonaNotConfigured) as ei:
            await get_persona_state(_wta(fake), "domari")
        assert ei.value.agent_id == "domari"

    async def test_agent_not_available(self) -> None:
        fake = _FakeAgents(error=ApiError("agent_not_available", "no", status=404))
        with pytest.raises(PersonaAgentNotAvailable) as ei:
            await get_persona_state(_wta(fake), "bogus")
        assert ei.value.agent_id == "bogus"

    async def test_auth_scope_denied(self) -> None:
        fake = _FakeAgents(error=ApiError("auth_scope_denied", "no", status=403))
        with pytest.raises(AuthScopeDenied) as ei:
            await get_persona_state(_wta(fake), "mimir")
        assert ei.value.scope == "persona.read"

    async def test_other_404_without_code_maps_to_default(self) -> None:
        # A 404 whose error_code is neither persona sub-code → generic default,
        # NOT a spurious PersonaNotConfigured (the code is the discriminator).
        fake = _FakeAgents(error=ApiError("weird", "no", status=404))
        with pytest.raises(SessionApiFailed) as ei:
            await get_persona_state(_wta(fake), "mimir")
        assert ei.value.status == 404

    async def test_empty_agent_id_asserts(self) -> None:
        fake = _FakeAgents(result={})
        with pytest.raises(AssertionError):
            await get_persona_state(_wta(fake), "")
        assert fake.calls == []


class TestDefineAgent:
    """slice-4: define_agent → SDK agents.define(); open-world DefinedAgent dict
    (echoes `role` post-b128). Slug validated client-side; tier3 error rows."""

    async def test_happy_builds_body_and_returns_dict(self) -> None:
        resp = {"agent_id": "ratatoskr:wizard", "role": "thoughtful-character"}
        fake = _FakeAgents(result=resp)
        out = await define_agent(
            _wta(fake), agent_name="wizard", system_prompt="You are a wizard.",
            role="thoughtful-character",
        )
        assert out is resp  # open-world passthrough (no Tier3AgentInfo)
        name, args, _kwargs = fake.calls[-1]
        assert name == "define"
        # AgentDefineInput body: exactly the three keys, no layer fields.
        assert args[0] == {
            "agent_name": "wizard",
            "role": "thoughtful-character",
            "system_prompt": "You are a wizard.",
        }

    async def test_quota_exceeded_defaults_retry_after_zero(self) -> None:
        # The SDK's ApiError floor drops the Retry-After header; spec §2675 pins it
        # to 0, so the adapter defaults retry_after=0.
        fake = _FakeAgents(error=ApiError("agent_quota_exceeded", "full", status=429))
        with pytest.raises(Tier3QuotaExceeded) as ei:
            await define_agent(_wta(fake), agent_name="overflow", system_prompt="x", role="m")
        assert ei.value.retry_after == 0

    async def test_user_id_unsupported(self) -> None:
        fake = _FakeAgents(error=ApiError("tier3_user_id_unsupported", "no", status=403))
        with pytest.raises(Tier3UserIdUnsupported):
            await define_agent(_wta(fake), agent_name="wizard", system_prompt="x", role="m")

    async def test_layer_deferred_field_parsed_from_body(self) -> None:
        # `field` is not on ApiError — the adapter body-parses detail.field.
        fake = _FakeAgents(error=ApiError(
            "layer_deferred", "no", status=422,
            body='{"detail": {"error_code": "layer_deferred", "field": "persona"}}',
        ))
        with pytest.raises(Tier3LayerDeferred) as ei:
            await define_agent(_wta(fake), agent_name="wizard", system_prompt="x", role="m")
        assert ei.value.field == "persona"

    async def test_layer_deferred_flat_field_body(self) -> None:
        # _error_field_from_body also handles a flat top-level `field` (both-shape
        # unwrap) — locks the contract's "detail.field / flat field" claim.
        fake = _FakeAgents(error=ApiError(
            "layer_deferred", "no", status=422, body='{"field": "valence"}',
        ))
        with pytest.raises(Tier3LayerDeferred) as ei:
            await define_agent(_wta(fake), agent_name="wizard", system_prompt="x", role="m")
        assert ei.value.field == "valence"

    async def test_layer_deferred_non_string_field_is_none(self) -> None:
        # A non-string `field` value collapses to None — the exception surface is
        # `field: str | None` and the CLI prints it (heid-bug-hunt hardening).
        fake = _FakeAgents(error=ApiError(
            "layer_deferred", "no", status=422, body='{"detail": {"field": {"x": 1}}}',
        ))
        with pytest.raises(Tier3LayerDeferred) as ei:
            await define_agent(_wta(fake), agent_name="wizard", system_prompt="x", role="m")
        assert ei.value.field is None

    async def test_403_wrong_code_maps_to_default(self) -> None:
        # Dual-key negative: a 403 whose code is NOT tier3_user_id_unsupported →
        # generic default, not a spurious Tier3UserIdUnsupported (INV-CUT-2).
        fake = _FakeAgents(error=ApiError("auth_revoked", "no", status=403))
        with pytest.raises(SessionApiFailed) as ei:
            await define_agent(_wta(fake), agent_name="wizard", system_prompt="x", role="m")
        assert ei.value.status == 403

    async def test_422_wrong_code_maps_to_default(self) -> None:
        # Dual-key negative: a 422 whose code is NOT layer_deferred → default.
        fake = _FakeAgents(error=ApiError("validation_failed", "no", status=422))
        with pytest.raises(SessionApiFailed) as ei:
            await define_agent(_wta(fake), agent_name="wizard", system_prompt="x", role="m")
        assert ei.value.status == 422

    async def test_bad_slug_asserts_no_call(self) -> None:
        fake = _FakeAgents(result={})
        with pytest.raises(AssertionError):
            await define_agent(_wta(fake), agent_name="Wizard", system_prompt="x", role="m")
        assert fake.calls == []

    async def test_short_slug_asserts_no_call(self) -> None:
        fake = _FakeAgents(result={})
        with pytest.raises(AssertionError):
            await define_agent(_wta(fake), agent_name="ab", system_prompt="x", role="m")
        assert fake.calls == []

    async def test_empty_prompt_asserts(self) -> None:
        fake = _FakeAgents(result={})
        with pytest.raises(AssertionError):
            await define_agent(_wta(fake), agent_name="wizard", system_prompt="", role="m")
        assert fake.calls == []

    async def test_empty_role_asserts(self) -> None:
        fake = _FakeAgents(result={})
        with pytest.raises(AssertionError):
            await define_agent(_wta(fake), agent_name="wizard", system_prompt="x", role="")
        assert fake.calls == []

    async def test_other_5xx_maps_to_default(self) -> None:
        fake = _FakeAgents(error=ApiError("upstream", "out", status=503))
        with pytest.raises(SessionApiFailed) as ei:
            await define_agent(_wta(fake), agent_name="wizard", system_prompt="x", role="m")
        assert ei.value.status == 503


class TestPatchAgent:
    """slice-4: patch_agent → SDK agents.patch(id, changes); open PatchedAgent dict."""

    async def test_happy_both_fields(self) -> None:
        resp = {"agent_id": "ratatoskr:wizard", "role": "different"}
        fake = _FakeAgents(result=resp)
        out = await patch_agent(
            _wta(fake), "ratatoskr:wizard", system_prompt="new", role="different"
        )
        assert out is resp
        name, args, _kwargs = fake.calls[-1]
        assert name == "patch"
        assert args[0] == "ratatoskr:wizard"
        assert args[1] == {"system_prompt": "new", "role": "different"}

    async def test_happy_single_field_omits_none(self) -> None:
        fake = _FakeAgents(result={})
        await patch_agent(_wta(fake), "ratatoskr:wizard", system_prompt="only this")
        assert fake.calls[-1][1][1] == {"system_prompt": "only this"}

    async def test_404_maps_to_agent_not_found(self) -> None:
        fake = _FakeAgents(error=ApiError("not_found", "no", status=404))
        with pytest.raises(Tier3AgentNotFound) as ei:
            await patch_agent(_wta(fake), "ratatoskr:ghost", system_prompt="x")
        assert ei.value.agent_id == "ratatoskr:ghost"

    async def test_field_not_mutable_field_parsed(self) -> None:
        fake = _FakeAgents(error=ApiError(
            "field_not_mutable", "no", status=422,
            body='{"detail": {"error_code": "field_not_mutable", "field": "agent_name"}}',
        ))
        with pytest.raises(Tier3FieldNotMutable) as ei:
            await patch_agent(_wta(fake), "ratatoskr:wizard", system_prompt="x")
        assert ei.value.field == "agent_name"

    async def test_422_wrong_code_maps_to_default(self) -> None:
        # Dual-key negative: a 422 whose code is NOT field_not_mutable → default,
        # not a spurious Tier3FieldNotMutable (INV-CUT-2).
        fake = _FakeAgents(error=ApiError("validation_failed", "no", status=422))
        with pytest.raises(SessionApiFailed) as ei:
            await patch_agent(_wta(fake), "ratatoskr:wizard", system_prompt="x")
        assert ei.value.status == 422

    async def test_no_fields_asserts_no_call(self) -> None:
        fake = _FakeAgents(result={})
        with pytest.raises(AssertionError):
            await patch_agent(_wta(fake), "ratatoskr:wizard")
        assert fake.calls == []

    async def test_non_tier3_id_asserts(self) -> None:
        fake = _FakeAgents(result={})
        with pytest.raises(AssertionError):
            await patch_agent(_wta(fake), "mimir", system_prompt="x")
        assert fake.calls == []

    async def test_other_error_maps_to_default(self) -> None:
        fake = _FakeAgents(error=ApiError("upstream", "boom", status=500))
        with pytest.raises(SessionApiFailed) as ei:
            await patch_agent(_wta(fake), "ratatoskr:wizard", system_prompt="x")
        assert ei.value.status == 500


class TestDeleteAgent:
    """slice-4: delete_agent → SDK agents.delete(id); None on 204; 404 → not-found."""

    async def test_happy_returns_none(self) -> None:
        fake = _FakeAgents(result=None)
        out = await delete_agent(_wta(fake), "ratatoskr:wizard")
        assert out is None
        assert fake.calls[-1] == ("delete", ("ratatoskr:wizard",), {})

    async def test_404_maps_to_agent_not_found(self) -> None:
        fake = _FakeAgents(error=ApiError("not_found", "no", status=404))
        with pytest.raises(Tier3AgentNotFound) as ei:
            await delete_agent(_wta(fake), "ratatoskr:ghost")
        assert ei.value.agent_id == "ratatoskr:ghost"

    async def test_non_tier3_id_asserts(self) -> None:
        fake = _FakeAgents(result=None)
        with pytest.raises(AssertionError):
            await delete_agent(_wta(fake), "mimir")
        assert fake.calls == []

    async def test_other_error_maps_to_default(self) -> None:
        fake = _FakeAgents(error=ApiError("upstream", "oops", status=500))
        with pytest.raises(SessionApiFailed) as ei:
            await delete_agent(_wta(fake), "ratatoskr:wizard")
        assert ei.value.status == 500


# ── slice-5: characters + me/capabilities/models adapter routes ───────────────
# One canned result / error per fake (each slice-5 adapter fn touches exactly one
# sub-resource method), recorded by qualified name so the test can assert the route.


class _FakeMe:
    def __init__(self, rec: _FakeMisc) -> None:
        self._rec = rec

    async def get(self, *a: Any, **k: Any) -> Any:
        return await self._rec._dispatch("me.get", *a, **k)


class _FakeCapabilities:
    def __init__(self, rec: _FakeMisc) -> None:
        self._rec = rec

    async def get(self, *a: Any, **k: Any) -> Any:
        return await self._rec._dispatch("capabilities.get", *a, **k)


class _FakeModels:
    def __init__(self, rec: _FakeMisc) -> None:
        self._rec = rec

    async def available_for_characters(self, *a: Any, **k: Any) -> Any:
        return await self._rec._dispatch("models.available_for_characters", *a, **k)


class _FakeCharacters:
    def __init__(self, rec: _FakeMisc) -> None:
        self._rec = rec

    async def create(self, *a: Any, **k: Any) -> Any:
        return await self._rec._dispatch("characters.create", *a, **k)

    async def state(self, *a: Any, **k: Any) -> Any:
        return await self._rec._dispatch("characters.state", *a, **k)

    async def delete(self, *a: Any, **k: Any) -> Any:
        return await self._rec._dispatch("characters.delete", *a, **k)


class _FakeMisc:
    """Stand-in for the slice-5 client surface — exposes `.me` / `.capabilities` /
    `.models` / `.characters`, recording each call under its qualified name and
    returning a canned result or raising a canned error (same shape as `_FakeSessions`
    / `_FakeAgents`)."""

    def __init__(self, *, result: Any = None, error: BaseException | None = None) -> None:
        self._result = result
        self._error = error
        self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = []
        self.me = _FakeMe(self)
        self.capabilities = _FakeCapabilities(self)
        self.models = _FakeModels(self)
        self.characters = _FakeCharacters(self)

    async def _dispatch(self, name: str, *args: Any, **kwargs: Any) -> Any:
        self.calls.append((name, args, kwargs))
        if self._error is not None:
            raise self._error
        return self._result


def _wtm(misc: _FakeMisc) -> WorldtreeClient:
    """Cast the slice-5 misc-surface fake (me/capabilities/models/characters) to the
    nominal client type the route functions are typed against."""
    return cast(WorldtreeClient, misc)


class TestGetMe:
    """slice-5: get_me → SDK me.get(); open-world dict verbatim."""

    async def test_happy_returns_dict_verbatim(self) -> None:
        me = {"user_id": "alice", "scopes": ["conversations.read"], "tier": "user"}
        fake = _FakeMisc(result=me)
        out = await get_me(_wtm(fake))
        assert out is me
        assert fake.calls[-1][0] == "me.get"

    async def test_401_maps_to_session_api_failed(self) -> None:
        fake = _FakeMisc(error=ApiError("auth_invalid", "no", status=401))
        with pytest.raises(SessionApiFailed) as ei:
            await get_me(_wtm(fake))
        assert ei.value.status == 401


class TestGetCapabilities:
    """slice-5: get_capabilities → SDK capabilities.get(); open-world verbatim."""

    async def test_happy_returns_dict_verbatim(self) -> None:
        caps = {"ephemeral_templates": {"echo": {"default_role": "echo"}}}
        fake = _FakeMisc(result=caps)
        out = await get_capabilities(_wtm(fake))
        assert out is caps
        assert fake.calls[-1][0] == "capabilities.get"

    async def test_error_maps_to_session_api_failed(self) -> None:
        fake = _FakeMisc(error=ApiError("upstream", "boom", status=500))
        with pytest.raises(SessionApiFailed) as ei:
            await get_capabilities(_wtm(fake))
        assert ei.value.status == 500


class TestListCharacterModels:
    """slice-5: list_character_models → SDK models.available_for_characters()."""

    async def test_happy_returns_dict_verbatim(self) -> None:
        models = {"items": [{"name": "fast", "thinking": False}]}
        fake = _FakeMisc(result=models)
        out = await list_character_models(_wtm(fake))
        assert out is models
        assert fake.calls[-1][0] == "models.available_for_characters"

    async def test_error_maps_to_session_api_failed(self) -> None:
        fake = _FakeMisc(error=ApiError("auth_scope_denied", "no", status=403))
        with pytest.raises(SessionApiFailed) as ei:
            await list_character_models(_wtm(fake))
        assert ei.value.status == 403


class TestCreateCharacter:
    """slice-5: create_character → SDK characters.create(body); body-building + parity."""

    async def test_happy_omits_state_when_none(self) -> None:
        # SDK-idiomatic body: {character} only — no redundant explicit state:null.
        created = {"character_id": "char_x", "ttl_expires_at": "t"}
        fake = _FakeMisc(result=created)
        out = await create_character(_wtm(fake), {"schema_version": "1", "name": "H"})
        assert out is created
        name, args, _ = fake.calls[-1]
        assert name == "characters.create"
        assert args[0] == {"character": {"schema_version": "1", "name": "H"}}

    async def test_includes_state_when_supplied(self) -> None:
        fake = _FakeMisc(result={"character_id": "c1"})
        await create_character(
            _wtm(fake), {"name": "H"}, state={"mood": "calm"}
        )
        assert fake.calls[-1][1][0] == {
            "character": {"name": "H"},
            "state": {"mood": "calm"},
        }

    async def test_empty_character_asserts_no_call(self) -> None:
        fake = _FakeMisc(result={})
        with pytest.raises(AssertionError):
            await create_character(_wtm(fake), {})
        assert fake.calls == []

    async def test_403_maps_to_session_api_failed(self) -> None:
        fake = _FakeMisc(error=ApiError("auth_scope_denied", "no", status=403))
        with pytest.raises(SessionApiFailed) as ei:
            await create_character(_wtm(fake), {"name": "H"})
        assert ei.value.status == 403


class TestGetCharacterState:
    """slice-5: get_character_state → SDK characters.state(id); open-world verbatim."""

    async def test_happy_returns_dict_verbatim(self) -> None:
        state = {"schema_version": "1", "pad": [0.4, 0.1, -0.2]}
        fake = _FakeMisc(result=state)
        out = await get_character_state(_wtm(fake), "char_x")
        assert out is state
        assert fake.calls[-1] == ("characters.state", ("char_x",), {})

    async def test_empty_id_asserts_no_call(self) -> None:
        fake = _FakeMisc(result={})
        with pytest.raises(AssertionError):
            await get_character_state(_wtm(fake), "")
        assert fake.calls == []

    async def test_error_maps_to_session_api_failed(self) -> None:
        fake = _FakeMisc(error=ApiError("not_found", "no", status=404))
        with pytest.raises(SessionApiFailed) as ei:
            await get_character_state(_wtm(fake), "char_x")
        assert ei.value.status == 404


class TestDeleteCharacter:
    """slice-5: delete_character → SDK characters.delete(id); open ack verbatim."""

    async def test_returns_ack_verbatim(self) -> None:
        # Worldtree returns an open ack body here (not 204) — passed through, NOT
        # normalized to None (parity posture).
        ack = {"deleted": True, "character_id": "char_x"}
        fake = _FakeMisc(result=ack)
        out = await delete_character(_wtm(fake), "char_x")
        assert out is ack
        assert fake.calls[-1] == ("characters.delete", ("char_x",), {})

    async def test_none_on_204(self) -> None:
        # A 204 no-content yields None from the SDK — passed through unchanged.
        fake = _FakeMisc(result=None)
        assert await delete_character(_wtm(fake), "char_x") is None

    async def test_empty_id_asserts_no_call(self) -> None:
        fake = _FakeMisc(result=None)
        with pytest.raises(AssertionError):
            await delete_character(_wtm(fake), "")
        assert fake.calls == []

    async def test_error_maps_to_session_api_failed(self) -> None:
        fake = _FakeMisc(error=ApiError("upstream", "oops", status=500))
        with pytest.raises(SessionApiFailed) as ei:
            await delete_character(_wtm(fake), "char_x")
        assert ei.value.status == 500


# ── slice-6: admin (bifrost inspection + admin-events stream) adapter routes ──


class _SdkAdminEvent:
    """Minimal stand-in for the SDK's `AdminEvent` — the adapter reads
    `admin_id`/`type`/`timestamp`/`data`. `admin_id` may be `nan` (id-less);
    `type`/`data` may be None (open-world)."""

    def __init__(self, admin_id: Any, type: Any, timestamp: Any = None, data: Any = None) -> None:
        self.admin_id = admin_id
        self.type = type
        self.timestamp = timestamp
        self.data = data


class _FakeAdminSessions:
    def __init__(self, admin: _FakeAdmin) -> None:
        self._admin = admin

    async def bifrost(self, *a: Any, **k: Any) -> Any:
        return await self._admin._bifrost(*a, **k)


class _FakeAdmin:
    """Stand-in for `client.admin` — `.sessions.bifrost(id)` (canned result/error) +
    `.stream_events(...)` (canned events / terminal error). Same shape as `_FakeSessions`."""

    def __init__(
        self,
        *,
        result: Any = None,
        error: BaseException | None = None,
        events: list[Any] | None = None,
        stream_error: BaseException | None = None,
    ) -> None:
        self._result = result
        self._error = error
        self._events = events or []
        self._stream_error = stream_error
        self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = []
        self.sessions = _FakeAdminSessions(self)

    async def _bifrost(self, *a: Any, **k: Any) -> Any:
        self.calls.append(("bifrost", a, k))
        if self._error is not None:
            raise self._error
        return self._result

    def stream_events(self, *a: Any, **k: Any) -> Any:
        self.calls.append(("stream_events", a, k))
        return self._astream()

    async def _astream(self) -> Any:
        for ev in self._events:
            yield ev
        if self._stream_error is not None:
            raise self._stream_error


class _FakeAdminClient:
    def __init__(self, admin: _FakeAdmin) -> None:
        self.admin = admin


def _wtad(admin: _FakeAdmin) -> WorldtreeClient:
    """Cast the admin-surface fake (`.admin.sessions.bifrost` + `.admin.stream_events`)
    to the nominal client type the slice-6 route functions are typed against."""
    return cast(WorldtreeClient, _FakeAdminClient(admin))


class TestGetSessionBifrostWt:
    """slice-6: get_session_bifrost → SDK admin.sessions.bifrost(id); open-world verbatim."""

    async def test_happy_returns_dict_verbatim(self) -> None:
        binding = {"endpoint_url": "https://b/mcp", "connected": True, "tools": []}
        fake = _FakeAdmin(result=binding)
        out = await get_session_bifrost(_wtad(fake), "s1")
        assert out is binding
        assert fake.calls[-1] == ("bifrost", ("s1",), {})

    async def test_empty_id_asserts_no_call(self) -> None:
        fake = _FakeAdmin(result={})
        with pytest.raises(AssertionError):
            await get_session_bifrost(_wtad(fake), "")
        assert fake.calls == []

    async def test_403_maps_to_session_api_failed(self) -> None:
        fake = _FakeAdmin(error=ApiError("auth_scope_denied", "no", status=403))
        with pytest.raises(SessionApiFailed) as ei:
            await get_session_bifrost(_wtad(fake), "s1")
        assert ei.value.status == 403

    async def test_404_not_bound_maps_to_session_api_failed(self) -> None:
        fake = _FakeAdmin(error=ApiError("session_not_bifrost_bound", "no", status=404))
        with pytest.raises(SessionApiFailed) as ei:
            await get_session_bifrost(_wtad(fake), "s1")
        assert ei.value.status == 404


class TestStreamAdminEventsWt:
    """slice-6: stream_admin_events → SDK admin.stream_events; re-wrap SDK AdminEvent →
    ratatoskr AdminEvent (nan/None degraded), terminal errors → Sse* types."""

    async def test_rewraps_events_to_ratatoskr_shape(self) -> None:
        sdk_evs = [
            _SdkAdminEvent(5, "session.created", "t0", {"session_id": "s1"}),
            _SdkAdminEvent(6, "turn.completed", "t1", {"session_id": "s1", "turn_id": 2}),
        ]
        fake = _FakeAdmin(events=sdk_evs)
        out = await _drain(stream_admin_events(_wtad(fake)))
        assert all(isinstance(e, AdminEvent) for e in out)
        assert (out[0].id, out[0].type, out[0].timestamp) == (5, "session.created", "t0")
        assert out[0].data == {"session_id": "s1"}
        assert out[1].id == 6

    async def test_nan_admin_id_degrades_to_zero(self) -> None:
        fake = _FakeAdmin(events=[_SdkAdminEvent(float("nan"), "system.heartbeat", None, None)])
        out = await _drain(stream_admin_events(_wtad(fake)))
        assert out[0].id == 0  # id-less envelope → 0, not nan

    async def test_none_type_and_data_degrade(self) -> None:
        # A partial wire: type=None (would crash `.startswith` in the web filter) and
        # data=None (would crash `.get`) → "" and {} at the adapter boundary.
        fake = _FakeAdmin(events=[_SdkAdminEvent(1, None, None, None)])
        out = await _drain(stream_admin_events(_wtad(fake)))
        assert out[0].type == ""
        assert out[0].data == {}

    async def test_passes_last_event_id(self) -> None:
        fake = _FakeAdmin(events=[])
        await _drain(stream_admin_events(_wtad(fake), last_event_id=42))
        assert fake.calls[-1] == ("stream_events", (), {"last_event_id": 42})

    async def test_non_200_apierror_maps_to_sse_connect_failed(self) -> None:
        # A non-200 open raises ApiError("admin_stream_failed", status=…) → SseConnectFailed.
        fake = _FakeAdmin(stream_error=ApiError("admin_stream_failed", "no", status=502))
        with pytest.raises(SseConnectFailed) as ei:
            await _drain(stream_admin_events(_wtad(fake)))
        assert ei.value.status == 502

    async def test_connect_failed_maps_to_sse_connect_failed(self) -> None:
        # A connect-time transport / auth-resolution failure surfaces as ConnectFailed
        # (the SDK's general floor) → SseConnectFailed, mirroring stream_turn — else it
        # escapes the web gen's Sse* handler and aborts the SSE (heid bug-hunt slice-6).
        fake = _FakeAdmin(stream_error=wtsdk.ConnectFailed("connect_failed", "refused", status=0))
        with pytest.raises(SseConnectFailed) as ei:
            await _drain(stream_admin_events(_wtad(fake)))
        assert ei.value.status == 0

    async def test_nonstr_type_degrades_to_empty(self) -> None:
        # A truthy NON-str `type` (a partial/wrong open-world wire) must degrade to ""
        # so the web filter's `.startswith` never AttributeErrors — `or ""` (falsy-only)
        # would let it through; the isinstance guard catches it (heid bug-hunt slice-6).
        fake = _FakeAdmin(events=[_SdkAdminEvent(1, 123, "t", {"session_id": "s"})])
        out = await _drain(stream_admin_events(_wtad(fake)))
        assert out[0].type == ""

    async def test_connection_dropped_carries_cursor(self) -> None:
        # A mid-stream drop / resumable EOF carries the resume cursor.
        fake = _FakeAdmin(stream_error=wtsdk.ConnectionDropped("42"))
        with pytest.raises(SseConnectionDropped) as ei:
            await _drain(stream_admin_events(_wtad(fake)))
        assert ei.value.last_seen_sse_id == "42"

    async def test_response_too_large_maps_to_sse_response_too_large(self) -> None:
        # 1.2.0: an oversized admin SSE frame (ProtocolError) → terminal SseResponseTooLarge,
        # mirroring stream_turn (never a resumable drop).
        fake = _FakeAdmin(
            stream_error=wtsdk.ResponseTooLarge(limit_bytes=540000, scope="sse_frame")
        )
        with pytest.raises(SseResponseTooLarge) as ei:
            await _drain(stream_admin_events(_wtad(fake)))
        assert ei.value.limit_bytes == 540000

    async def test_connection_dropped_none_cursor_connect_time(self) -> None:
        # A connect-time transport failure surfaces as ConnectionDropped(None) →
        # SseConnectionDropped(last_seen_sse_id=None) (the map's other cursor shape;
        # heid-code-review slice-6 test-gap).
        fake = _FakeAdmin(stream_error=wtsdk.ConnectionDropped(None))
        with pytest.raises(SseConnectionDropped) as ei:
            await _drain(stream_admin_events(_wtad(fake)))
        assert ei.value.last_seen_sse_id is None
