// @vitest-environment node

import { createHash } from "node:crypto";
import {
  appendFile,
  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 {
  buildPhpPack,
  PHP_ENGINE_LOCK,
  verifyPhpPack,
} from "./php-engine-pack.mjs";

const temporaryDirectories: string[] = [];

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");
}

describe("PHP engine-pack gate", () => {
  it("verifies the installed Asyncify PHP runtime and native smoke cases", async () => {
    await expect(
      verifyPhpPack(path.resolve("public", "engines", "php")),
    ).resolves.toEqual(
      expect.objectContaining({
        engineVersion: "PHP 8.5.8 preg / PCRE2 10.44",
        platform: "wasm32-emscripten",
        source: expect.objectContaining({
          runtimeVariant: "PHP 8.5 Asyncify web build (without intl extension)",
          nativeComponents: expect.objectContaining({
            linkedComponentCount: 32,
            excludedDetectionCount: 7,
          }),
        }),
        licensing: expect.objectContaining({
          nativeInventory: expect.objectContaining({
            independentlyPinnedLegalAssets: 42,
          }),
        }),
      }),
    );
  });

  it("pins exact package, loader, and WebAssembly identities", () => {
    expect(PHP_ENGINE_LOCK.packageVersion).toBe("3.1.46");
    expect(PHP_ENGINE_LOCK.phpVersion).toBe("8.5.8");
    expect(PHP_ENGINE_LOCK.pcreVersion).toBe("10.44");
    expect(PHP_ENGINE_LOCK.asyncMode).toBe("asyncify");
    expect(PHP_ENGINE_LOCK.npm.web.integrity).toMatch(/^sha512-/u);
    expect(PHP_ENGINE_LOCK.npm.universal.integrity).toMatch(/^sha512-/u);
    expect(PHP_ENGINE_LOCK.sourceAssets.wasm).toHaveLength(64);
    expect(PHP_ENGINE_LOCK.sourceAssets.nativeInventory).toHaveLength(64);
    expect(Object.keys(PHP_ENGINE_LOCK.legalAssets)).toHaveLength(42);
  });

  it("builds byte-identical closed packs from pinned npm inputs", async () => {
    const directory = await mkdtemp(path.join(tmpdir(), "regex-php-pack-"));
    temporaryDirectories.push(directory);
    const first = path.join(directory, "first");
    const second = path.join(directory, "second");

    await buildPhpPack({ output: first });
    await buildPhpPack({ output: second });

    for (const file of [
      "php.wasm",
      "php-loader.mjs",
      "engine-metadata.json",
      "native-components.json",
      "SHA256SUMS",
      "LICENSE.Emscripten-4.0.19.txt",
      "LICENSE.llvm-compiler-rt.txt",
      "LICENSE.PHP-4.0.txt",
      "LICENSE.PHP-Zend-2.0.txt",
      "LICENSE.PCRE2-10.44.txt",
      "LICENSE.php-wasm-GPL-2.0-or-later.txt",
      "NOTICE.PHP-REDIST-BINS.txt",
      "PATENTS.libaom-3.12.1.txt",
    ]) {
      expect(await digest(path.join(first, file))).toBe(
        await digest(path.join(second, file)),
      );
    }
  }, 30_000);

  it("anchors legal assets independently from the pack checksum manifest", async () => {
    const directory = await mkdtemp(path.join(tmpdir(), "regex-php-legal-"));
    temporaryDirectories.push(directory);
    const pack = path.join(directory, "pack");
    await cp(path.resolve("public", "engines", "php"), pack, {
      recursive: true,
      force: false,
      errorOnExist: true,
    });
    const legalName = "LICENSE.OpenSSL-1.1.1t.txt";
    const legalFile = path.join(pack, legalName);
    await appendFile(legalFile, "\n");
    const checksumsFile = path.join(pack, "SHA256SUMS");
    const checksums = await readFile(checksumsFile, "utf8");
    const updated = checksums.replace(
      new RegExp(`^[a-f0-9]{64}  ${legalName}$`, "mu"),
      `${await digest(legalFile)}  ${legalName}`,
    );
    expect(updated).not.toBe(checksums);
    await writeFile(checksumsFile, updated);

    await expect(verifyPhpPack(pack)).rejects.toThrow(
      `Packed PHP legal asset ${legalName} SHA-256 mismatch`,
    );
  });
});
