"""Kernel-synchronized liveness probes for the real-subprocess stdio lifecycle suite.

A spawned (grand)child connects back to a test-owned TCP listener and sends
`b'alive'`; the kernel then provides every signal a test needs, with no sleeps or
polling. The kernel closes all of a process's file descriptors on exit, so EOF
(clean close / FIN) or `BrokenResourceError` (abrupt close / RST, typical of
SIGKILL and Windows job termination) proves death; only a running process can
answer an echo, so a reply proves liveness without racing a kill.

Extracted from the real-process section of tests/client/test_stdio.py; the two
copies on this branch are deliberate -- consolidating them is follow-up work.
"""

import anyio
import anyio.abc
import pytest


def connect_back_script(port: int, *, echo: bool = False) -> str:
    """Return a `python -c` script body that connects to 127.0.0.1:`port` and sends `b'alive'`.

    After the banner the script blocks forever -- or, with `echo=True`, echoes every
    received chunk back so `assert_peer_echoes` can prove the process still runs.
    """
    # lax no cover: echo mode is used only by POSIX-gated tests; Windows runners enforce 100% per job.
    if echo:  # pragma: lax no cover
        tail = "while True:\n    data = s.recv(65536)\n    if not data:\n        break\n    s.sendall(data)\n"
    else:
        tail = "time.sleep(3600)\n"
    return f"import socket, time\ns = socket.create_connection(('127.0.0.1', {port}))\ns.sendall(b'alive')\n" + tail


async def open_liveness_listener() -> tuple[anyio.abc.SocketListener, int]:
    """Open a TCP listener on localhost and return it along with its port."""
    multi = await anyio.create_tcp_listener(local_host="127.0.0.1")
    sock = multi.listeners[0]
    assert isinstance(sock, anyio.abc.SocketListener)
    addr = sock.extra(anyio.abc.SocketAttribute.local_address)
    # IPv4 local_address is (host: str, port: int)
    assert isinstance(addr, tuple) and len(addr) >= 2 and isinstance(addr[1], int)
    return sock, addr[1]


async def accept_alive(sock: anyio.abc.SocketListener) -> anyio.abc.SocketStream:
    """Accept one connection and assert the peer sent `b'alive'`.

    Reads until the full 5-byte banner arrives (TCP may legally split even a tiny
    send). Callers bound this with `anyio.fail_after` to catch a subprocess that
    never started.
    """
    stream = await sock.accept()
    msg = b""
    while len(msg) < 5:
        msg += await stream.receive(5 - len(msg))
    assert msg == b"alive", f"expected b'alive', got {msg!r}"
    return stream


async def assert_stream_closed(stream: anyio.abc.SocketStream) -> None:
    """Assert the peer holding the other end of `stream` has terminated."""
    with anyio.fail_after(5.0), pytest.raises((anyio.EndOfStream, anyio.BrokenResourceError)):
        await stream.receive(1)


async def assert_peer_echoes(stream: anyio.abc.SocketStream) -> None:  # pragma: lax no cover
    """Assert the peer holding the other end of `stream` is still running.

    Round-trips one echo through the stream (the peer must use `echo=True`); a dead
    process can never answer, so this cannot pass spuriously.

    lax no cover: only POSIX-gated survival tests call this; Windows runners
    enforce 100% coverage per job.
    """
    with anyio.fail_after(5.0):
        await stream.send(b"ping")
        # Read until the full echo has arrived: TCP may legally split even a tiny send.
        echoed = b""
        while len(echoed) < 4:
            echoed += await stream.receive(4 - len(echoed))
    assert echoed == b"ping", f"expected b'ping', got {echoed!r}"
