/**
 * `randomId` exists because the app must boot on an insecure origin.
 *
 * `crypto.randomUUID` is defined only in a secure context. Every way a
 * developer normally opens this app is one — `vite dev` and `vite preview` serve
 * `localhost`, the deployed site is HTTPS — so a top-level call to it in
 * `main.ts` ran green for months and threw on the two origins nobody tries:
 * the brand-capture harness, which serves `dist/` over
 * `http://tera.lumbridgecorp.com:5210` so the app can read its own hostname, and
 * the plain `http://` static host `deploy/STATIC.md` invites.
 *
 * The symptom was a boot card stuck on "Starting up" and one TypeError, which is
 * why these tests check the *fallback path specifically* rather than only the
 * happy one — the happy one was never broken.
 */

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { randomId } from "../../ids.ts";

const V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;

/** Run `fn` with `globalThis.crypto` replaced, then put it back. */
function withCrypto<T>(replacement: unknown, fn: () => T): T {
  const real = Object.getOwnPropertyDescriptor(globalThis, "crypto");
  Object.defineProperty(globalThis, "crypto", {
    value: replacement,
    configurable: true,
    writable: true,
  });
  try {
    return fn();
  } finally {
    if (real === undefined) delete (globalThis as { crypto?: unknown }).crypto;
    else Object.defineProperty(globalThis, "crypto", real);
  }
}

describe("randomId", () => {
  it("is a well-formed v4 with the platform's own implementation", () => {
    assert.match(randomId(), V4);
  });

  it("still returns a well-formed v4 on an insecure origin, where randomUUID is absent", () => {
    // Exactly the shape of an insecure context: getRandomValues, no randomUUID.
    const insecure = {
      getRandomValues<T extends ArrayBufferView>(view: T): T {
        const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
        for (let i = 0; i < bytes.length; i++) bytes[i] = (i * 37 + 11) & 0xff;
        return view;
      },
    };
    const id = withCrypto(insecure, () => randomId());
    assert.match(id, V4, "the fallback must set the version and variant nibbles, not just fill bytes");
  });

  it("does not throw when there is no crypto at all", () => {
    const id = withCrypto(undefined, () => randomId());
    assert.match(id, V4);
  });

  it("does not repeat itself", () => {
    const seen = new Set(Array.from({ length: 500 }, () => randomId()));
    assert.equal(seen.size, 500);
  });

  it("main.ts mints its wire identities through it, not through crypto directly", async () => {
    const { readFileSync } = await import("node:fs");
    const source = readFileSync(new URL("../../main.ts", import.meta.url), "utf8");
    assert.ok(
      !/crypto\.randomUUID/.test(source),
      "a bare crypto.randomUUID in main.ts runs at module load and breaks boot on http://",
    );
  });
});
