// @vitest-environment node

import { createHash } from "node:crypto";
import { cp, 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 { writeEngineChecksums } from "./checksummed-engine-pack.mjs";
import { CPP_LOCK, verifyCppPack } from "./cpp-engine-pack.mjs";

const temporaryDirectories: string[] = [];

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

describe("C++ engine-pack gate", () => {
  it("verifies the installed exact Emscripten/libc++ pack and bridge sources", async () => {
    await expect(
      verifyCppPack(path.resolve("public", "engines", "cpp")),
    ).resolves.toEqual(
      expect.objectContaining({
        engineVersion: "Emscripten 6.0.4 libc++ std::wregex",
        bridge: expect.objectContaining({
          abiVersion: 1,
          dynamicExecutionDisabled: true,
          replacement: {
            semantics: "libc++ match_results::format default grammar",
            implementation:
              "bounded streaming expansion without materializing an unbounded native format result",
            paritySelfTest:
              "load-time fixtures compare the bounded expander with libc++ match_results::format",
          },
        }),
        toolchain: expect.objectContaining({
          version: "6.0.4",
          commitSha1: "fe5be6afdff43ad58860d821fcc8572a23f92d19",
          emsdkCommitSha1: "224ec5f9f2f72f09f9ce0e26d66bae7dbd8b692f",
          releaseCommitSha1: "b23272fac5d05617cd36dc451356dca0f79bf22d",
          llvmVersion: "24.0.0git",
          llvmCommitSha1: "c050c487e9edb97ef44f53cb29fe1d8bcddb8f76",
        }),
        licensing: {
          sourceManifest: "SOURCE-MANIFEST.json",
          linkedComponentCount: 6,
          auditedExclusions: ["libunwind"],
          independentlyPinnedLegalFiles: 6,
        },
      }),
    );
  });

  it("pins exception-enabled compilation and its exact traced native component set", async () => {
    expect(CPP_LOCK.emscriptenVersion).toBe("6.0.4");
    expect(CPP_LOCK.linkedArchives).toEqual([
      "libc++.a",
      "libc++abi.a",
      "libc.a",
      "libclang_rt.builtins.a",
      "libembind-rtti.a",
      "libemmalloc.a",
      "libnoexit.a",
      "libstubs.a",
    ]);
    const metadata = JSON.parse(
      await readFile(
        path.resolve("public", "engines", "cpp", "engine-metadata.json"),
        "utf8",
      ),
    ) as { toolchain: { command: string } };
    expect(metadata.toolchain.command).toContain("-fexceptions");
    expect(metadata.toolchain.command).toContain("-sMALLOC=emmalloc");
    expect(metadata.toolchain.command).toContain("-Wl,--trace");

    const sourceManifest = JSON.parse(
      await readFile(
        path.resolve("public", "engines", "cpp", "SOURCE-MANIFEST.json"),
        "utf8",
      ),
    ) as {
      buildSelection: { exactLinkedArchives: string[] };
      sourceEvidence: { sourcePath: string; sourceSha256: string }[];
      linkedComponents: {
        id: string;
        linked: boolean;
        legalFiles: string[];
      }[];
    };
    expect(sourceManifest.buildSelection.exactLinkedArchives).toEqual(
      CPP_LOCK.linkedArchives,
    );
    expect(sourceManifest.sourceEvidence).toContainEqual({
      sourcePath: "system/lib/emmalloc.c",
      sourceSha256:
        "00eb577cbef51a3359c309f45a7c99a814b8c6151ab75c9a3c78a56f0169146b",
    });
    expect(
      sourceManifest.linkedComponents
        .filter(({ linked }) => linked)
        .map(({ id }) => id),
    ).toEqual([
      "emscripten-runtime",
      "musl",
      "emmalloc",
      "compiler-rt",
      "libc++",
      "libc++abi",
    ]);
    expect(
      sourceManifest.linkedComponents.find(({ id }) => id === "libunwind"),
    ).toEqual(
      expect.objectContaining({
        linked: false,
        legalFiles: ["LICENSE-libunwind.txt"],
      }),
    );

    const module = await readFile(
      path.resolve("public", "engines", "cpp", "cpp-regex.mjs"),
      "utf8",
    );
    expect(module).not.toContain("new Function");
    expect(module).not.toMatch(/\beval\s*\(/u);
  });

  it("rejects substituted legal text even when self-reported checksums are regenerated", async () => {
    const directory = await mkdtemp(path.join(tmpdir(), "regex-cpp-pack-"));
    temporaryDirectories.push(directory);
    const pack = path.join(directory, "cpp");
    await cp(path.resolve("public", "engines", "cpp"), pack, {
      recursive: true,
    });
    const replacement = Buffer.from("substituted legal text\n");
    await writeFile(path.join(pack, "LICENSE-Emscripten.txt"), replacement);
    const metadataFile = path.join(pack, "engine-metadata.json");
    const metadata = JSON.parse(await readFile(metadataFile, "utf8")) as {
      files: { path: string; sha256: string; bytes: number }[];
    };
    const record = metadata.files.find(
      ({ path: name }) => name === "LICENSE-Emscripten.txt",
    );
    expect(record).toBeDefined();
    if (!record) return;
    record.sha256 = createHash("sha256").update(replacement).digest("hex");
    record.bytes = replacement.byteLength;
    await writeFile(metadataFile, `${JSON.stringify(metadata, null, 2)}\n`);
    await writeEngineChecksums(pack);

    await expect(verifyCppPack(pack)).rejects.toThrow(
      /metadata drifted from its pinned sources/u,
    );
  });

  it("rejects a forged source inventory even when self-reported checksums are regenerated", async () => {
    const directory = await mkdtemp(path.join(tmpdir(), "regex-cpp-pack-"));
    temporaryDirectories.push(directory);
    const pack = path.join(directory, "cpp");
    await cp(path.resolve("public", "engines", "cpp"), pack, {
      recursive: true,
    });
    const sourceManifestFile = path.join(pack, "SOURCE-MANIFEST.json");
    const sourceManifest = JSON.parse(
      await readFile(sourceManifestFile, "utf8"),
    ) as {
      linkedComponents: { id: string; linked: boolean }[];
    };
    const libunwind = sourceManifest.linkedComponents.find(
      ({ id }) => id === "libunwind",
    );
    expect(libunwind).toBeDefined();
    if (!libunwind) return;
    libunwind.linked = true;
    const replacement = Buffer.from(
      `${JSON.stringify(sourceManifest, null, 2)}\n`,
    );
    await writeFile(sourceManifestFile, replacement);
    const metadataFile = path.join(pack, "engine-metadata.json");
    const metadata = JSON.parse(await readFile(metadataFile, "utf8")) as {
      files: { path: string; sha256: string; bytes: number }[];
    };
    const record = metadata.files.find(
      ({ path: name }) => name === "SOURCE-MANIFEST.json",
    );
    expect(record).toBeDefined();
    if (!record) return;
    record.sha256 = createHash("sha256").update(replacement).digest("hex");
    record.bytes = replacement.byteLength;
    await writeFile(metadataFile, `${JSON.stringify(metadata, null, 2)}\n`);
    await writeEngineChecksums(pack);

    await expect(verifyCppPack(pack)).rejects.toThrow(
      /metadata drifted from its pinned sources/u,
    );
  });
});
