//! urn_test — property / fuzz tests for URN ⇄ glob matching
//! (`model.Urn.matches`). The scope grammar — trailing-`**` ⇒ prefix
//! match, otherwise exact equality — underpins every enumeration and
//! every capability-scope decision (`list`, `dump_all`, cap filtering),
//! so its invariants are load-bearing.
//!
//! This is the COVERAGE_MAP "urn_glob properties" row, done in ZIG where
//! the matcher actually runs (a Python property test would only exercise
//! a proxy or pay a broker round-trip per example). Generated inputs use
//! a small URN-shaped alphabet so prefixes recur and the prefix logic is
//! genuinely exercised, plus a raw-bytes fuzz for never-panics.

const std = @import("std");
const testing = std.testing;
const Urn = @import("mgf_hrb_model").Urn;

// URN-shaped alphabet: real separators + a few letters/digits so random
// strings share prefixes (which is what the `**` logic keys on).
const ALPHABET = "hw:/pciusbtest0123-.";

fn randUrn(rand: std.Random, buf: []u8) []const u8 {
    // Bounded well under MAX_URN_BYTES (128) so `Urn.from` never truncates
    // and we always have room to append "**" + a byte.
    const n = rand.intRangeAtMost(usize, 0, 100);
    for (buf[0..n]) |*b| b.* = ALPHABET[rand.intRangeLessThan(usize, 0, ALPHABET.len)];
    return buf[0..n];
}

test "urn property: the universal glob `**` matches every URN" {
    var prng = std.Random.DefaultPrng.init(0x013B_C0FFEE);
    const rand = prng.random();
    var buf: [128]u8 = undefined;
    var i: usize = 0;
    while (i < 3000) : (i += 1) {
        const s = randUrn(rand, &buf);
        const u = Urn.from(s);
        // glob "**" → prefix "" → startsWith("") is always true.
        try testing.expect(u.matches("**"));
    }
}

test "urn property: a URN matches its own string exactly AND its own `**`" {
    var prng = std.Random.DefaultPrng.init(0x5EED_1234);
    const rand = prng.random();
    var sbuf: [128]u8 = undefined;
    var gbuf: [128]u8 = undefined;
    var i: usize = 0;
    while (i < 3000) : (i += 1) {
        const s = randUrn(rand, &sbuf);
        const u = Urn.from(s);
        // Exact self-match (no `**` ⇒ equality).
        try testing.expect(u.matches(s));
        // Self-prefix `**`: `<urn>**` — the URN startsWith itself.
        @memcpy(gbuf[0..s.len], s);
        gbuf[s.len] = '*';
        gbuf[s.len + 1] = '*';
        try testing.expect(u.matches(gbuf[0 .. s.len + 2]));
    }
}

test "urn property: a URN matches every one of its own prefixes + `**`" {
    var prng = std.Random.DefaultPrng.init(0xA11CE_99);
    const rand = prng.random();
    var sbuf: [128]u8 = undefined;
    var gbuf: [128]u8 = undefined;
    var i: usize = 0;
    while (i < 3000) : (i += 1) {
        const s = randUrn(rand, &sbuf);
        const u = Urn.from(s);
        if (s.len == 0) continue;
        // For a random prefix length k ≤ len, `urn[0..k] ++ "**"` matches.
        const k = rand.intRangeAtMost(usize, 0, s.len);
        @memcpy(gbuf[0..k], s[0..k]);
        gbuf[k] = '*';
        gbuf[k + 1] = '*';
        try testing.expect(u.matches(gbuf[0 .. k + 2]));
    }
}

test "urn property: nesting — narrower `**` scope implies the wider one" {
    // If P1 is a prefix of P2, then ANY urn matching `P2**` also matches
    // `P1**` (narrower scope ⊆ wider scope — the relation cap attenuation
    // relies on). Generate a base, derive two prefix lengths, and check.
    var prng = std.Random.DefaultPrng.init(0xDEED_5151);
    const rand = prng.random();
    var sbuf: [128]u8 = undefined;
    var g1: [128]u8 = undefined;
    var g2: [128]u8 = undefined;
    var i: usize = 0;
    while (i < 3000) : (i += 1) {
        const s = randUrn(rand, &sbuf);
        const u = Urn.from(s);
        const k1 = rand.intRangeAtMost(usize, 0, s.len);
        const k2 = rand.intRangeAtMost(usize, k1, s.len); // k1 ≤ k2 ≤ len
        @memcpy(g1[0..k1], s[0..k1]);
        g1[k1] = '*';
        g1[k1 + 1] = '*';
        @memcpy(g2[0..k2], s[0..k2]);
        g2[k2] = '*';
        g2[k2 + 1] = '*';
        if (u.matches(g2[0 .. k2 + 2])) {
            try testing.expect(u.matches(g1[0 .. k1 + 2]));
        }
    }
}

test "urn property: a non-`**` glob matches ONLY on exact equality" {
    var prng = std.Random.DefaultPrng.init(0xFACE_77);
    const rand = prng.random();
    var sbuf: [128]u8 = undefined;
    var gbuf: [128]u8 = undefined;
    var i: usize = 0;
    while (i < 3000) : (i += 1) {
        const s = randUrn(rand, &sbuf);
        const u = Urn.from(s);
        const g = randUrn(rand, &gbuf);
        // Skip globs ending in `**` (those are prefix-globs, not exact).
        if (std.mem.endsWith(u8, g, "**")) continue;
        try testing.expectEqual(std.mem.eql(u8, s, g), u.matches(g));
    }
}

test "urn fuzz: raw random bytes never panic / OOB the matcher" {
    var prng = std.Random.DefaultPrng.init(0xB00B_1E5);
    const rand = prng.random();
    var sbuf: [128]u8 = undefined;
    var gbuf: [128]u8 = undefined;
    var i: usize = 0;
    while (i < 4000) : (i += 1) {
        const sn = rand.intRangeAtMost(usize, 0, 120);
        const gn = rand.intRangeAtMost(usize, 0, 120);
        rand.bytes(sbuf[0..sn]);
        rand.bytes(gbuf[0..gn]);
        const u = Urn.from(sbuf[0..sn]);
        // The contract under fuzz is "no panic, no OOB" — the result value
        // is whatever; matching arbitrary bytes is harmless (the GPA's
        // safety checks fail the test on any OOB/UB).
        _ = u.matches(gbuf[0..gn]);
    }
}
