"""Regression pin for cluster-G1 (review wave 2, commit ``b339c56``).

**The bug.** ``hrb-code/src/server.zig`` wrote 36 bytes for a
34-byte ``NOT_PERMITTED`` response — the literal was 34 chars
(33 + newline) but the call hard-coded ``linux.write(cfd, msg,
36)``. Two stack bytes leaked into every cap-reject reply.
Stream-oriented clients (which line-buffer) would silently
accept them as part of the line, sometimes corrupting the next
op's response.

**The fix.** Hoisted the literal to a top-level constant
``NOT_PERMITTED_LINE`` and passed ``.len`` instead of ``36``. The
write now uses the constant's own length — drift between literal
and count is impossible.

**This pin.** Sends two consecutive cap-gated ops without a
cap_id (the broker rejects both with NOT_PERMITTED). Asserts:

  1. Each individual response parses as the canonical JSON shape
     ``{"ok":false,"err":"NotPermitted"}`` — no trailing garbage.
  2. The raw bytes of the first response, read up to and
     including newline, are EXACTLY 34. If the bug returned,
     they'd be 36 — and the kernel buffer would carry 2 mystery
     bytes into the second op's response, which would either
     prefix the JSON (parse failure) or appear as extra bytes
     after a successful parse.
  3. Two ops in sequence both succeed (no inter-op drift). This
     is the highest-leverage assertion: pre-fix, the second op's
     reply prefix would be the two leaked stack bytes from the
     first reply, and the test would fail when JSON parsing
     tripped on garbage.

Marked ``@pytest.mark.contract`` per the federation test
taxonomy — wire-shape pins live there.
"""

from __future__ import annotations

import json
from typing import TYPE_CHECKING

import pytest

if TYPE_CHECKING:
    import socket

pytestmark = pytest.mark.contract

# The exact bytes the server now writes on a cap-reject. If the
# constant in server.zig ever changes shape (different err code,
# different field order), this test fails — the wire format is
# operator-visible and shouldn't drift silently.
_EXPECTED_NOT_PERMITTED_LINE = b'{"ok":false,"err":"NotPermitted"}\n'
assert len(_EXPECTED_NOT_PERMITTED_LINE) == 34


def _recv_one_line(sock: socket.socket, *, max_bytes: int = 512) -> bytes:
    """Read raw bytes from `sock` up to and including the first
    newline. Returns the bytes (newline-terminated). Bounded so
    a regression that produces a runaway response still fails."""
    buf = bytearray()
    while b"\n" not in buf:
        chunk = sock.recv(max_bytes - len(buf))
        if not chunk:
            break
        buf.extend(chunk)
        if len(buf) >= max_bytes:
            break
    # Trim anything after the first newline (in case the kernel
    # gave us extra — which is EXACTLY what the bug produced).
    idx = buf.find(b"\n")
    if idx < 0:
        return bytes(buf)
    return bytes(buf[: idx + 1])


def test_cap_reject_writes_exactly_34_bytes(
    raw_broker: socket.socket,
) -> None:
    """Direct wire-byte assertion. The cap-rejected response MUST
    be exactly 34 bytes (the literal's length) — not 36."""
    req = (json.dumps({"op": "revoke", "urn": "hw:/test/x"}) + "\n").encode()
    raw_broker.sendall(req)
    response = _recv_one_line(raw_broker)
    assert response == _EXPECTED_NOT_PERMITTED_LINE, (
        f"G1 regression: cap-reject wire bytes drifted from "
        f"{_EXPECTED_NOT_PERMITTED_LINE!r} to {response!r}. The "
        f"server.zig literal or the write length has changed."
    )


def test_back_to_back_cap_rejects_dont_smear(
    raw_broker: socket.socket,
) -> None:
    """The G1 bug's load-bearing failure mode: the 2 leaked stack
    bytes from the first reply prefix the second reply's JSON,
    causing json.loads to fail.

    Send two cap-rejected ops in sequence; expect both to parse
    cleanly + both to be exactly the canonical bytes. Pre-fix,
    the second one would carry a 2-byte prefix and trip JSON
    parsing — depending on what bytes the stack held."""
    req = (json.dumps({"op": "revoke", "urn": "hw:/test/x"}) + "\n").encode()

    raw_broker.sendall(req)
    first = _recv_one_line(raw_broker)
    assert json.loads(first.decode()) == {
        "ok": False,
        "err": "NotPermitted",
    }, f"first response malformed: {first!r}"

    raw_broker.sendall(req)
    second = _recv_one_line(raw_broker)
    # The killer assertion: if G1 came back, the kernel buffer
    # carries 2 mystery bytes before the second JSON line. Either
    # they'd parse-fail OR equality-fail against the canonical
    # bytes. Equality covers both.
    assert second == _EXPECTED_NOT_PERMITTED_LINE, (
        f"G1 regression: second cap-reject smeared with leaked "
        f"bytes from the first. Got {second!r}; expected "
        f"{_EXPECTED_NOT_PERMITTED_LINE!r}."
    )
    assert json.loads(second.decode()) == {
        "ok": False,
        "err": "NotPermitted",
    }


def test_burst_of_cap_rejects_stays_clean(
    raw_broker: socket.socket,
) -> None:
    """Stronger: 8 cap-rejects in a row. Pre-fix, the cumulative
    drift could shift bytes around enough to surface in unusual
    ways (e.g., one reply gets 4 bytes of garbage if the previous
    two leaked overlapped). Post-fix, every reply is exactly the
    canonical bytes."""
    req = (json.dumps({"op": "revoke", "urn": "hw:/test/x"}) + "\n").encode()
    for n in range(8):
        raw_broker.sendall(req)
        resp = _recv_one_line(raw_broker)
        assert resp == _EXPECTED_NOT_PERMITTED_LINE, f"G1 regression on iteration {n}: got {resp!r}"
