"""Conformance: G3 — ghost ownership is structurally impossible.

G3 (design Part 1, "Reliability of representation") promises: a
lease exists only while its holder is provably alive. When a client
dies, the broker reclaims its leases — at the wire level, that means
the next connection from another client (or a reconnect) sees a clean
state. No "stuck" lease from a crashed previous client.

The Zig side proves this end-to-end via the liveness module. This
Python suite pins the property AT THE WIRE.
"""

# ruff: noqa: N802 — conformance test names cite design promises (G2/G3/S2/etc.); keeping the spec's case improves traceability.

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

if TYPE_CHECKING:
    from mgf.hrb import Client

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


def test_G3_releasing_a_lease_makes_the_urn_immediately_reclaimable(
    broker: Client,
) -> None:
    """G3 surface: after release, ANOTHER claim on the same URN
    succeeds immediately. No grace period, no eventual consistency,
    no "wait for the cleanup thread"."""
    lease_a = broker.claim("hw:/test/dev0", "exclusive")
    broker.release("hw:/test/dev0", lease_a.lease_id)

    # Immediately re-claim — no sleep, no retry.
    with broker.claim("hw:/test/dev0", "exclusive") as lease_b:
        assert lease_b.lease_id != lease_a.lease_id  # fresh lease


def test_G3_double_release_is_a_no_op(broker: Client) -> None:
    """G3 robustness: releasing a lease twice is total + idempotent
    (Part 6.3). A consumer that crashes between "decided to release"
    and "got the response" can safely retry — the broker won't error."""
    lease = broker.claim("hw:/test/dev0", "exclusive")
    broker.release("hw:/test/dev0", lease.lease_id)
    # Second release — must not raise.
    broker.release("hw:/test/dev0", lease.lease_id)


def test_G3_releasing_an_unknown_lease_is_a_no_op(broker: Client) -> None:
    """G3 robustness: release(urn, lease_id) for a lease_id that
    was never issued is a no-op (not an error). A client that
    inherited a stale lease_id from a logfile or a crash recovery
    can safely try to release it; the broker absorbs the no-op."""
    # 99999 was never minted by THIS broker (cap ids are random; the
    # chance of collision with a real lease is ~2^-63).
    broker.release("hw:/test/dev0", 99999)  # must not raise


def test_G3_claim_count_returns_to_zero_after_release(broker: Client) -> None:
    """G3 invariant: the lease table returns to its pre-claim
    cardinality after release. Caught by `list_leases().__len__()
    == 0`. A failure here means a lease leaked — exactly the
    "ghost" G3 forbids."""
    assert broker.list_leases("hw:/test/**") == []
    lease = broker.claim("hw:/test/dev0", "exclusive")
    assert len(broker.list_leases("hw:/test/**")) == 1
    broker.release("hw:/test/dev0", lease.lease_id)
    assert broker.list_leases("hw:/test/**") == []


def test_G3_client_close_does_not_leak_leases_indefinitely(
    broker: Client,
) -> None:
    """G3 cross-process: when a Client's socket closes (even without
    explicit release), the broker eventually notices via liveness
    and reclaims. Today's broker reclaims on the read returning EOF
    (handled by the thread-per-client model). Pin: after `Client.close()`,
    a SECOND client on the same broker eventually sees an empty
    lease table."""
    from mgf.hrb._client import Client as ClientCls

    sock_path = broker.socket_path
    assert sock_path is not None

    # Take a lease, then close the connection WITHOUT releasing.
    c2 = ClientCls.connect(sock_path)
    try:
        c2.claim("hw:/test/dev0", "exclusive")  # not held in a `with`
        # Don't call release; just close.
    finally:
        c2.close()

    # The broker's per-client thread receives EOF on the read loop
    # and exits. (Today's broker doesn't yet do live liveness-driven
    # holderDied() at the wire, so this test asserts the WEAKER
    # property: the broker stays up and serves other clients.)
    # When liveness wiring lands at the wire, this becomes an empty
    # list_leases assertion.
    devs = broker.list("hw:/test/**")
    # broker still alive — serves the full synthetic scope (dev0, dev1,
    # edge_attrs; see hrb-code/src/server.zig).
    assert len(devs) == 3
