"""Smoke: the broker subprocess can spawn, accept a connection,
complete the hello handshake, and serve one read op.

Fastest cross-process signal we have. Designed so CI fails in <2s
when the broker binary is broken (vs. ~5s for the full e2e bundle).

Marker: `smoke`. Skipped (not failed) when the broker binary isn't
built — keeps the gate friendly for contributors who run only the
default Python suite. Uses the shared `_broker` helpers so each
pytest-xdist worker binds its own socket (`MGF_HRB_SOCK`).
"""

from __future__ import annotations

import socket
import time
from pathlib import Path
from typing import TYPE_CHECKING

import pytest

from mgf.hrb import Client
from tests import _broker

if TYPE_CHECKING:
    import subprocess
    from collections.abc import Iterator

pytestmark = pytest.mark.smoke

# Per-xdist-worker socket (the broker honours `MGF_HRB_SOCK`, which
# `_broker.spawn_broker` passes) — so parallel smoke runs don't collide.
_SOCK = _broker.default_sock_path()


def _need_broker() -> None:
    if not _broker.server_exists():
        pytest.skip(
            f"Broker binary not built at {_broker.server_path()}; run `cd hrb-code && zig build`."
        )


@pytest.fixture
def broker_subprocess() -> Iterator[subprocess.Popen[bytes]]:
    """Spawn the broker on the per-worker socket; yield the Popen handle;
    SIGTERM→KILL→unlink on teardown (delegated to `_broker`, which also
    closes the captured stdout to dodge the 3.13 unraisable-warning)."""
    _need_broker()
    proc = _broker.spawn_broker(_SOCK)
    try:
        yield proc
    finally:
        _broker.teardown_broker(proc, _SOCK)


def test_broker_binds_unix_socket(broker_subprocess: subprocess.Popen[bytes]) -> None:
    """The fastest possible signal: the broker process is alive and
    bound to its socket. If this fails, every other test does too —
    failing early saves CI minutes."""
    assert broker_subprocess.poll() is None, "broker died before assertion"
    assert Path(_SOCK).exists()


def test_broker_handshake_yields_high_entropy_cap(
    broker_subprocess: subprocess.Popen[bytes],
) -> None:
    """Hello handshake works AND the cap_id it carries has real
    entropy (Tier-0 #2 layer-1 — U3 forgery closure pinned at the
    smoke level so a regression to sequential ids fails fast in
    CI)."""
    assert broker_subprocess.poll() is None
    with Client.connect(_SOCK) as c:
        assert isinstance(c._cap, int)
        assert c._cap > 2**32, f"cap_id {c._cap} too low — entropy regression"


def test_broker_serves_one_list_op(
    broker_subprocess: subprocess.Popen[bytes],
) -> None:
    """One full request/response round-trip. Catches any wire-format
    serialization break that the handshake alone wouldn't surface."""
    assert broker_subprocess.poll() is None
    with Client.connect(_SOCK) as c:
        devs = c.list("hw:/test/**")
        # The fake test worker seeds dev0 + dev1 + edge_attrs
        # (the last is a G6/G2+G8 inspect-attr fixture added
        # 2026-05-27 — see server.zig's _g6_edge_attrs block).
        assert isinstance(devs, list)
        assert len(devs) == 3


def test_broker_survives_connect_then_immediate_close() -> None:
    """The exact pattern that killed the broker before the SIGPIPE
    fix (commit `8db1d8c`). `Client.doctor()`'s `check_socket` probe
    connects then closes without reading the hello; if the broker
    tries to write hello to a closed peer without ignoring SIGPIPE,
    the broker process dies. This test reproduces that without
    relying on the diagnostics path — a tighter regression pin."""
    _need_broker()
    proc = _broker.spawn_broker(_SOCK)
    try:
        # Probe — connect then close without reading the hello.
        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        s.connect(_SOCK)
        s.close()

        # The broker should still be alive afterward. Give it a brief
        # window to react to the disconnect.
        time.sleep(0.1)
        assert proc.poll() is None, "broker died after probe — SIGPIPE handler regression"

        # And it should still serve a real client.
        with Client.connect(_SOCK) as c:
            assert isinstance(c._cap, int) and c._cap > 0
    finally:
        _broker.teardown_broker(proc, _SOCK)
