//! format_test — unit tests for the wire formatters (`format.zig`),
//! extracted from server.zig in 1A-ii. Before the decomposition these
//! were exercised ONLY indirectly via broker-spawning Python contract
//! tests; now `format.zig` is a library module, the formatters are pure
//! `fn(facade, …, out) []const u8`, and they're directly unit-testable
//! against a FakeBackend-backed facade (WS-B, the #1 coverage gap closure).

const std = @import("std");
const testing = std.testing;
const model = @import("mgf_hrb_model");
const kabi = @import("mgf_hrb_kabi");
const Core = @import("mgf_hrb_core").Core;
const facademod = @import("mgf_hrb_facade");
const FakeBackend = @import("mgf_hrb_fake_backend").FakeBackend;
const format = @import("mgf_hrb_format");

const Urn = model.Urn;
const Err = model.Err;

// ---------------------------------------------------------------------------
// errToWireCode — the WIRE-CODE CONTRACT. These strings ARE the protocol the
// SDK's `from_wire` matches on; a rename here is a wire break. The switch in
// `errToWireCode` is exhaustive over `Err` (compile-fails on a new variant),
// so this pins the string each variant maps to.
// ---------------------------------------------------------------------------

test "errToWireCode maps each Err variant to its stable wire code" {
    try testing.expectEqualStrings("NotFound", format.errToWireCode(Err.NotFound));
    try testing.expectEqualStrings("NotPermitted", format.errToWireCode(Err.NotPermitted));
    try testing.expectEqualStrings("Conflict", format.errToWireCode(Err.Conflict));
    try testing.expectEqualStrings("Unsupported", format.errToWireCode(Err.Unsupported));
    try testing.expectEqualStrings("HardwareFault", format.errToWireCode(Err.HardwareFault));
    try testing.expectEqualStrings("InvalidToken", format.errToWireCode(Err.InvalidToken));
    try testing.expectEqualStrings("BadRequest", format.errToWireCode(Err.BadRequest));
    try testing.expectEqualStrings("Timeout", format.errToWireCode(Err.Timeout));
    try testing.expectEqualStrings("Overloaded", format.errToWireCode(Err.Overloaded));
    try testing.expectEqualStrings("Revoked", format.errToWireCode(Err.Revoked));
    try testing.expectEqualStrings("Unavailable", format.errToWireCode(Err.Unavailable));
}

test "fmtErr emits the typed error envelope" {
    var out: [128]u8 = undefined;
    try testing.expectEqualStrings(
        "{\"ok\":false,\"err\":\"NotFound\"}\n",
        format.fmtErr(&out, "NotFound"),
    );
}

// ---------------------------------------------------------------------------
// Facade-backed serialisers. A 2-worker FakeBackend constellation (mirrors
// facade_test.zig's helper) lets us assert the enumeration formatters produce
// the right wire shape — empty, populated, and the honest `truncated` flag.
// ---------------------------------------------------------------------------

const Fix = struct {
    pci_fb: FakeBackend,
    usb_fb: FakeBackend,
    pci_core: Core,
    usb_core: Core,
    workers: [2]facademod.Worker,
    facade: facademod.Facade,

    fn deinit(self: *Fix) void {
        self.pci_core.deinit();
        self.usb_core.deinit();
        self.pci_fb.deinit();
        self.usb_fb.deinit();
    }
};

fn build(c: *Fix, alloc: std.mem.Allocator) !void {
    c.pci_fb = FakeBackend.init(alloc);
    c.usb_fb = FakeBackend.init(alloc);
    try c.pci_fb.add("hw:/pci/0000:01:00.0/fn0", .claimable, .pcie_function);
    try c.usb_fb.add("hw:/usb/bus1/dev4/if0", .claimable, .usb_interface);
    c.pci_core = Core.init(alloc, c.pci_fb.backend());
    c.usb_core = Core.init(alloc, c.usb_fb.backend());
    c.workers = .{
        .{ .scope_prefix = "hw:/pci/", .core = &c.pci_core },
        .{ .scope_prefix = "hw:/usb/", .core = &c.usb_core },
    };
    c.facade = facademod.Facade.init(&c.workers);
}

test "fmtList: a scope with no matches → empty resources, truncated:false" {
    var c: Fix = undefined;
    try build(&c, testing.allocator);
    defer c.deinit();
    var out: [4096]u8 = undefined;
    const r = format.fmtList(&c.facade, "hw:/nope/**", 0, &out);
    try testing.expect(std.mem.startsWith(u8, r, "{\"ok\":true,\"resources\":["));
    try testing.expect(std.mem.indexOf(u8, r, "\"resources\":[]") != null);
    try testing.expect(std.mem.indexOf(u8, r, "\"truncated\":false") != null);
}

test "fmtList: a populated scope lists the seeded URN" {
    var c: Fix = undefined;
    try build(&c, testing.allocator);
    defer c.deinit();
    var out: [4096]u8 = undefined;
    const r = format.fmtList(&c.facade, "hw:/pci/**", 0, &out);
    try testing.expect(std.mem.indexOf(u8, r, "hw:/pci/0000:01:00.0/fn0") != null);
    // The USB URN is out of this scope — must NOT appear.
    try testing.expect(std.mem.indexOf(u8, r, "hw:/usb/") == null);
    try testing.expect(std.mem.indexOf(u8, r, "\"truncated\":false") != null);
}

test "fmtListLeases: no live leases → empty leases, truncated:false" {
    var c: Fix = undefined;
    try build(&c, testing.allocator);
    defer c.deinit();
    var out: [4096]u8 = undefined;
    const r = format.fmtListLeases(&c.facade, "hw:/**", 0, &out);
    try testing.expect(std.mem.startsWith(u8, r, "{\"ok\":true,\"leases\":["));
    try testing.expect(std.mem.indexOf(u8, r, "\"leases\":[]") != null);
    try testing.expect(std.mem.indexOf(u8, r, "\"truncated\":false") != null);
}

test "fmtListLeases: a held lease is serialised with its fields" {
    var c: Fix = undefined;
    try build(&c, testing.allocator);
    defer c.deinit();
    const urn = Urn.from("hw:/pci/0000:01:00.0/fn0");
    _ = try c.pci_core.claim(&urn, .exclusive, 42, kabi.Deadline.none);
    var out: [4096]u8 = undefined;
    const r = format.fmtListLeases(&c.facade, "hw:/**", 0, &out);
    try testing.expect(std.mem.indexOf(u8, r, "hw:/pci/0000:01:00.0/fn0") != null);
    try testing.expect(std.mem.indexOf(u8, r, "\"holder\":42") != null);
    try testing.expect(std.mem.indexOf(u8, r, "\"mode\":\"exclusive\"") != null);
}
