"""Trust-boundary hardening pins — F-HRB-001/002/003 (review sweep 2026-06-02).

The SDK trusts the broker *after* the hello handshake (S1 gate), but the
broker's *numbers* — response length, response timing, and the shm descriptor
sizes/offsets — were still taken on faith. A wedged or buggy broker could
therefore (1) stream an unbounded response and OOM the client, (2) accept a
request and never reply, hanging the SDK thread forever despite a deadline, or
(3) name an shm region/offset that blows up the mmap or reads outside it.
Reliability is mgf-hrb's #1 priority, so each of these is now bounded.

  * F-HRB-001 — ``_MAX_RESPONSE_BYTES`` caps a single response line.
  * F-HRB-002 — ``deadline_ms`` now bounds the client's OWN socket wait, not
    only the broker's, and a socket timeout re-types to the broker
    ``TimeoutError`` family. The deadline is restored to blocking afterward.
  * F-HRB-003 — ``ShmChannel`` validates ``region_bytes`` before mapping and
    rejects a descriptor whose slot_count isn't a power of two or whose
    offsets fall outside the region (partially closes Tier-0 #5, asymmetric
    shm trust).
"""

from __future__ import annotations

import os
import socket
import struct

import pytest

from mgf.hrb import _client
from mgf.hrb._client import _Transport
from mgf.hrb._shm import (
    _HEADER_FMT,
    _MAGIC,
    _MAX_REGION_BYTES,
    _VERSION,
    ShmChannel,
)
from mgf.hrb.exceptions import MgfHrbError
from mgf.hrb.exceptions import TimeoutError as HrbTimeoutError

# ── F-HRB-001 — bounded response buffer ───────────────────────────────────


def test_recv_line_rejects_unbounded_response(monkeypatch: pytest.MonkeyPatch) -> None:
    """A broker that streams bytes with no terminating newline must not grow
    ``self._buf`` without bound. Patch the cap low and feed past it."""
    monkeypatch.setattr(_client, "_MAX_RESPONSE_BYTES", 1024)
    a, b = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
    try:
        b.sendall(b"x" * 4096)  # no "\n", exceeds the (patched) 1 KiB cap
        t = _Transport(a)
        with pytest.raises(MgfHrbError) as ei:
            t._recv_line()
        assert "unbounded" in str(ei.value).lower()
    finally:
        a.close()
        b.close()


def test_recvmsg_path_rejects_unbounded_response(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Same cap on the fd-carrying ``recvmsg`` path (used by raw/open_shm)."""
    monkeypatch.setattr(_client, "_MAX_RESPONSE_BYTES", 1024)
    a, b = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
    try:
        b.sendall(b"y" * 4096)
        t = _Transport(a)
        with pytest.raises(MgfHrbError) as ei:
            t._recv_line_with_optional_fd()
        assert "unbounded" in str(ei.value).lower()
    finally:
        a.close()
        b.close()


# ── F-HRB-002 — client-side deadline ──────────────────────────────────────


def test_call_honors_client_side_deadline_when_broker_silent() -> None:
    """Broker accepts the request and never replies. With a positive
    ``deadline_ms`` the client must time out (typed) instead of blocking
    forever, and must restore blocking mode afterward."""
    a, b = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
    try:
        t = _Transport(a)
        with pytest.raises(HrbTimeoutError) as ei:
            t._call_locked(op="list", scope="hw:/**", deadline_ms=50)
        assert "deadline" in str(ei.value).lower()
        # The `finally` restored blocking mode — no leaked timeout onto the
        # next call.
        assert a.gettimeout() is None
    finally:
        a.close()
        b.close()


def test_zero_deadline_leaves_socket_blocking() -> None:
    """``deadline_ms=0`` means "no deadline" — no socket timeout is set, and a
    prompt response returns normally."""
    a, b = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
    try:
        t = _Transport(a)
        b.sendall(b'{"ok": true}\n')
        resp = t._call_locked(op="list", scope="hw:/**", deadline_ms=0)
        assert resp["ok"] is True
        assert a.gettimeout() is None
    finally:
        a.close()
        b.close()


# ── F-HRB-003 — shm descriptor bounds ─────────────────────────────────────


def _region_fd(
    tmp_path: object,
    *,
    region_bytes: int,
    slot_count: int,
    slots_offset: int,
    head_off: int,
    tail_off: int,
    dropped_off: int,
    cap_mask: int | None = None,
) -> int:
    """Write a region file with a crafted header and return an O_RDWR fd.
    ``ShmChannel`` mmaps it and closes the fd itself."""
    cap_mask = (slot_count - 1) if cap_mask is None else cap_mask
    buf = bytearray(region_bytes)
    struct.pack_into(
        _HEADER_FMT, buf, 0, _MAGIC, _VERSION, 8, slot_count, slots_offset, 0, cap_mask
    )
    p = tmp_path / "region.bin"  # type: ignore[attr-defined]
    p.write_bytes(bytes(buf))
    return os.open(p, os.O_RDWR)


def _descriptor(
    *,
    region_bytes: int,
    slot_count: int,
    slots_offset: int,
    head_off: int,
    tail_off: int,
    dropped_off: int,
) -> dict[str, int]:
    return {
        "region_bytes": region_bytes,
        "payload_size": 8,
        "slot_count": slot_count,
        "slots_offset": slots_offset,
        "head_offset": head_off,
        "tail_offset": tail_off,
        "dropped_offset": dropped_off,
        "channel": 1,
    }


# A valid 128-byte layout: header(32) then head/tail/dropped counters at
# 32/40/48, then 4 slots of 16 bytes starting at 64 (ends at 128).
_VALID = {
    "region_bytes": 128,
    "slot_count": 4,
    "slots_offset": 64,
    "head_off": 32,
    "tail_off": 40,
    "dropped_off": 48,
}


def test_shm_valid_descriptor_constructs(tmp_path: object) -> None:
    fd = _region_fd(tmp_path, **_VALID)  # type: ignore[arg-type]
    ch = ShmChannel(fd, _descriptor(**_VALID))  # type: ignore[arg-type]
    try:
        assert ch.slot_count == 4
    finally:
        ch.close()


def test_shm_rejects_oversized_region(tmp_path: object) -> None:
    fd = _region_fd(tmp_path, **_VALID)  # type: ignore[arg-type]
    bad = {**_VALID, "region_bytes": _MAX_REGION_BYTES + 1}
    with pytest.raises(MgfHrbError) as ei:
        ShmChannel(fd, _descriptor(**bad))  # type: ignore[arg-type]
    assert "region_bytes" in str(ei.value)


def test_shm_rejects_offset_outside_region(tmp_path: object) -> None:
    fd = _region_fd(tmp_path, **_VALID)  # type: ignore[arg-type]
    bad = {**_VALID, "head_off": 200}  # past the 128-byte region
    with pytest.raises(MgfHrbError) as ei:
        ShmChannel(fd, _descriptor(**bad))  # type: ignore[arg-type]
    assert "outside" in str(ei.value).lower()


def test_shm_rejects_non_power_of_two_slot_count(tmp_path: object) -> None:
    # header + descriptor agree (cap_mask = slot_count - 1 = 2), but 3 is not
    # a power of two, so `idx & mask` would mis-wrap.
    spec = {**_VALID, "slot_count": 3}
    fd = _region_fd(tmp_path, cap_mask=2, **spec)  # type: ignore[arg-type]
    with pytest.raises(MgfHrbError) as ei:
        ShmChannel(fd, _descriptor(**spec))  # type: ignore[arg-type]
    assert "power of two" in str(ei.value).lower()
