"""Use case: a GUI's multi-operator lease pane.

Reproduces the hrb-app Phase-2 lease-pane scenario end-to-end. Cites:
FEEDBACK SDK-03 (the paper that motivated `Client.list_leases`).

The scenario in words:

  1. Operator A opens hrb-app, sees the device tree.
  2. Operator A claims a device. The lease pane shows it as held
     BY THIS PROCESS (cA's holder id).
  3. A SECOND operator (B) — different process, same uid — opens
     hrb-app concurrently.
  4. B's lease pane should show A's lease too (multi-operator
     visibility — the whole point of list_leases).
  5. B tries to claim the same URN; gets ConflictError (broker
     refuses double-exclusive). B's UI surfaces this.
  6. A releases (closes the with-block, or hits the GUI's "release"
     button). B retries; succeeds. The pane updates.

The test asserts at each step what the GUI's data layer would
observe — not the rendering, but the data the rendering composes from.
"""

from __future__ import annotations

import pytest

from mgf.hrb import ClaimMode, Client, ConflictError, LeaseRecord

pytestmark = [pytest.mark.use_case, pytest.mark.timeout(60)]


def test_two_operators_see_the_same_lease_state(broker: Client, sock_path: str) -> None:
    """Walks the multi-operator scenario from FEEDBACK SDK-03."""
    c_a = broker  # the fixture's connected Client
    # Open a second client — same process, same uid; the SO_PEERCRED
    # binding is idempotent (Tier-0 #2 layer-2), so this succeeds.
    c_b = Client.connect(sock_path)
    try:
        # Step 1+2: A claims, A's view shows it.
        with c_a.claim("hw:/test/dev0", ClaimMode.EXCLUSIVE) as lease_a:
            leases_a = c_a.list_leases("hw:/test/**")
            assert len(leases_a) == 1
            assert leases_a[0].lease_id == lease_a.lease_id

            # Step 3+4: B's view ALSO shows A's lease (the multi-operator
            # property the paper was filed for).
            leases_b = c_b.list_leases("hw:/test/**")
            assert len(leases_b) == 1
            assert isinstance(leases_b[0], LeaseRecord)
            assert leases_b[0].urn == "hw:/test/dev0"
            assert leases_b[0].lease_id == lease_a.lease_id
            assert leases_b[0].mode is ClaimMode.EXCLUSIVE
            # Before SDK-03 shipped, hrb-app could only see ITS OWN
            # leases — this assertion is the "what changed" pin.

            # Step 5: B's claim attempt fails with ConflictError.
            with pytest.raises(ConflictError):
                with c_b.claim("hw:/test/dev0", ClaimMode.EXCLUSIVE):
                    pass

        # Step 6: After A releases (with-block exits), B can claim.
        # And the pane sees zero leases between A's release and B's claim.
        assert c_a.list_leases("hw:/test/**") == []
        assert c_b.list_leases("hw:/test/**") == []

        with c_b.claim("hw:/test/dev0", ClaimMode.EXCLUSIVE) as lease_b:
            leases_after = c_a.list_leases("hw:/test/**")
            assert len(leases_after) == 1
            assert leases_after[0].lease_id == lease_b.lease_id
    finally:
        c_b.close()


def test_lease_pane_scope_filter_narrows_to_one_bus(broker: Client, sock_path: str) -> None:
    """The hrb-app device tree lets the user pick a bus to inspect;
    the lease pane filters by the same scope. SDK-03's scope arg is
    that filter — pin it works for both fully-broad (`hw:/**`) and
    per-bus (`hw:/usb/**`) globs."""
    c_a = broker
    with c_a.claim("hw:/test/dev0", ClaimMode.EXCLUSIVE):
        # Broad scope: sees the lease.
        assert len(c_a.list_leases("hw:/**")) == 1
        # USB-only scope: empty, because dev0 lives at hw:/test/, not hw:/usb/.
        assert c_a.list_leases("hw:/usb/**") == []
        # Test-only scope: matches.
        assert len(c_a.list_leases("hw:/test/**")) == 1
        # Non-existent bus: empty (no error).
        assert c_a.list_leases("hw:/nonexistent/**") == []


def test_granted_at_ms_orders_leases_by_grant_time(broker: Client, sock_path: str) -> None:
    """A "since when" column in the GUI sorts by granted_at_ms. The
    timestamp is the broker's monotonic clock at commit time —
    the hrb-app pane uses it for both display and sort. Pin that
    two leases granted in sequence get strictly-non-decreasing
    timestamps."""
    c_a = broker
    with c_a.claim("hw:/test/dev0", ClaimMode.EXCLUSIVE):
        with c_a.claim("hw:/test/dev1", ClaimMode.EXCLUSIVE):
            leases = c_a.list_leases("hw:/test/**")
            assert len(leases) == 2
            # Sort by granted_at_ms ascending — the order should match
            # the URNs (dev0 first, then dev1).
            sorted_by_time = sorted(leases, key=lambda r: r.granted_at_ms)
            assert sorted_by_time[0].urn == "hw:/test/dev0"
            assert sorted_by_time[1].urn == "hw:/test/dev1"
            # Both timestamps positive + monotonic.
            assert sorted_by_time[0].granted_at_ms > 0
            assert sorted_by_time[1].granted_at_ms >= sorted_by_time[0].granted_at_ms
