"""Contract: every read-only success response carries top-level
`"fidelity"` (Part 13 §7.2). Write ops + error responses do NOT
carry it.

Why a top-level field even when `inspect` already has per-URN
`fidelity` inside `resource`: the top-level represents how the
WHOLE response was assembled. Today's broker has no caching layer
so every response is `"live"`; a future `dump_all` that falls
back to cached data for some URNs would report top-level
`"cached"` while individual `items[].fidelity` stay `"live"` or
flip to `"cached"` per URN. The contract is uniform across read
ops today so consumers learn one shape.

This file pins:

  • Every read-only op (list / inspect / capabilities / drain_events
    / list_leases / dump_all) carries `"fidelity":"live"` at the
    top level on success.
  • `inspect`'s inner per-URN fidelity (inside `resource`) is
    UNCHANGED by sub-phase 1D/2 — it still ships exactly as today.
  • Write ops (claim / release / control) do NOT carry top-level
    fidelity. (Raw is excluded too; it returns an fd handle, not
    queried data.)
  • Error responses (ok:false) do NOT carry fidelity — there's no
    data to tag.
  • Coexistence with correlation_id: when both apply, the response
    carries `"fidelity":"live"` BEFORE `"correlation_id":"<id>"`
    (fidelity first, correlation_id last — easier to read in
    traces).
"""

from __future__ import annotations

import json
from typing import TYPE_CHECKING

import pytest

from mgf.hrb import Client
from tests.contract.conftest import _SOCK_PATH, rt

if TYPE_CHECKING:
    import socket

pytestmark = pytest.mark.contract


# ----- read-only ops — every one MUST carry top-level fidelity --------------


def test_list_carries_fidelity(raw_broker: socket.socket) -> None:
    resp = rt(raw_broker, {"op": "list", "scope": "hw:/test/**"})
    assert resp["ok"] is True
    assert resp.get("fidelity") == "live"


def test_inspect_carries_top_level_fidelity(raw_broker: socket.socket) -> None:
    """inspect's top-level fidelity is independent of (and ADDITIONAL
    to) the inner per-URN fidelity inside `resource`. Pin both."""
    resp = rt(raw_broker, {"op": "inspect", "urn": "hw:/test/dev0"})
    assert resp["ok"] is True
    assert resp.get("fidelity") == "live", f"top-level fidelity missing: {resp!r}"
    assert resp["resource"].get("fidelity") == "live", (
        f"per-URN fidelity missing or changed: {resp!r}"
    )


def test_capabilities_carries_fidelity(raw_broker: socket.socket) -> None:
    resp = rt(raw_broker, {"op": "capabilities"})
    assert resp["ok"] is True
    assert resp.get("fidelity") == "live"


def test_drain_events_carries_fidelity(raw_broker: socket.socket) -> None:
    resp = rt(raw_broker, {"op": "drain_events", "scope": "hw:/**"})
    assert resp["ok"] is True
    assert resp.get("fidelity") == "live"


def test_list_leases_carries_fidelity(raw_broker: socket.socket) -> None:
    resp = rt(raw_broker, {"op": "list_leases", "scope": "hw:/**"})
    assert resp["ok"] is True
    assert resp.get("fidelity") == "live"


def test_dump_all_carries_fidelity(raw_broker: socket.socket) -> None:
    resp = rt(raw_broker, {"op": "dump_all", "scope": "hw:/test/**"})
    assert resp["ok"] is True
    assert resp.get("fidelity") == "live"


# ----- error responses — must NOT carry fidelity ----------------------------


def test_inspect_error_does_not_carry_fidelity(raw_broker: socket.socket) -> None:
    """An error response means we couldn't compute anything — there's
    no fidelity to tag. Pin: no field at all (not even
    `"fidelity":"unknown"`); the response is honest about being an
    error, not a low-fidelity datum."""
    resp = rt(raw_broker, {"op": "inspect", "urn": "hw:/nonexistent"})
    assert resp["ok"] is False
    assert "fidelity" not in resp, f"error response unexpectedly carries fidelity: {resp!r}"


# ----- write ops — must NOT carry fidelity ----------------------------------


def test_release_does_not_carry_fidelity(raw_broker: socket.socket) -> None:
    """Write op success: no fidelity. The op's outcome isn't a
    fidelity question. (release is total + idempotent, so calling it
    on a non-held lease is a no-op success — exercise that path
    to avoid needing a prior claim.)"""
    resp = rt(
        raw_broker,
        {
            "op": "release",
            "urn": "hw:/test/dev0",
            "lease_id": 999999,
        },
    )
    assert resp["ok"] is True, f"release returned error: {resp!r}"
    assert "fidelity" not in resp, f"write op carries fidelity: {resp!r}"


# ----- coexistence with correlation_id (1D/1 + 1D/2 together) ---------------


def test_fidelity_and_correlation_id_coexist(raw_broker: socket.socket) -> None:
    """When BOTH apply (read-only success with a correlation_id), the
    response carries both. Order: fidelity first, correlation_id last
    (trace-readability — the correlation tag is the trailing element)."""
    resp = rt(
        raw_broker,
        {
            "op": "list",
            "scope": "hw:/test/**",
            "correlation_id": "trace-coexist-001",
        },
    )
    assert resp.get("fidelity") == "live"
    assert resp.get("correlation_id") == "trace-coexist-001"


def test_fidelity_field_order_matches_design(raw_broker: socket.socket) -> None:
    """Pin the textual order in the wire (not just dict equality).
    `"fidelity":"live"` precedes `"correlation_id":"..."` in the
    response bytes. Decoders that re-emit JSON in source order
    (some loggers) will preserve this — consumers reading the raw
    line see `fidelity` first."""
    raw_broker.send(
        (
            json.dumps(
                {
                    "op": "list",
                    "scope": "hw:/test/**",
                    "correlation_id": "trace-order-001",
                }
            )
            + "\n"
        ).encode()
    )
    buf = b""
    while b"\n" not in buf:
        buf += raw_broker.recv(4096)
    line = buf.split(b"\n", 1)[0].decode()
    fidelity_pos = line.index('"fidelity"')
    correlation_pos = line.index('"correlation_id"')
    assert fidelity_pos < correlation_pos, (
        f"unexpected field order — fidelity should precede correlation_id "
        f"in the response bytes; got: {line}"
    )


# ----- SDK passthrough — Client doesn't break on the new field --------------


def test_client_list_still_works_with_top_level_fidelity(
    raw_broker: socket.socket,
) -> None:
    """The SDK's `Client.list` returns `list[str]` of URNs by reading
    `resp["resources"]`. Adding `"fidelity"` at the top level is
    additive — the SDK ignores unknown top-level keys. Pin the SDK
    keeps working unchanged."""
    client = Client.connect(_SOCK_PATH)
    try:
        urns = client.list("hw:/test/**")
        assert "hw:/test/dev0" in urns
        assert "hw:/test/dev1" in urns
    finally:
        client.close()
