"""Contract: the hello-handshake wire shape.

Pins the EXACT field names and types the broker emits on accept.
A consumer that doesn't speak this shape cannot authenticate
(Tier-0 #2 layer-1 — U3 forgery closure depends on the cap_id
flowing through this message).

When this test fails, the wire format intentionally changed — review
catches that the change is intentional (or rejects it).
"""

from __future__ import annotations

import json
import socket
import threading
from typing import TYPE_CHECKING, Any

import pytest

from mgf.hrb import Client, MgfHrbError

if TYPE_CHECKING:
    from pathlib import Path

pytestmark = pytest.mark.contract


def _spawn_fake_broker(
    tmp_path: Path,
    hello_payload: dict[str, Any] | str,
) -> tuple[str, threading.Thread, threading.Event]:
    """Start a fake broker on a temp socket that sends `hello_payload`
    (as JSON) on accept then idles. Returns (sock_path, thread, stop)."""
    sock_path = str(tmp_path / "fake-broker.sock")
    server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    server.bind(sock_path)
    server.listen(1)
    stop = threading.Event()

    def serve() -> None:
        try:
            client_sock, _ = server.accept()
            try:
                if isinstance(hello_payload, dict):
                    line = json.dumps(hello_payload) + "\n"
                else:
                    line = hello_payload
                client_sock.sendall(line.encode())
                stop.wait(2.0)  # idle until test signals or times out
            finally:
                client_sock.close()
        except OSError:
            pass
        finally:
            server.close()

    t = threading.Thread(target=serve, daemon=True)
    t.start()
    return sock_path, t, stop


def test_hello_carries_required_fields(tmp_path: Path) -> None:
    """The hello MUST be `{"ok": true, "hello": "mgf-hrb", "cap_id":
    <int>, "version": <str>}`. A consumer that doesn't see those four
    fields raises immediately."""
    sock_path, _t, stop = _spawn_fake_broker(
        tmp_path,
        {
            "ok": True,
            "hello": "mgf-hrb",
            "cap_id": 12345678901234567,
            "version": "0.1.0",
        },
    )
    try:
        c = Client.connect(sock_path)
        try:
            assert c._cap == 12345678901234567
        finally:
            c.close()
    finally:
        stop.set()


def test_hello_with_wrong_service_name_is_refused(tmp_path: Path) -> None:
    """If `hello` doesn't equal `"mgf-hrb"`, the consumer refuses to
    proceed — defends against accidental cross-talk with another
    daemon binding the same socket path."""
    sock_path, _t, stop = _spawn_fake_broker(
        tmp_path,
        {
            "ok": True,
            "hello": "some-other-service",
            "cap_id": 1,
            "version": "0.1.0",
        },
    )
    try:
        with pytest.raises(MgfHrbError, match="unexpected broker greeting"):
            Client.connect(sock_path)
    finally:
        stop.set()


def test_hello_cap_id_must_be_positive_int(tmp_path: Path) -> None:
    """`cap_id == 0` is the wire sentinel ("no cap presented"). A
    hello carrying 0 is malformed by construction — the consumer
    refuses rather than authenticating with a guaranteed-broken id."""
    sock_path, _t, stop = _spawn_fake_broker(
        tmp_path,
        {
            "ok": True,
            "hello": "mgf-hrb",
            "cap_id": 0,
            "version": "0.1.0",
        },
    )
    try:
        with pytest.raises(MgfHrbError, match="positive cap_id"):
            Client.connect(sock_path)
    finally:
        stop.set()


def test_hello_missing_cap_id_is_refused(tmp_path: Path) -> None:
    """A hello without a cap_id is malformed. The consumer refuses
    rather than authenticating with no cap (which would result in
    every subsequent op getting NotPermitted from the broker)."""
    sock_path, _t, stop = _spawn_fake_broker(
        tmp_path,
        {
            "ok": True,
            "hello": "mgf-hrb",
            "version": "0.1.0",
        },
    )
    try:
        with pytest.raises(MgfHrbError, match="cap_id"):
            Client.connect(sock_path)
    finally:
        stop.set()


def test_hello_malformed_json_is_refused(tmp_path: Path) -> None:
    """Garbage on the wire is a wire-protocol break, not "bad data"
    — refuse rather than try to interpret. Pins the consumer's
    error path so a future "be lenient" refactor doesn't accept
    half-JSON."""
    sock_path, _t, stop = _spawn_fake_broker(tmp_path, "not-json-at-all\n")
    try:
        with pytest.raises(MgfHrbError, match="handshake failed"):
            Client.connect(sock_path)
    finally:
        stop.set()
