// @vitest-environment node

import { createHash } from "node:crypto";
import {
  copyFile,
  cp,
  mkdir,
  mkdtemp,
  readFile,
  rm,
  writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
  buildPerlPack,
  installPerlPack,
  PERL_ENGINE_LOCK,
  verifyPerlPack,
} from "./perl-engine-pack.mjs";

const temporaryDirectories: string[] = [];
const installedPack = path.resolve("public", "engines", "perl");
const packFiles = [
  "LICENSE-Devel-StackTrace-Artistic-2.0.txt",
  "LICENSE-Emscripten.txt",
  "LICENSE-WebPerl-Artistic.txt",
  "LICENSE-WebPerl-GPL.txt",
  "NOTICE.txt",
  "SHA256SUMS",
  "emperl.data",
  "emperl.js",
  "emperl.wasm",
  "engine-metadata.json",
  "perl-runtime.worker.js",
  "regex_tools_bridge.pl",
] as const;

afterEach(async () => {
  await Promise.all(
    temporaryDirectories
      .splice(0)
      .map((directory) => rm(directory, { recursive: true, force: true })),
  );
});

async function digest(file: string): Promise<string> {
  return createHash("sha256")
    .update(await readFile(file))
    .digest("hex");
}

async function makeExtractedPrebuilt(parent: string): Promise<string> {
  const prebuilt = path.join(parent, "webperl_prebuilt_v0.09-beta");
  await mkdir(prebuilt, { recursive: true });
  for (const name of ["emperl.data", "emperl.js", "emperl.wasm"]) {
    await copyFile(path.join(installedPack, name), path.join(prebuilt, name));
  }
  await copyFile(
    path.join(installedPack, "LICENSE-WebPerl-Artistic.txt"),
    path.join(prebuilt, "LICENSE_artistic.txt"),
  );
  await copyFile(
    path.join(installedPack, "LICENSE-WebPerl-GPL.txt"),
    path.join(prebuilt, "LICENSE_gpl.txt"),
  );
  return prebuilt;
}

describe("Perl engine-pack gate", () => {
  it("verifies the installed closed WebPerl pack in a strict-CSP Chromium worker", async () => {
    await expect(verifyPerlPack(installedPack)).resolves.toEqual(
      expect.objectContaining({
        engineVersion: "5.28.1",
        status: "legacy-beta-compatibility-runtime",
        source: expect.objectContaining({
          tag: "v0.09-beta",
          commitSha1: "6f2173d29a2c2e3536e1de75ff5d291ae96ab348",
        }),
        bridge: expect.objectContaining({
          replacementOutputTransport: expect.stringContaining(
            "fixed MEMFS binary file",
          ),
          limits: expect.objectContaining({
            maximumResponseJsonBytes: 8_388_608,
            outputChunkCharacters: 4_096,
          }),
        }),
        securityAudit: expect.objectContaining({
          generatedLoaderJavaScriptEvalSites: 1,
          generatedLoaderNewFunctionSites: 0,
        }),
      }),
    );
  }, 60_000);

  it("pins the exact archive, runtime assets, and legacy identity", () => {
    expect(PERL_ENGINE_LOCK.engineVersion).toBe("5.28.1");
    expect(PERL_ENGINE_LOCK.webPerlVersion).toBe("0.09-beta");
    expect(PERL_ENGINE_LOCK.prebuilt.archiveSha256).toBe(
      "5f441249217e90ab378c666f473d4206ab4f44907f6bb0aa8d70834bc38c40dc",
    );
    expect(PERL_ENGINE_LOCK.prebuilt.files["emperl.wasm"]).toEqual(
      expect.objectContaining({
        bytes: 3_734_063,
        sha256:
          "f1d49c4514c7332a57992c4a2444fd6a56ae3b5e6651b4fd484852a641e5e4ec",
      }),
    );
  });

  it("builds byte-identical offline packs and installs one atomically", async () => {
    const directory = await mkdtemp(path.join(tmpdir(), "regex-perl-pack-"));
    temporaryDirectories.push(directory);
    const prebuilt = await makeExtractedPrebuilt(directory);
    const first = path.join(directory, "first");
    const second = path.join(directory, "second");

    await buildPerlPack({ prebuiltDirectory: prebuilt, output: first });
    await buildPerlPack({ prebuiltDirectory: prebuilt, output: second });

    for (const file of packFiles) {
      expect(await digest(path.join(first, file)), file).toBe(
        await digest(path.join(second, file)),
      );
    }

    const fakeRoot = path.join(directory, "repository");
    await mkdir(path.join(fakeRoot, "engines"), { recursive: true });
    await cp(
      path.resolve("engines", "perl"),
      path.join(fakeRoot, "engines", "perl"),
      { recursive: true },
    );
    const oldTarget = path.join(fakeRoot, "public", "engines", "perl");
    await mkdir(oldTarget, { recursive: true });
    await writeFile(path.join(oldTarget, "old-marker"), "old\n", "utf8");

    const installed = await installPerlPack(first, fakeRoot);

    expect(installed.output).toBe(oldTarget);
    await expect(
      readFile(path.join(oldTarget, "old-marker")),
    ).rejects.toThrow();
    await expect(
      verifyPerlPack(oldTarget, fakeRoot, { smoke: false }),
    ).resolves.toMatchObject({ engineVersion: "5.28.1" });
  }, 120_000);

  it("rejects extra pack entries and drifted extracted inputs", async () => {
    const directory = await mkdtemp(path.join(tmpdir(), "regex-perl-reject-"));
    temporaryDirectories.push(directory);
    const pack = path.join(directory, "pack");
    await cp(installedPack, pack, { recursive: true });
    await writeFile(path.join(pack, "unexpected"), "not allowed\n", "utf8");
    await expect(
      verifyPerlPack(pack, path.resolve("."), { smoke: false }),
    ).rejects.toThrow("must contain only");

    const prebuilt = await makeExtractedPrebuilt(directory);
    await writeFile(path.join(prebuilt, "emperl.wasm"), "not wasm\n");
    await expect(
      buildPerlPack({
        prebuiltDirectory: prebuilt,
        output: path.join(directory, "rejected"),
      }),
    ).rejects.toThrow("pinned WebPerl release");
  });
});
