"""R-T1-11D (survivability §3.6, Tier-1 #11) — capability-filtered
enumeration on the single-URN ops ``inspect`` and ``dump_chain``.

This is the final mechanism slice of Tier-1 #11. R-T1-11A closed the S3
isolation gap for ``list``; R-T1-11B for ``list_leases`` / ``dump_all`` /
``inspect_full``. These two ops are single-URN (like ``inspect_full``),
so they use the SAME pattern: when the caller presents a cap_id that
can't name the target URN, the broker returns ``NotFound`` — an
out-of-scope URN is indistinguishable from a nonexistent one (existence
itself is not observable beyond the grant).

  * ``inspect``     — resource + attrs for one URN.
  * ``dump_chain``  — parent-chain topology walk for one URN. The chain
    above the leaf is substrate topology, not separately-claimable
    broker URNs, so the S3 check is on the named leaf only.

As with the earlier slices the SDK doesn't yet pass ``cap_id`` on these
ops — that's R-T1-11C. The contract pin proves the broker honors the
field on the wire. The scope-narrowing decision itself is the shared
``capPermitsNaming`` / ``usesCapFilter`` surface already pinned in
``facade_test.zig`` (R-T1-11A); these wire tests add per-op proof that
the parser accepts the field and the formatter applies the root check.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from tests.contract.conftest import rt

if TYPE_CHECKING:
    import socket

pytestmark = pytest.mark.contract


def _first_urn(sock: socket.socket) -> str:
    resp = rt(sock, {"op": "list", "scope": "hw:/**"})
    urns = resp.get("resources", [])
    if not urns:
        pytest.skip("host exposes no resources; single-URN pins need a real URN")
    return urns[0]


# ---------------------------------------------------------------------------
# inspect
# ---------------------------------------------------------------------------


def test_inspect_with_zero_cap_id_preserves_legacy_behavior(
    raw_broker_with_cap: tuple[socket.socket, int],
) -> None:
    """Omitted cap_id and explicit cap_id=0 are equivalent — both run the
    legacy unfiltered path (the documented 'no cap presented' sentinel)."""
    sock, _root_cap = raw_broker_with_cap
    urn = _first_urn(sock)
    resp_omit = rt(sock, {"op": "inspect", "urn": urn})
    resp_zero = rt(sock, {"op": "inspect", "urn": urn, "cap_id": 0})
    assert resp_omit.get("ok") is True
    assert resp_zero.get("ok") is True


def test_inspect_with_root_cap_succeeds(
    raw_broker_with_cap: tuple[socket.socket, int],
) -> None:
    """Root cap scope hw:/** contains every URN, so inspect of a real URN
    under it succeeds (the filter applies but permits the URN)."""
    sock, root_cap = raw_broker_with_cap
    urn = _first_urn(sock)
    resp = rt(sock, {"op": "inspect", "urn": urn, "cap_id": root_cap})
    assert resp.get("ok") is True, f"root cap should permit naming {urn}; got {resp!r}"
    assert "resource" in resp


def test_inspect_with_unknown_cap_id_is_not_found(
    raw_broker_with_cap: tuple[socket.socket, int],
) -> None:
    """The S3 pin: an unknown cap_id can't name the URN, so inspect of a
    real URN returns NotFound — the holder can't even tell it exists.
    Not NotPermitted (that would leak existence)."""
    sock, _root_cap = raw_broker_with_cap
    urn = _first_urn(sock)
    assert rt(sock, {"op": "inspect", "urn": urn}).get("ok") is True
    resp = rt(sock, {"op": "inspect", "urn": urn, "cap_id": 0xDEAD_BEEF})
    assert resp.get("ok") is False, (
        f"unknown cap_id naming a real URN must not succeed; got {resp!r}"
    )
    assert resp.get("err") == "NotFound", (
        f"out-of-scope inspect must be indistinguishable from a nonexistent "
        f"URN (NotFound), not a NotPermitted leak; got {resp!r}"
    )


# ---------------------------------------------------------------------------
# dump_chain
# ---------------------------------------------------------------------------


def test_dump_chain_with_zero_cap_id_preserves_legacy_behavior(
    raw_broker_with_cap: tuple[socket.socket, int],
) -> None:
    """Omitted cap_id and explicit cap_id=0 are equivalent on dump_chain."""
    sock, _root_cap = raw_broker_with_cap
    urn = _first_urn(sock)
    resp_omit = rt(sock, {"op": "dump_chain", "urn": urn})
    resp_zero = rt(sock, {"op": "dump_chain", "urn": urn, "cap_id": 0})
    assert resp_omit.get("ok") is True
    assert resp_zero.get("ok") is True


def test_dump_chain_with_root_cap_succeeds(
    raw_broker_with_cap: tuple[socket.socket, int],
) -> None:
    """Root cap permits the leaf URN, so the chain walk succeeds."""
    sock, root_cap = raw_broker_with_cap
    urn = _first_urn(sock)
    resp = rt(sock, {"op": "dump_chain", "urn": urn, "cap_id": root_cap})
    assert resp.get("ok") is True, f"root cap should permit naming {urn}; got {resp!r}"
    assert "chain" in resp


def test_dump_chain_with_unknown_cap_id_is_not_found(
    raw_broker_with_cap: tuple[socket.socket, int],
) -> None:
    """The S3 pin for the chain walk: unknown cap_id ⇒ NotFound on the
    named leaf URN (the chain above is never walked / leaked)."""
    sock, _root_cap = raw_broker_with_cap
    urn = _first_urn(sock)
    assert rt(sock, {"op": "dump_chain", "urn": urn}).get("ok") is True
    resp = rt(sock, {"op": "dump_chain", "urn": urn, "cap_id": 0xDEAD_BEEF})
    assert resp.get("ok") is False, (
        f"unknown cap_id naming a real URN must not succeed; got {resp!r}"
    )
    assert resp.get("err") == "NotFound", (
        f"out-of-scope dump_chain must be indistinguishable from a "
        f"nonexistent URN (NotFound); got {resp!r}"
    )
