"""Tests for ratatoskr.first_message per docs/contracts/first_message.contract.md.

Slice-3 (worldtree-sdk cutover): `seed_preset_first_message` routes through the
`ratatoskr.wt` adapter over a `WorldtreeClient`, no longer the hand-rolled httpx
wrapper. These tests drive it through a fake client whose `sessions.write_history`
returns or raises the SDK's real types — exercising the adapter's error mapping AND
first_message's best-effort swallow in one pass. The wire format itself is the SDK's
to prove (the parity corpus); first_message's contract is behavioral: never block,
never raise (except CancelledError), and exactly one write on a preset hit.
"""

import asyncio
import hashlib
from typing import Any, cast

import pytest
import worldtree_sdk as wtsdk
from worldtree_sdk import ApiError, WorldtreeClient

from ratatoskr.first_message import (
    FIRST_MESSAGE_PRESETS,
    preset_for,
    seed_preset_first_message,
)


class _FakeSessions:
    """Stand-in for `WorldtreeClient.sessions` — records each `write_history` call
    and returns a canned ack or raises a canned error (the SDK's real exceptions)."""

    def __init__(self, *, result: Any = None, error: BaseException | None = None) -> None:
        self._result = result if result is not None else {}
        self._error = error
        self.calls: list[tuple[str, Any]] = []

    async def write_history(self, session_id: str, entry: Any) -> Any:
        self.calls.append((session_id, entry))
        if self._error is not None:
            raise self._error
        return self._result


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 (no network; the seed
    path only touches `client.sessions.write_history`, which the fake provides)."""
    return cast(WorldtreeClient, _FakeClient(sessions))


class TestPresetFor:
    """first_message contract — preset_for (dict lookup)."""

    def test_preset_hit(self) -> None:
        """preset_hit [happy,tracer]: sindra has a non-empty str preset."""
        val = preset_for("ratatoskr:sindra")
        assert isinstance(val, str) and val

    def test_preset_miss(self) -> None:
        """preset_miss [happy]: an agent with no preset → None."""
        assert preset_for("mimir") is None

    def test_empty_agent_id(self) -> None:
        """empty_agent_id [adversarial]: "" → AssertionError."""
        with pytest.raises(AssertionError):
            preset_for("")


class TestSeedPresetFirstMessage:
    """first_message contract — seed_preset_first_message (best-effort #347 seed)."""

    async def test_seeds_preset(self) -> None:
        """seeds_preset [happy,tracer]: preset agent → one write_history, correct entry."""
        content = FIRST_MESSAGE_PRESETS["ratatoskr:sindra"]
        key = "ratatoskr-preset-" + hashlib.sha256(content.encode("utf-8")).hexdigest()[:12]
        fake = _FakeSessions(result={"seq": 0, "phase": "seeded"})
        result = await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra")
        assert result == content
        assert len(fake.calls) == 1  # POST-002: exactly one history write
        session_id, entry = fake.calls[0]
        assert session_id == "s1"
        assert entry == {
            "author": "assistant",
            "content": content,
            "idempotency_key": key,
        }

    async def test_no_preset_zero_write(self) -> None:
        """no_preset_zero_write [happy]: no-preset agent → None, ZERO write (INV-002)."""
        fake = _FakeSessions()
        result = await seed_preset_first_message(_wt(fake), "s1", "mimir")
        assert result is None
        assert fake.calls == []

    async def test_feature_absent_swallowed(self) -> None:
        """feature_absent_swallowed [error]: 404 → AuthoredHistoryUnavailable → None (INV-001)."""
        fake = _FakeSessions(error=ApiError("session_not_found", "no", status=404))
        result = await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra")
        assert result is None
        assert len(fake.calls) == 1  # the write was attempted, then swallowed

    async def test_session_api_failed_swallowed(self) -> None:
        """session_api_failed_swallowed [error]: 409 → SessionApiFailed → None (INV-001)."""
        fake = _FakeSessions(error=ApiError("generation_active", "busy", status=409))
        result = await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra")
        assert result is None

    async def test_transport_error_swallowed(self) -> None:
        """transport_error_swallowed [error]: SDK ConnectFailed → None, no raise (INV-001)."""
        fake = _FakeSessions(error=wtsdk.ConnectFailed("connect_failed", "boom", status=0))
        result = await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra")
        assert result is None

    async def test_unexpected_exception_swallowed(self) -> None:
        """unexpected_exception [error]: write raises ValueError → None (broad never-raise)."""
        fake = _FakeSessions(error=ValueError("unexpected"))
        result = await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra")
        assert result is None

    async def test_cancellation_propagates(self) -> None:
        """cancellation_propagates [error]: CancelledError from the write is RE-RAISED."""
        fake = _FakeSessions(error=asyncio.CancelledError())
        with pytest.raises(asyncio.CancelledError):
            await seed_preset_first_message(_wt(fake), "s1", "ratatoskr:sindra")

    async def test_malformed_agent_id_no_write(self) -> None:
        """malformed_agent_id [adversarial]: non-str or empty agent_id → None; no write; no raise."""
        fake = _FakeSessions()
        assert await seed_preset_first_message(_wt(fake), "s1", 123) is None  # type: ignore[arg-type]
        assert await seed_preset_first_message(_wt(fake), "s1", "") is None
        assert fake.calls == []

    async def test_empty_session_id(self) -> None:
        """empty_session_id [adversarial]: "" → None (soft guard); no write; no raise."""
        fake = _FakeSessions()
        result = await seed_preset_first_message(_wt(fake), "", "ratatoskr:sindra")
        assert result is None
        assert fake.calls == []
