//! Zig-side unit tests for the subscription module (Part 13 §5.4.2).
//!
//! Covers:
//!   * Predicate parser — every grammar form + each rejection branch.
//!   * Predicate evaluator — every operator + term against synthetic
//!     events, with both intrinsic-attr matching and the lookup
//!     closure path.
//!   * RingBuffer — ordering, overflow → gap-event emission.
//!   * Registry — register, dedup, unregister, reapPeer, fan-out
//!     filtering.

const std = @import("std");
const testing = std.testing;
const json = std.json;

const model = @import("mgf_hrb_model");
const sub = @import("mgf_hrb_subscription");
const Event = model.Event;
const Urn = model.Urn;
const Err = model.Err;

// ---------------------------------------------------------------------------
// Predicate parsing.
// ---------------------------------------------------------------------------

fn parse(s: []const u8, arena: std.mem.Allocator) Err!sub.Predicate {
    var p = json.parseFromSlice(json.Value, testing.allocator, s, .{}) catch
        return Err.BadRequest;
    defer p.deinit();
    var budget: u32 = sub.MAX_PREDICATE_NODES;
    return sub.parsePredicate(p.value, arena, &budget);
}

test "predicate: kind_in single value" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse("{\"kind_in\":[\"added\"]}", arena.allocator());
    try testing.expect(pred == .kind_in);
    try testing.expectEqual(@as(usize, 1), pred.kind_in.len);
    try testing.expectEqual(sub.EventKind.added, pred.kind_in[0]);
}

test "predicate: kind_in unknown value rejects" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    try testing.expectError(Err.BadRequest, parse(
        "{\"kind_in\":[\"not_a_real_event\"]}",
        arena.allocator(),
    ));
}

test "predicate: urn_glob string" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        "{\"urn_glob\":\"hw:/usb/**\"}",
        arena.allocator(),
    );
    try testing.expectEqualStrings("hw:/usb/**", pred.urn_glob);
}

test "predicate: attr_eq parses key/value" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        "{\"attr_eq\":{\"key\":\"vid\",\"value\":\"0x1234\"}}",
        arena.allocator(),
    );
    try testing.expectEqualStrings("vid", pred.attr_eq.key);
    try testing.expectEqualStrings("0x1234", pred.attr_eq.value);
}

test "predicate: attr_in parses values list" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        "{\"attr_in\":{\"key\":\"vid\",\"values\":[\"0x1\",\"0x2\"]}}",
        arena.allocator(),
    );
    try testing.expectEqualStrings("vid", pred.attr_in.key);
    try testing.expectEqual(@as(usize, 2), pred.attr_in.values.len);
    try testing.expectEqualStrings("0x1", pred.attr_in.values[0]);
    try testing.expectEqualStrings("0x2", pred.attr_in.values[1]);
}

test "predicate: all_of nests children" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        \\{"all_of":[{"kind_in":["added"]},{"urn_glob":"hw:/x/**"}]}
    ,
        arena.allocator(),
    );
    try testing.expectEqual(@as(usize, 2), pred.all_of.len);
}

test "predicate: any_of + not" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        \\{"not":{"any_of":[{"kind_in":["gap"]}]}}
    ,
        arena.allocator(),
    );
    try testing.expect(pred == .not);
    try testing.expect(pred.not.* == .any_of);
}

test "predicate: multi-key object rejects (must be tagged-dict)" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    try testing.expectError(Err.BadRequest, parse(
        "{\"kind_in\":[\"added\"],\"urn_glob\":\"hw:/x\"}",
        arena.allocator(),
    ));
}

test "predicate: unknown operator rejects" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    try testing.expectError(Err.BadRequest, parse(
        "{\"weird_op\":[]}",
        arena.allocator(),
    ));
}

test "predicate: node-budget exhaustion rejects" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    // Build a deeply nested chain of `not` wrappers to exhaust the budget.
    var buf: [4096]u8 = undefined;
    var pos: usize = 0;
    for (0..sub.MAX_PREDICATE_NODES + 5) |_| {
        const seg = "{\"not\":";
        @memcpy(buf[pos..][0..seg.len], seg);
        pos += seg.len;
    }
    const leaf = "{\"kind_in\":[\"added\"]}";
    @memcpy(buf[pos..][0..leaf.len], leaf);
    pos += leaf.len;
    for (0..sub.MAX_PREDICATE_NODES + 5) |_| {
        buf[pos] = '}';
        pos += 1;
    }
    try testing.expectError(Err.BadRequest, parse(buf[0..pos], arena.allocator()));
}

// ---------------------------------------------------------------------------
// Predicate evaluation.
// ---------------------------------------------------------------------------

const no_lookup: sub.AttrLookup = .{ .ctx = null, .get = null };

fn ev_added(u: []const u8) Event {
    return .{ .added = .{ .urn = Urn.from(u) } };
}
fn ev_driver_bound(u: []const u8, drv: []const u8) Event {
    return .{ .driver_bound = .{
        .urn = Urn.from(u),
        .driver = model.DriverName.from(drv),
    } };
}
fn ev_gap() Event {
    return .{ .gap = .{ .source = .netlink_uevent, .since_ms = 0 } };
}

test "eval: matches_all matches every event" {
    const pred: sub.Predicate = .{ .matches_all = {} };
    try testing.expect(sub.evaluate(&pred, &ev_added("hw:/usb/x"), no_lookup));
    try testing.expect(sub.evaluate(&pred, &ev_gap(), no_lookup));
}

test "eval: kind_in" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse("{\"kind_in\":[\"added\",\"driver_bound\"]}", arena.allocator());
    try testing.expect(sub.evaluate(&pred, &ev_added("hw:/x"), no_lookup));
    try testing.expect(sub.evaluate(&pred, &ev_driver_bound("hw:/x", "usbhid"), no_lookup));
    try testing.expect(!sub.evaluate(&pred, &ev_gap(), no_lookup));
}

test "eval: urn_glob" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse("{\"urn_glob\":\"hw:/usb/**\"}", arena.allocator());
    try testing.expect(sub.evaluate(&pred, &ev_added("hw:/usb/3-9"), no_lookup));
    try testing.expect(!sub.evaluate(&pred, &ev_added("hw:/pci/0000:00:1c.0"), no_lookup));
    // Gap has no URN; urn_glob should reject (no false positives).
    try testing.expect(!sub.evaluate(&pred, &ev_gap(), no_lookup));
}

test "eval: attr_eq against intrinsic 'kind'" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        "{\"attr_eq\":{\"key\":\"kind\",\"value\":\"added\"}}",
        arena.allocator(),
    );
    try testing.expect(sub.evaluate(&pred, &ev_added("hw:/x"), no_lookup));
    try testing.expect(!sub.evaluate(&pred, &ev_gap(), no_lookup));
}

test "eval: attr_eq against intrinsic 'driver'" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        "{\"attr_eq\":{\"key\":\"driver\",\"value\":\"usbhid\"}}",
        arena.allocator(),
    );
    try testing.expect(sub.evaluate(&pred, &ev_driver_bound("hw:/x", "usbhid"), no_lookup));
    try testing.expect(!sub.evaluate(&pred, &ev_driver_bound("hw:/x", "uvcvideo"), no_lookup));
    // added has no driver field; attr_eq{driver} falls through to lookup
    // (which is null) — should NOT match.
    try testing.expect(!sub.evaluate(&pred, &ev_added("hw:/x"), no_lookup));
}

test "eval: attr_eq with device-attr lookup closure" {
    const Ctx = struct {
        const Self = @This();
        fn lookup(ctx: ?*anyopaque, urn: []const u8, key: []const u8) ?[]const u8 {
            _ = ctx;
            if (std.mem.eql(u8, urn, "hw:/usb/a") and std.mem.eql(u8, key, "vid")) {
                return "0x1234";
            }
            return null;
        }
    };
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        "{\"attr_eq\":{\"key\":\"vid\",\"value\":\"0x1234\"}}",
        arena.allocator(),
    );
    const lookup: sub.AttrLookup = .{ .ctx = null, .get = Ctx.lookup };
    try testing.expect(sub.evaluate(&pred, &ev_added("hw:/usb/a"), lookup));
    try testing.expect(!sub.evaluate(&pred, &ev_added("hw:/usb/b"), lookup));
}

test "eval: attr_in against intrinsic 'kind'" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        "{\"attr_in\":{\"key\":\"kind\",\"values\":[\"added\",\"removed\"]}}",
        arena.allocator(),
    );
    try testing.expect(sub.evaluate(&pred, &ev_added("hw:/x"), no_lookup));
    try testing.expect(!sub.evaluate(&pred, &ev_driver_bound("hw:/x", "u"), no_lookup));
}

test "eval: all_of combines (both true)" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        \\{"all_of":[{"kind_in":["driver_bound"]},{"urn_glob":"hw:/usb/**"}]}
    , arena.allocator());

    // Sanity: each child predicate evaluates independently as expected
    // (catches a regression in either kind_in or urn_glob without
    // ascribing the failure to the all_of combiner).
    try testing.expect(pred == .all_of);
    try testing.expectEqual(@as(usize, 2), pred.all_of.len);
    const dbound = ev_driver_bound("hw:/usb/3-9", "usbhid");
    try testing.expect(sub.evaluate(&pred.all_of[0], &dbound, no_lookup));
    try testing.expect(sub.evaluate(&pred.all_of[1], &dbound, no_lookup));
    try testing.expect(sub.evaluate(&pred, &dbound, no_lookup));

    const dbound_pci = ev_driver_bound("hw:/pci/x", "x");
    try testing.expect(!sub.evaluate(&pred, &dbound_pci, no_lookup));
    const added_usb = ev_added("hw:/usb/x");
    try testing.expect(!sub.evaluate(&pred, &added_usb, no_lookup));
}

test "eval: any_of (either true)" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        \\{"any_of":[{"kind_in":["added"]},{"kind_in":["driver_bound"]}]}
    , arena.allocator());
    try testing.expect(sub.evaluate(&pred, &ev_added("hw:/x"), no_lookup));
    try testing.expect(sub.evaluate(&pred, &ev_driver_bound("hw:/x", "u"), no_lookup));
    try testing.expect(!sub.evaluate(&pred, &ev_gap(), no_lookup));
}

test "eval: not inverts" {
    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    defer arena.deinit();
    const pred = try parse(
        \\{"not":{"kind_in":["gap"]}}
    , arena.allocator());
    try testing.expect(sub.evaluate(&pred, &ev_added("hw:/x"), no_lookup));
    try testing.expect(!sub.evaluate(&pred, &ev_gap(), no_lookup));
}

// ---------------------------------------------------------------------------
// Registry — fan-out + lifecycle.
// ---------------------------------------------------------------------------

test "registry: register + drain matched events" {
    sub.init(testing.allocator);
    defer sub.deinit();

    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    const pred = try parse("{\"kind_in\":[\"added\"]}", arena.allocator());

    _ = try sub.register(1234, "s1", "hw:/usb/**", pred, arena);

    sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
    sub.pushFanout(ev_driver_bound("hw:/usb/3-9", "usbhid"), no_lookup);
    sub.pushFanout(ev_added("hw:/pci/x"), no_lookup); // wrong scope
    sub.pushFanout(ev_added("hw:/usb/4-1"), no_lookup);

    var buf: [16]Event = undefined;
    const n = try sub.drainSubscription(1234, "s1", &buf);
    try testing.expectEqual(@as(usize, 2), n);
    try testing.expectEqualStrings("hw:/usb/3-9", buf[0].added.urn.slice());
    try testing.expectEqualStrings("hw:/usb/4-1", buf[1].added.urn.slice());
}

test "registry: unregister tears down" {
    sub.init(testing.allocator);
    defer sub.deinit();

    var arena = std.heap.ArenaAllocator.init(testing.allocator);
    const pred = try parse("{\"kind_in\":[\"added\"]}", arena.allocator());
    _ = try sub.register(1, "s1", "hw:/**", pred, arena);
    try testing.expectEqual(@as(usize, 1), sub.count());

    try sub.unregister(1, "s1");
    try testing.expectEqual(@as(usize, 0), sub.count());

    try testing.expectError(Err.NotFound, sub.unregister(1, "s1"));
}

test "registry: reapPeer drops all peer's subscriptions" {
    sub.init(testing.allocator);
    defer sub.deinit();

    var a1 = std.heap.ArenaAllocator.init(testing.allocator);
    var a2 = std.heap.ArenaAllocator.init(testing.allocator);
    var a3 = std.heap.ArenaAllocator.init(testing.allocator);
    const p1 = try parse("{\"kind_in\":[\"added\"]}", a1.allocator());
    const p2 = try parse("{\"kind_in\":[\"removed\"]}", a2.allocator());
    const p3 = try parse("{\"kind_in\":[\"added\"]}", a3.allocator());
    _ = try sub.register(1, "s1", "hw:/**", p1, a1);
    _ = try sub.register(1, "s2", "hw:/**", p2, a2);
    _ = try sub.register(2, "s1", "hw:/**", p3, a3); // different peer, same id ok
    try testing.expectEqual(@as(usize, 3), sub.count());

    sub.reapPeer(1);
    try testing.expectEqual(@as(usize, 1), sub.count());
    // peer 2's s1 still there.
    var buf: [4]Event = undefined;
    sub.pushFanout(ev_added("hw:/x"), no_lookup);
    try testing.expectEqual(@as(usize, 1), try sub.drainSubscription(2, "s1", &buf));
}

test "registry: duplicate (peer, id) rejects" {
    sub.init(testing.allocator);
    defer sub.deinit();

    var a1 = std.heap.ArenaAllocator.init(testing.allocator);
    var a2 = std.heap.ArenaAllocator.init(testing.allocator);
    const p1 = try parse("{\"kind_in\":[\"added\"]}", a1.allocator());
    const p2 = try parse("{\"kind_in\":[\"removed\"]}", a2.allocator());
    _ = try sub.register(1, "s1", "hw:/**", p1, a1);
    try testing.expectError(Err.BadRequest, sub.register(1, "s1", "hw:/**", p2, a2));
}

test "registry: queue overflow emits a gap event" {
    sub.init(testing.allocator);
    defer sub.deinit();

    const arena = std.heap.ArenaAllocator.init(testing.allocator);
    const pred: sub.Predicate = .{ .matches_all = {} };
    _ = try sub.register(1, "s1", "hw:/**", pred, arena);

    // Fill the queue past MAX_QUEUE_LEN.
    for (0..sub.MAX_QUEUE_LEN + 4) |i| {
        var buf: [32]u8 = undefined;
        const urn = std.fmt.bufPrint(&buf, "hw:/x/{d}", .{i}) catch unreachable;
        sub.pushFanout(ev_added(urn), no_lookup);
    }

    // Drain everything.
    var out: [sub.MAX_QUEUE_LEN]Event = undefined;
    const n = try sub.drainSubscription(1, "s1", &out);
    try testing.expectEqual(@as(usize, sub.MAX_QUEUE_LEN), n);
    // The OLDEST entries got replaced by a gap event during overflow.
    var saw_gap = false;
    for (out[0..n]) |ev| {
        if (ev == .gap and ev.gap.source == .subscription_overflow) saw_gap = true;
    }
    try testing.expect(saw_gap);
}

test "registry: gap events pass to every subscription regardless of scope" {
    sub.init(testing.allocator);
    defer sub.deinit();

    const arena = std.heap.ArenaAllocator.init(testing.allocator);
    const pred: sub.Predicate = .{ .matches_all = {} };
    _ = try sub.register(1, "s1", "hw:/very/specific/**", pred, arena);

    sub.pushFanout(ev_gap(), no_lookup);
    var buf: [4]Event = undefined;
    const n = try sub.drainSubscription(1, "s1", &buf);
    try testing.expectEqual(@as(usize, 1), n);
    try testing.expect(buf[0] == .gap);
}

// ---------------------------------------------------------------------------
// R2-A3 — concurrent pushFanout (evaluates predicates OUTSIDE the registry
// lock) racing reapPeer (which may free a subscription mid-eval). The
// refcount + deferred-free contract must hold: no use-after-free, no
// double-free, no leak. The GPA-backed testing allocator's safety checks
// ARE the assertion — a UAF/double-free/leak fails the test at teardown.
//
// `testing.allocator` is used directly (not a thread-safe wrapper): every
// real child-allocator call — `register`'s `create`, the deferred free's
// `destroy`, and the empty arena's `deinit` — happens UNDER `registry.lock`,
// so they're serialized. The `matches_all` predicate allocates nothing into
// its arena, so the only unlocked work on the churn thread (arena init) and
// the fan-out thread (Phase-2 eval) touches no allocator at all.
// ---------------------------------------------------------------------------

const StressFanout = struct {
    fn run() void {
        var i: usize = 0;
        while (i < 4000) : (i += 1) {
            // Phase-2 eval dereferences the (possibly-being-reaped)
            // predicate AST; the ref pin must keep it alive.
            sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
        }
    }
};

const StressChurn = struct {
    fn run(alloc: std.mem.Allocator) void {
        var i: usize = 0;
        while (i < 4000) : (i += 1) {
            // matches_all needs no arena allocation, so the churn loop
            // doesn't depend on the JSON `parse` helper (which would pin
            // the raw testing.allocator and race the fan-out free).
            const arena = std.heap.ArenaAllocator.init(alloc);
            const pred: sub.Predicate = .{ .matches_all = {} };
            _ = sub.register(42, "s", "hw:/usb/**", pred, arena) catch continue;
            sub.reapPeer(42); // may unlink while a fan-out eval holds a ref
        }
    }
};

test "R2-A3: concurrent pushFanout + reapPeer is UAF/leak-free (deferred free)" {
    sub.init(testing.allocator);
    defer sub.deinit();

    const t_fan = try std.Thread.spawn(.{}, StressFanout.run, .{});
    const t_churn = try std.Thread.spawn(.{}, StressChurn.run, .{testing.allocator});
    t_fan.join();
    t_churn.join();

    // Reap any survivor so deinit + the allocator's end-of-test leak check
    // see a clean registry. The real verdict is the allocator's safety
    // accounting across the ~4000 racing register/reap/eval cycles.
    sub.reapPeer(42);
    try testing.expectEqual(@as(usize, 0), sub.count());
}

// ---------------------------------------------------------------------------
// M-B B2 — long-poll drains.
//
// Everything below exercises `drainSubscriptionWait`'s three exits (events
// / death / timeout) and the missed-wakeup discipline that makes the first
// one reliable. `testing.allocator` is the GPA: a leaked subscription or a
// use-after-free on a waiter's teardown path fails these at end-of-test,
// which is what makes the teardown pin a pin rather than a hope.
// ---------------------------------------------------------------------------

const kabi = @import("mgf_hrb_kabi");
const admission = @import("mgf_hrb_admission");

/// std's `sleep` moved onto the async `std.Io` interface in 0.16; these
/// plain test threads carry no `Io`. Same raw-syscall idiom `shm.zig` uses.
fn sleepMs(ms: u64) void {
    const ts = std.os.linux.timespec{
        .sec = @intCast(ms / std.time.ms_per_s),
        .nsec = @intCast((ms % std.time.ms_per_s) * std.time.ns_per_ms),
    };
    _ = std.os.linux.nanosleep(&ts, null);
}

fn registerAll(peer: u64, id: []const u8, scope: []const u8) !void {
    const arena = std.heap.ArenaAllocator.init(testing.allocator);
    _ = try sub.register(peer, id, scope, .{ .matches_all = {} }, arena);
}

test "B2 long-poll: an empty ring with wait_ms=0 answers immediately (pre-B2 behaviour)" {
    sub.init(testing.allocator);
    defer sub.deinit();
    try registerAll(1, "s1", "hw:/**");

    var buf: [4]Event = undefined;
    const start = kabi.nowMs();
    const n = try sub.drainSubscriptionWait(1, "s1", &buf, 0);
    const elapsed = kabi.nowMs() - start;
    try testing.expectEqual(@as(usize, 0), n);
    try testing.expect(elapsed < 50); // did not block at all
}

test "B2 long-poll: a queued event is returned without waiting" {
    sub.init(testing.allocator);
    defer sub.deinit();
    try registerAll(1, "s1", "hw:/**");
    sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);

    var buf: [4]Event = undefined;
    const start = kabi.nowMs();
    const n = try sub.drainSubscriptionWait(1, "s1", &buf, 5_000);
    try testing.expectEqual(@as(usize, 1), n);
    try testing.expect(kabi.nowMs() - start < 50);
}

const LatePusher = struct {
    fn run(delay_ms: u64) void {
        sleepMs(delay_ms);
        sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
    }
};

test "B2 long-poll LATENCY: a push wakes a blocked drain in well under 100 ms" {
    sub.init(testing.allocator);
    defer sub.deinit();
    try registerAll(1, "s1", "hw:/**");

    const t = try std.Thread.spawn(.{}, LatePusher.run, .{@as(u64, 20)});
    defer t.join();

    var buf: [4]Event = undefined;
    const start = kabi.nowMs();
    const n = try sub.drainSubscriptionWait(1, "s1", &buf, 5_000);
    const elapsed = kabi.nowMs() - start;

    try testing.expectEqual(@as(usize, 1), n);
    // The pin the milestone's MB-3 gate rests on: the wake is an event, not
    // a poll interval. 100 ms is the packet's ceiling; the push is at 20 ms,
    // so anything approaching the ceiling means we slept through the wake.
    try testing.expect(elapsed < 100);
}

test "B2 long-poll: the budget lapses into an honest empty answer, not an error" {
    sub.init(testing.allocator);
    defer sub.deinit();
    try registerAll(1, "s1", "hw:/**");

    var buf: [4]Event = undefined;
    const start = kabi.nowMs();
    const n = try sub.drainSubscriptionWait(1, "s1", &buf, 120);
    const elapsed = kabi.nowMs() - start;
    try testing.expectEqual(@as(usize, 0), n);
    // It really waited (not a busy return), and it really stopped.
    try testing.expect(elapsed >= 100);
    try testing.expect(elapsed < 2_000);
}

const Reaper = struct {
    fn run(peer: u64, delay_ms: u64) void {
        sleepMs(delay_ms);
        sub.reapPeer(peer);
    }
};

test "B2 long-poll TEARDOWN: the connection dying frees the waiter, no leak" {
    sub.init(testing.allocator);
    defer sub.deinit();
    try registerAll(1, "s1", "hw:/**");

    // The real shape: a connection blocked in a 30 s long poll, and its
    // peer teardown (`handleClient`'s `defer subscription.reapPeer`) racing
    // in. The waiter must not sleep out the budget against a freed ring,
    // and must not touch the subscription it was holding a name for.
    const t = try std.Thread.spawn(.{}, Reaper.run, .{ @as(u64, 1), @as(u64, 20) });
    defer t.join();

    var buf: [4]Event = undefined;
    const start = kabi.nowMs();
    const r = sub.drainSubscriptionWait(1, "s1", &buf, 30_000);
    const elapsed = kabi.nowMs() - start;

    try testing.expectError(Err.NotFound, r);
    try testing.expect(elapsed < 1_000);
    try testing.expectEqual(@as(usize, 0), sub.count());
    // The GPA is the rest of the verdict: a subscription freed while a
    // waiter still referenced it would fail this test at teardown.
}

var race_done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false);

const RacePusher = struct {
    fn run(n: usize) void {
        var i: usize = 0;
        while (i < n) : (i += 1) {
            // A short pause before each push so the drainer is usually
            // parked in its wait when the push lands — i.e. so the pushes
            // fall INSIDE the check-then-wait window, which is the window
            // the whole proof is about.
            sleepMs(1);
            sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
        }
        race_done.store(true, .release);
        // One last push so the drainer's final lap ends on an event
        // rather than sitting out its budget.
        sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
    }
};

test "B2 long-poll MISSED-WAKEUP stress: no lap sleeps through an event it already had" {
    sub.init(testing.allocator);
    defer sub.deinit();
    try registerAll(1, "s1", "hw:/**");
    race_done.store(false, .release);

    // The pin is NOT "it finished" — a lost wakeup still finishes, one
    // budget later. It is the WORST per-lap latency among the laps that
    // returned events: if a push could land between the ring check and
    // the wait and be missed, that lap would sleep out most of its 2 s
    // budget and THEN hand back the event that had been sitting there the
    // whole time. Ordinary operation keeps every such lap in single-digit
    // milliseconds.
    const t = try std.Thread.spawn(.{}, RacePusher.run, .{@as(usize, 250)});
    defer t.join();

    var buf: [sub.MAX_QUEUE_LEN]Event = undefined;
    var laps: usize = 0;
    var got: usize = 0;
    var worst_with_events_ms: u64 = 0;
    while (!race_done.load(.acquire)) {
        const t0 = kabi.nowMs();
        const n = try sub.drainSubscriptionWait(1, "s1", &buf, 2_000);
        const dt = kabi.nowMs() - t0;
        laps += 1;
        if (n > 0) {
            got += n;
            if (dt > worst_with_events_ms) worst_with_events_ms = dt;
        }
    }
    try testing.expect(laps > 10);
    try testing.expect(got > 0);
    try testing.expect(worst_with_events_ms < 500);
}

test "B2 long-poll: an unknown subscription is NotFound even with a budget" {
    sub.init(testing.allocator);
    defer sub.deinit();
    var buf: [4]Event = undefined;
    const start = kabi.nowMs();
    try testing.expectError(
        Err.NotFound,
        sub.drainSubscriptionWait(1, "never-existed", &buf, 5_000),
    );
    // Refused up front — a name that was never registered must not consume
    // a wait budget before saying so.
    try testing.expect(kabi.nowMs() - start < 50);
}

test "B2 legacy long-poll: the hidden subscription blocks and wakes like any other" {
    sub.init(testing.allocator);
    defer sub.deinit();

    var buf: [4]Event = undefined;
    // First call opens the hidden subscription and finds nothing.
    try testing.expectEqual(@as(usize, 0), try sub.drainLegacyScope(9, "hw:/usb/**", &buf, 0));
    // F-B2-03: hidden subs are counted separately and are NOT visible.
    try testing.expectEqual(@as(usize, 1), sub.hiddenCount());
    try testing.expectEqual(@as(usize, 0), sub.count());

    const t = try std.Thread.spawn(.{}, LatePusher.run, .{@as(u64, 20)});
    defer t.join();
    const start = kabi.nowMs();
    const n = try sub.drainLegacyScope(9, "hw:/usb/**", &buf, 5_000);
    try testing.expectEqual(@as(usize, 1), n);
    try testing.expect(kabi.nowMs() - start < 100);
}

test "B2 legacy: a second scope-mode drain reuses ONE hidden subscription" {
    sub.init(testing.allocator);
    defer sub.deinit();
    var buf: [4]Event = undefined;
    _ = try sub.drainLegacyScope(9, "hw:/usb/**", &buf, 0);
    _ = try sub.drainLegacyScope(9, "hw:/usb/**", &buf, 0);
    _ = try sub.drainLegacyScope(9, "hw:/usb/**", &buf, 0);
    try testing.expectEqual(@as(usize, 1), sub.hiddenCount());
}

test "B2 legacy: two connections each keep their OWN copy of the same event" {
    sub.init(testing.allocator);
    defer sub.deinit();
    var buf: [4]Event = undefined;
    _ = try sub.drainLegacyScope(1, "hw:/usb/**", &buf, 0);
    _ = try sub.drainLegacyScope(2, "hw:/usb/**", &buf, 0);

    sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);

    try testing.expectEqual(@as(usize, 1), try sub.drainLegacyScope(1, "hw:/usb/**", &buf, 0));
    // Peer 1 draining did not empty peer 2's ring: separate rings, no theft.
    try testing.expectEqual(@as(usize, 1), try sub.drainLegacyScope(2, "hw:/usb/**", &buf, 0));
}

test "B2-AM1: the zero-subscriber fan-out short-circuits, and re-arms on register" {
    sub.init(testing.allocator);
    defer sub.deinit();
    // With no subscriptions the fan-out must be a no-op that never reaches
    // the registry lock. Observable only by consequence: registering after
    // the early-return path has been taken must still deliver.
    var i: usize = 0;
    while (i < 1000) : (i += 1) sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);

    try registerAll(1, "s1", "hw:/**");
    sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
    var buf: [4]Event = undefined;
    try testing.expectEqual(@as(usize, 1), try sub.drainSubscription(1, "s1", &buf));

    // ...and it re-arms in the other direction too: after the last
    // subscription goes, the counter must fall back to zero rather than
    // leaving the broker permanently paying for the scan.
    sub.reapPeer(1);
    sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
    try testing.expectEqual(@as(usize, 0), sub.count());
}

// ---------------------------------------------------------------------------
// M-B B2 court fold — F-B2-02 (split accounting) and F-B2-05 (one scope per
// connection).
// ---------------------------------------------------------------------------

test "F-B2-02: a saturated VISIBLE table cannot refuse a new connection's legacy drain" {
    // The court's exact scenario, reproduced. Before the split, hidden
    // legacy-drain subscriptions occupied slots in the same 256-entry
    // table as `subscribe()`, so one uid's 32 connections × 8
    // subscriptions filled it — and the next connection's FIRST
    // `drain_events`, the plainest call in the protocol and one that
    // could not fail at all before B2, answered `Overloaded`.
    sub.init(testing.allocator);
    defer sub.deinit();

    // Saturate the visible table exactly as 32 conns × 8 subs would.
    var peer: u64 = 0;
    while (peer < sub.MAX_TOTAL_SUBSCRIPTIONS / sub.MAX_SUBSCRIPTIONS_PER_PEER) : (peer += 1) {
        var k: usize = 0;
        while (k < sub.MAX_SUBSCRIPTIONS_PER_PEER) : (k += 1) {
            var id_buf: [16]u8 = undefined;
            const id = try std.fmt.bufPrint(&id_buf, "s{d}", .{k});
            const arena = std.heap.ArenaAllocator.init(testing.allocator);
            _ = try sub.register(peer, id, "hw:/**", .{ .matches_all = {} }, arena);
        }
    }
    try testing.expectEqual(sub.MAX_TOTAL_SUBSCRIPTIONS, sub.count());
    // ...and the visible table really is full, so this is not a vacuous setup.
    const spill = std.heap.ArenaAllocator.init(testing.allocator);
    try testing.expectError(
        Err.Overloaded,
        sub.register(9_999, "one-more", "hw:/**", .{ .matches_all = {} }, spill),
    );

    // A brand-new connection's legacy drain still answers, honestly empty.
    var buf: [4]Event = undefined;
    const n = try sub.drainLegacyScope(1_234, "hw:/usb/**", &buf, 0);
    try testing.expectEqual(@as(usize, 0), n);
    try testing.expectEqual(@as(usize, 1), sub.hiddenCount());
}

test "F-B2-02: the hidden table is bounded by the CONNECTION cap, not invented" {
    try testing.expectEqual(
        @as(usize, admission.MAX_ACTIVE_CLIENTS),
        sub.MAX_HIDDEN_SUBSCRIPTIONS,
    );
}

test "F-B2-02: a hidden sub still delivers — splitting the table did not split fan-out" {
    sub.init(testing.allocator);
    defer sub.deinit();
    var buf: [4]Event = undefined;
    _ = try sub.drainLegacyScope(7, "hw:/usb/**", &buf, 0);
    sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
    try testing.expectEqual(@as(usize, 1), try sub.drainLegacyScope(7, "hw:/usb/**", &buf, 0));
}

test "F-B2-03: a legacy drain does not move the operator-facing subscription count" {
    // `state_snapshot.active_subscriptions` reports `count()`. The court
    // watched it go 0 → 1 after a single scope-mode drain, so `mgf-hrb
    // doctor` reported a subscription the operator had never made.
    sub.init(testing.allocator);
    defer sub.deinit();
    try testing.expectEqual(@as(usize, 0), sub.count());

    var buf: [4]Event = undefined;
    _ = try sub.drainLegacyScope(3, "hw:/**", &buf, 0);
    _ = try sub.drainLegacyScope(4, "hw:/**", &buf, 0);
    _ = try sub.drainLegacyScope(5, "hw:/**", &buf, 0);

    try testing.expectEqual(@as(usize, 0), sub.count()); // no phantoms
    try testing.expectEqual(@as(usize, 3), sub.hiddenCount());

    // A real subscribe still shows up, so the counter is not simply dead.
    try registerAll(3, "real", "hw:/**");
    try testing.expectEqual(@as(usize, 1), sub.count());
}

test "F-B2-05: repeat legacy drains on the SAME scope are fine" {
    sub.init(testing.allocator);
    defer sub.deinit();
    var buf: [4]Event = undefined;
    _ = try sub.drainLegacyScope(2, "hw:/usb/**", &buf, 0);
    _ = try sub.drainLegacyScope(2, "hw:/usb/**", &buf, 0);
    sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
    try testing.expectEqual(@as(usize, 1), try sub.drainLegacyScope(2, "hw:/usb/**", &buf, 0));
}

test "F-B2-05: a DIFFERENT scope is refused, and refused without destroying anything" {
    // The shipped-then-withdrawn behaviour drained the ring and then threw
    // away the popped events that fell outside the newly-asked scope.
    // Popping is destructive, so those events were gone while the response
    // said ok — a silent loss (D-022). The refusal is the honest answer,
    // and this pin proves the events SURVIVE it.
    sub.init(testing.allocator);
    defer sub.deinit();
    var buf: [4]Event = undefined;
    _ = try sub.drainLegacyScope(2, "hw:/**", &buf, 0);

    sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
    sub.pushFanout(ev_added("hw:/pci/x"), no_lookup);

    // A narrower scope on the same connection: refused.
    try testing.expectError(
        Err.BadRequest,
        sub.drainLegacyScope(2, "hw:/usb/**", &buf, 0),
    );
    // ...and BOTH events are still there. Nothing was quietly discarded on
    // the way to that error.
    try testing.expectEqual(@as(usize, 2), try sub.drainLegacyScope(2, "hw:/**", &buf, 0));
}

test "F-B2-05: the refusal does not consume the wait budget" {
    sub.init(testing.allocator);
    defer sub.deinit();
    var buf: [4]Event = undefined;
    _ = try sub.drainLegacyScope(2, "hw:/**", &buf, 0);
    const start = kabi.nowMs();
    try testing.expectError(
        Err.BadRequest,
        sub.drainLegacyScope(2, "hw:/usb/**", &buf, 5_000),
    );
    // A malformed ask is answered now, not in five seconds.
    try testing.expect(kabi.nowMs() - start < 50);
}

test "F-B2-05: two connections may each hold their OWN different scope" {
    // One scope per CONNECTION — not one scope broker-wide. The refusal
    // must not have quietly become a global constraint.
    sub.init(testing.allocator);
    defer sub.deinit();
    var buf: [4]Event = undefined;
    _ = try sub.drainLegacyScope(1, "hw:/usb/**", &buf, 0);
    _ = try sub.drainLegacyScope(2, "hw:/pci/**", &buf, 0);
    sub.pushFanout(ev_added("hw:/usb/3-9"), no_lookup);
    try testing.expectEqual(@as(usize, 1), try sub.drainLegacyScope(1, "hw:/usb/**", &buf, 0));
    try testing.expectEqual(@as(usize, 0), try sub.drainLegacyScope(2, "hw:/pci/**", &buf, 0));
}
