"""Contract: the broker's ``open_shm_channel`` wire op + the SDK
``ShmChannel`` (Part 5.3 shared-memory data plane, R-TIER0-5-DATAPLANE).

Pinned here (slice 2 — open + map + validate):

  * **open_shm_channel shape.** Request ``{op:'open_shm_channel'}``;
    response ``{ok:true, shm:true, fd:true, channel:N, slot_count:128,
    region_bytes:4096, payload_size:8, slots_offset:256, head_offset:64,
    tail_offset:128, version:1}`` with the region's memfd passed via
    SCM_RIGHTS.
  * **ShmChannel maps the fd + validates** the in-region header against
    the wire descriptor (magic/version/sizes/offsets) — a mismatch raises.
  * **The mapping is writable** — the producer ``push`` path succeeds into
    a fresh ring (the broker-side drain round-trip is slice 3).
  * **Isolation** — each open hands out a distinct region/channel.

The deterministic push→drain→checksum round-trip lives in slice 3.
"""

from __future__ import annotations

import struct
import time
from typing import TYPE_CHECKING

from mgf.hrb import ShmChannel

if TYPE_CHECKING:
    from mgf.hrb import Client


def _poll_drained(
    broker: Client, channel: int, target: int, timeout: float = 5.0
) -> dict[str, object]:
    """Push-mode is asynchronous: the background drain thread accumulates as
    the producer pushes. Poll the broker's stats until it reports ``target``
    drained (or the timeout elapses — the caller's assertion then fails with
    the last-seen snapshot for diagnosis)."""
    deadline = time.monotonic() + timeout
    res: dict[str, object] = broker.drain_shm_channel(channel)
    while time.monotonic() < deadline:
        if int(res["drained"]) >= target:
            return res
        time.sleep(0.005)
        res = broker.drain_shm_channel(channel)
    return res


def test_open_shm_channel_descriptor_and_mapping(broker: Client) -> None:
    ch = broker.open_shm_channel()
    try:
        assert isinstance(ch, ShmChannel)
        # The descriptor was validated against the in-region header inside
        # the ctor; a constructed channel means they agreed.
        assert ch.slot_count == 128
        assert ch.channel >= 0
        # The mapping is writable: pushing into a fresh ring succeeds.
        assert ch.push(0xCAFEBABE) is True
    finally:
        ch.close()


def test_open_shm_channel_hands_out_distinct_regions(broker: Client) -> None:
    a = broker.open_shm_channel()
    b = broker.open_shm_channel()
    try:
        assert a.channel != b.channel  # private per-channel regions
    finally:
        a.close()
        b.close()


def test_shm_channel_push_fills_then_drops(broker: Client) -> None:
    ch = broker.open_shm_channel()
    try:
        # Fill the ring exactly to capacity, then the next push drops
        # (drop_newest) — honest back-pressure.
        for i in range(ch.slot_count):
            assert ch.push(i) is True
        assert ch.push(0xDEAD) is False
    finally:
        ch.close()


def test_drain_round_trip(broker: Client) -> None:
    """The deterministic cross-process proof: the client (producer) pushes a
    known sequence through the shared ring; the broker (consumer) drains it
    via popUntrustedProducer; the count + checksum + last_seq match."""
    ch = broker.open_shm_channel()
    try:
        k = 50
        expect_xor = 0
        for i in range(k):
            payload = (i * 0x1111 + 7) & 0xFFFF_FFFF_FFFF_FFFF
            assert ch.push(payload) is True
            expect_xor ^= payload
        res = broker.drain_shm_channel(ch.channel)
        assert res["drained"] == k
        assert res["checksum"] == expect_xor
        assert res["last_seq"] == k - 1  # seq == head at push time
        assert res["dropped"] == 0
        # Ring now empty — a second drain yields nothing.
        assert broker.drain_shm_channel(ch.channel)["drained"] == 0
    finally:
        ch.close()


def test_drain_overflow_accounting(broker: Client) -> None:
    """Pushing past capacity drops the newest and is honestly accounted: the
    broker drains exactly slot_count items and reports the drop count."""
    ch = broker.open_shm_channel()
    try:
        overflow = 10
        for i in range(ch.slot_count + overflow):
            ch.push(i)
        res = broker.drain_shm_channel(ch.channel)
        assert res["drained"] == ch.slot_count
        assert res["dropped"] == overflow
    finally:
        ch.close()


def test_push_mode_background_drain_round_trip(broker: Client) -> None:
    """Slice 4: with a background drainer running, the client just pushes and
    the broker drains continuously — no per-batch drain RPC. The accumulated
    stats (polled via drain_shm_channel) match the produced sequence."""
    ch = broker.open_shm_channel()
    try:
        started = broker.start_shm_drain(ch.channel)
        assert started["ok"] is True
        assert started["draining"] is True
        assert started["channel"] == ch.channel

        # k < slot_count so nothing is dropped regardless of drain timing.
        k = 40
        expect_xor = 0
        for i in range(k):
            payload = (i * 0x1111 + 7) & 0xFFFF_FFFF_FFFF_FFFF
            assert ch.push(payload) is True
            expect_xor ^= payload

        res = _poll_drained(broker, ch.channel, k)
        assert res["drained"] == k
        assert res["checksum"] == expect_xor
        assert res["last_seq"] == k - 1
        assert res["dropped"] == 0
    finally:
        ch.close()


def test_start_shm_drain_is_owner_checked_and_idempotent(broker: Client) -> None:
    """A second start on the same channel is refused (already draining), and a
    bogus channel index is refused — both surface as a typed exception (the
    SDK translates the wire error per EH-01/-02), not a crash."""
    import pytest

    from mgf.hrb.exceptions import MgfHrbError

    ch = broker.open_shm_channel()
    try:
        assert broker.start_shm_drain(ch.channel)["ok"] is True
        # Already draining → the broker refuses the second start.
        with pytest.raises(MgfHrbError):
            broker.start_shm_drain(ch.channel)
        # A channel index that was never opened → refused too.
        with pytest.raises(MgfHrbError):
            broker.start_shm_drain(9999)
    finally:
        ch.close()


def test_start_shm_drain_then_close_does_not_hang(broker: Client) -> None:
    """Lifecycle: closing a channel (and disconnecting) with a live background
    drainer must join the thread cleanly. If the join hung, this test would
    trip the suite timeout rather than pass."""
    ch = broker.open_shm_channel()
    broker.start_shm_drain(ch.channel)
    for i in range(10):
        ch.push(i)
    # The close()/disconnect path must stop+join the drain thread.
    ch.close()


def test_drain_survives_hostile_producer_fields(broker: Client) -> None:
    """Asymmetric trust at the wire: scribble garbage into the producer-
    written control word (head) via the mmap; the broker drain must return
    cleanly (bounded, no hang, no crash) — popUntrustedProducer + the
    capacity-bounded drain loop guarantee it."""
    ch = broker.open_shm_channel()
    try:
        ch.push(0x1234)
        # Forge `head` to a huge value directly in the shared region.
        struct.pack_into("<Q", ch._mm, ch._head_off, 0xDEAD_BEEF_DEAD_BEEF)
        res = broker.drain_shm_channel(ch.channel)
        # The broker survived and returned a bounded count (≤ capacity).
        assert res["ok"] is True
        assert 0 <= res["drained"] <= ch.slot_count
    finally:
        ch.close()
