// Cross-runtime wire-contract parity test.
//
// The DPF Edge Node ships in two runtimes that submit to the same
// `/api/v1/edge/*` endpoints: Mode 1 (TypeScript container at
// services/edge-node) and Mode 4 (Go native binary at
// services/edge-node-go). Both must produce request bodies that
// satisfy the SAME Zod schema in @/lib/edge-node/wire-contract.
//
// This test replays captured request fixtures from both runtimes
// against the schema. Any field-level drift — additional fields,
// missing required fields, type changes, enum changes — becomes a
// test failure. Catching this at the schema layer (not at the route
// handler) keeps the test focused on the contract itself rather than
// the route's auth / rate-limit / DB side effects.
//
// Fixture sources:
//   - fixtures/ts/<endpoint>/v1.json: hand-curated samples that match
//     what services/edge-node would produce. Regeneration is manual;
//     the TS implementation lives in the same repo, so any change to
//     its request shape goes through this test in the same PR.
//   - fixtures/go/<endpoint>/v1.json: generated by the Go binary's
//     --print-*-fixture modes. Regenerate after building Go changes:
//       services/edge-node-go/bin/dpf-edge-node-windows-amd64.exe \
//         --print-enroll-fixture > \
//         apps/web/app/api/v1/edge/__tests__/fixtures/go/enroll/v1.json
//
// Spec: docs/superpowers/specs/2026-05-09-dpf-edge-node-design.md
// Plan: docs/superpowers/plans/2026-05-14-edge-node-t3-windows-native.md
//   § W1 — "Wire-contract test suite"

import { readFileSync } from "node:fs";
import { join } from "node:path";

import { describe, expect, it } from "vitest";
import type { ZodType } from "zod";

import { edgeEventEnvelopeSchema } from "@dpf/validators";

import { EnrollBody, HeartbeatBody } from "@/lib/edge-node/wire-contract";

const FIXTURE_ROOT = join(__dirname, "fixtures");

type RuntimeName = "ts" | "go";
type EndpointName = "enroll" | "heartbeat" | "events";

function loadFixture(runtime: RuntimeName, endpoint: EndpointName): unknown {
  const path = join(FIXTURE_ROOT, runtime, endpoint, "v1.json");
  const raw = readFileSync(path, "utf8");
  return JSON.parse(raw);
}

function fieldNames(value: unknown): string[] {
  if (typeof value !== "object" || value === null) return [];
  return Object.keys(value as Record<string, unknown>).sort();
}

describe("Wire contract parity — both runtimes pass the same schema", () => {
  const cases: Array<{
    endpoint: EndpointName;
    schema: ZodType<unknown>;
  }> = [
    { endpoint: "enroll", schema: EnrollBody },
    { endpoint: "heartbeat", schema: HeartbeatBody },
    // Slice 0 of the edge-node detection engine (BI-9FE9D48D) — keeps
    // edge-node-go internal/envelope/event.go in lockstep with the
    // packages/validators Zod via captured TS + Go fixtures.
    { endpoint: "events", schema: edgeEventEnvelopeSchema },
  ];

  for (const { endpoint, schema } of cases) {
    describe(`POST /api/v1/edge/${endpoint}`, () => {
      it("Mode 1 (TypeScript) fixture parses cleanly", () => {
        const fixture = loadFixture("ts", endpoint);
        const result = schema.safeParse(fixture);
        if (!result.success) {
          // Surface every Zod issue so a contract regression in the
          // TS fixture is debuggable from one CI run.
          const issues = result.error.issues
            .map((i) => `  - ${i.path.join(".")}: ${i.message}`)
            .join("\n");
          expect.fail(
            `TS fixture for ${endpoint} failed schema validation:\n${issues}`,
          );
        }
      });

      it("Mode 4 (Go) fixture parses cleanly", () => {
        const fixture = loadFixture("go", endpoint);
        const result = schema.safeParse(fixture);
        if (!result.success) {
          const issues = result.error.issues
            .map((i) => `  - ${i.path.join(".")}: ${i.message}`)
            .join("\n");
          expect.fail(
            `Go fixture for ${endpoint} failed schema validation:\n${issues}`,
          );
        }
      });

      it("both runtimes use the same top-level field set", () => {
        // Field-level parity check. Different VALUES are fine (one
        // runtime can have installMode=container-host, the other
        // installMode=native — the schema accepts both). What we
        // refuse is one runtime adding/removing a field the other
        // doesn't have.
        const ts = loadFixture("ts", endpoint);
        const go = loadFixture("go", endpoint);
        const tsKeys = fieldNames(ts);
        const goKeys = fieldNames(go);
        expect(tsKeys).toEqual(goKeys);
      });
    });
  }
});
