"""SDK-11 pins: an unknown `kind` / `family` is reported as unknown,
not as whichever concrete member happened to be the default.

**The bug** (mgf-hrb ≤ 0.1.9). `ResourceDetail.from_wire` coerced
defensively — right at the seam — but chose CONCRETE fallbacks:
``kind`` → ``Kind.PCIE_FUNCTION``, ``family`` → ``Family.CLAIMABLE``.
`Kind`'s own docstring declares an open registry, and `from_wire`'s
docstring promised "unknown enum values fall back to ``unknown``" — but
no ``unknown`` member existed. So the first hardware kind the broker
ships would have rendered as a PCIe function in every consumer surface
at once, identically and silently, with only a DEBUG line as evidence.

**The fix** (0.1.10). `Kind.UNKNOWN` and `Family.UNKNOWN` exist; the
fallback is to them; and the raw wire string is preserved on the new
optional `kind_raw` / `family_raw` so a consumer can render
"unknown (`<raw>`)" — honest AND actionable, since the raw string tells
an operator which SDK/broker pair is skewed.

**These pins** cover the four states of the coercion (match, stranger,
absent, literal-"unknown"), the additive-ness of the change (existing
members and existing positional construction unaffected), and the
hashability the frozen dataclass promises.
"""

from __future__ import annotations

import pytest

from mgf.hrb import Family, FieldFidelity, Kind, ResourceDetail

# --- the enums themselves --------------------------------------------------


def test_unknown_members_exist_with_the_wire_spelling() -> None:
    """Both enums gained an `UNKNOWN` whose wire value is the literal
    string `"unknown"` — the same spelling `FieldFidelity.UNKNOWN`
    already uses, so the three honest fields read alike on the wire."""
    assert Kind.UNKNOWN.value == "unknown"
    assert Family.UNKNOWN.value == "unknown"
    assert FieldFidelity.UNKNOWN.value == "unknown"


def test_existing_enum_members_are_untouched() -> None:
    """Additive only (AP-04): every member that existed at 0.1.9 keeps
    its identity and its wire value. A consumer's `if kind is
    Kind.USB_INTERFACE` arm must not have moved."""
    assert Kind.PCIE_FUNCTION.value == "pcie_function"
    assert Kind.USB_INTERFACE.value == "usb_interface"
    assert Kind.CPU_SET.value == "cpu_set"
    assert Kind.MEMORY_REGION.value == "memory_region"
    assert Family.CLAIMABLE.value == "claimable"
    assert Family.APPORTIONABLE.value == "apportionable"
    # And the round trip through the value still lands on the member.
    assert Kind("pcie_function") is Kind.PCIE_FUNCTION
    assert Family("claimable") is Family.CLAIMABLE


# --- the four coercion states ----------------------------------------------


def test_known_wire_values_match_and_carry_no_raw() -> None:
    """State 1 — the enum matched. `*_raw` is None, which is the signal
    a consumer branches on: `kind_raw is None` means "I know this"."""
    d = ResourceDetail.from_wire(
        {
            "urn": "hw:/usb/bus3/port-9/dev/if0",
            "family": "claimable",
            "kind": "usb_interface",
            "fidelity": "live",
        }
    )
    assert d.kind is Kind.USB_INTERFACE
    assert d.family is Family.CLAIMABLE
    assert d.kind_raw is None
    assert d.family_raw is None


def test_stranger_wire_values_become_unknown_and_preserve_the_raw() -> None:
    """State 2 — the broker knows a variant this SDK does not. THE
    pin for SDK-11: the fallback is UNKNOWN (never a concrete member),
    and the raw string survives so the consumer can render
    "unknown (photonic_link)"."""
    d = ResourceDetail.from_wire(
        {
            "urn": "hw:/photonic/link0",
            "family": "fungible",
            "kind": "photonic_link",
            "fidelity": "live",
        }
    )
    assert d.kind is Kind.UNKNOWN
    assert d.family is Family.UNKNOWN
    assert d.kind_raw == "photonic_link"
    assert d.family_raw == "fungible"
    # The regression that motivated the paper: NOT the concrete default.
    assert d.kind is not Kind.PCIE_FUNCTION
    assert d.family is not Family.CLAIMABLE


def test_absent_keys_become_unknown_with_no_raw() -> None:
    """State 3 — the key is missing entirely (older broker). There is
    no wire string to preserve and the broker asserted nothing, so
    `*_raw` stays None while the enum still refuses to guess."""
    d = ResourceDetail.from_wire({"urn": "hw:/x"})
    assert d.kind is Kind.UNKNOWN
    assert d.family is Family.UNKNOWN
    assert d.kind_raw is None
    assert d.family_raw is None


def test_literal_unknown_on_the_wire_is_a_match_not_a_fallback() -> None:
    """State 4 — the broker itself says "unknown". That IS a member, so
    it matches: `*_raw` stays None. The distinction matters because a
    consumer uses `kind_raw is not None` to mean "this SDK is older than
    this broker", and a broker honestly reporting an unknown device is
    not version skew."""
    d = ResourceDetail.from_wire({"urn": "hw:/x", "kind": "unknown", "family": "unknown"})
    assert d.kind is Kind.UNKNOWN
    assert d.family is Family.UNKNOWN
    assert d.kind_raw is None
    assert d.family_raw is None


def test_one_unknown_does_not_taint_the_other_field() -> None:
    """The two fields coerce independently — an unknown kind must not
    drag `family` to UNKNOWN, or a consumer would lose information it
    actually has."""
    d = ResourceDetail.from_wire(
        {"urn": "hw:/x", "family": "apportionable", "kind": "quantum_well"}
    )
    assert d.family is Family.APPORTIONABLE
    assert d.family_raw is None
    assert d.kind is Kind.UNKNOWN
    assert d.kind_raw == "quantum_well"


def test_non_string_wire_values_still_degrade_honestly() -> None:
    """A broker bug (or a hand-built fake) that sends a number where a
    kind belongs must not raise inside the shared projection — the
    whole point of coercing at the seam. It degrades to UNKNOWN with
    the stringified raw preserved for the bug report."""
    d = ResourceDetail.from_wire({"urn": "hw:/x", "kind": 7, "family": ["claimable"]})
    assert d.kind is Kind.UNKNOWN
    assert d.kind_raw == "7"
    assert d.family is Family.UNKNOWN
    assert d.family_raw == "['claimable']"


# --- additive-ness of the dataclass change ---------------------------------


def test_new_fields_are_last_so_positional_construction_still_works() -> None:
    """`kind_raw` / `family_raw` were appended AFTER `state`, so a
    consumer constructing `ResourceDetail` positionally against the
    0.1.9 field order is unaffected (DP-03 frozen-dataclass contract)."""
    d = ResourceDetail(
        "hw:/x",
        Family.CLAIMABLE,
        Kind.USB_INTERFACE,
        FieldFidelity.LIVE,
        (("k", "v"),),
        "hw:/parent",
        "free",
    )
    assert d.kind_raw is None
    assert d.family_raw is None
    assert d.attr("k") == "v"


def test_detail_stays_hashable_with_the_raw_fields_set() -> None:
    """DP-03: the frozen dataclass must remain hashable so a GUI can
    cache it as a model row / put it in a set. `str | None` fields are
    hashable, but the pin makes the promise explicit."""
    d = ResourceDetail.from_wire({"urn": "hw:/x", "kind": "photonic_link"})
    assert hash(d) == hash(ResourceDetail.from_wire({"urn": "hw:/x", "kind": "photonic_link"}))
    assert len({d, ResourceDetail.from_wire({"urn": "hw:/x", "kind": "photonic_link"})}) == 1


def test_unknown_is_falsy_free_and_still_a_str() -> None:
    """`Kind` is a `StrEnum`, so `UNKNOWN` renders as `"unknown"` in an
    f-string with no `.value` — the shape a consumer's
    "unknown (<raw>)" render relies on."""
    d = ResourceDetail.from_wire({"urn": "hw:/x", "kind": "photonic_link"})
    rendered = f"{d.kind}" if d.kind_raw is None else f"{d.kind} ({d.kind_raw})"
    assert rendered == "unknown (photonic_link)"


@pytest.mark.parametrize("member", list(Kind))
def test_every_kind_member_round_trips_through_from_wire(member: Kind) -> None:
    """No member — including the new UNKNOWN — is unreachable from the
    wire. Guards against a future member whose value collides or is
    mistyped."""
    d = ResourceDetail.from_wire({"urn": "hw:/x", "kind": member.value})
    assert d.kind is member
    assert d.kind_raw is None
