"""Drift-detection between TUI presentation discipline and web JS
presenter per issue #16 INV-008.

The JSON fixture at `tests/fixtures/presentation_contract.json`
enumerates the expected browser-facing event payload for each Event
type. Server-side serialization (`_event_to_browser_payload`) is
unit-tested against the fixture. JS-side rendering in
`src/ratatoskr/web/static/index.html` consumes the same shape — if
this fixture changes, both sides update in lockstep.
"""

from __future__ import annotations

import json
from pathlib import Path

from ratatoskr.sse_client import (
    AffectUpdate,
    AwaitingLlmFirstToken,
    Cancelled,
    Done,
    Error,
    SseId,
    Text,
    TextBoundary,
    Thinking,
    ToolResult,
    ToolStart,
    WorkerPhase,
)
from ratatoskr.web.server import _event_to_browser_payload


def _load_fixture() -> dict:
    path = Path(__file__).parent / "fixtures" / "presentation_contract.json"
    return json.loads(path.read_text())


def _check(name: str, event: object) -> None:
    """Assert (event_type, data) for `event` matches the fixture entry."""
    fixture = _load_fixture()
    assert name in fixture, f"fixture missing entry for {name!r}"
    expected = fixture[name]
    event_type, data = _event_to_browser_payload(event)
    assert event_type == expected["event_type"], (
        f"{name}: event_type {event_type!r} != fixture {expected['event_type']!r}"
    )
    assert data == expected["data"], (
        f"{name}: data mismatch\n  got: {data}\n  fixture: {expected['data']}"
    )


def test_worker_phase_matches_fixture() -> None:
    _check(
        "worker_phase",
        WorkerPhase(sse_id=SseId(42, 3), phase="BuildingPrompt", turn_id=42),
    )


def test_thinking_matches_fixture() -> None:
    _check(
        "thinking",
        Thinking(sse_id=SseId(42, 5), content="Let me think..."),
    )


def test_text_matches_fixture() -> None:
    _check(
        "text",
        Text(sse_id=SseId(42, 7), content="Hello there"),
    )


def test_text_boundary_matches_fixture() -> None:
    _check(
        "text_boundary",
        TextBoundary(
            sse_id=SseId(42, 8), kind="sentence",
            char_offset=11, ts="2026-05-28T00:00:00Z",
        ),
    )


def test_tool_start_matches_fixture() -> None:
    _check(
        "tool_start",
        ToolStart(sse_id=SseId(42, 9), name="search", arguments={"q": "ratatoskr"}),
    )


def test_tool_result_matches_fixture() -> None:
    _check(
        "tool_result",
        ToolResult(
            sse_id=SseId(42, 10), name="search",
            result={"n": 1}, duration_ms=12,
        ),
    )


def test_done_matches_fixture() -> None:
    _check(
        "done",
        Done(
            sse_id=SseId(42, 11), phase="succeeded", response="Hello there",
            model="qwen3.6-35-a3b", duration_ms=1234,
            usage={
                "prompt_tokens": 100, "completion_tokens": 50,
                "total_tokens": 150, "cached_input_tokens": 0,
            },
        ),
    )


def test_error_matches_fixture() -> None:
    _check(
        "error",
        Error(
            sse_id=SseId(42, 11), phase="failed",
            message="llm output invalid", error_code="llm_output_invalid",
        ),
    )


def test_cancelled_matches_fixture() -> None:
    _check(
        "cancelled",
        Cancelled(
            sse_id=SseId(42, 11), phase="cancelled", turn_id=42,
            reason="user_cancel", partial_message_id=None,
        ),
    )


def test_affect_update_matches_fixture() -> None:
    _check(
        "affect_update",
        AffectUpdate(
            sse_id=SseId(42, 1), status="current", turn_id=42,
            snapshot={
                "agent_id": "mimir",
                "pad": {"pleasure": 0.52, "arousal": 0.47, "dominance": 0.50},
                "dominant_emotion": "curiosity",
                "emotions_active": [
                    {"type": "curiosity", "intensity": 0.6, "decay_remaining_s": 202.7}
                ],
                "baseline_pad": {"pleasure": 0.50, "arousal": 0.40, "dominance": 0.50},
                "mood_drift": {"valence_delta": 0.02, "arousal_delta": 0.07},
                "last_updated_at": "2026-05-28T00:00:00+00:00",
            },
        ),
    )


def test_awaiting_llm_first_token_matches_fixture() -> None:
    _check(
        "awaiting_llm_first_token",
        AwaitingLlmFirstToken(
            sse_id=SseId(42, 2), turn_id=42,
            elapsed_ms_since_building_prompt=5012.3,
        ),
    )
