import test from "node:test";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";

const script = fileURLToPath(new URL("./generate-third-party-notices.mjs", import.meta.url));
const repository = fileURLToPath(new URL("../", import.meta.url));

test("third-party notice sections use a literal separator without corrupting package names", async (t) => {
  const directory = await mkdtemp(join(tmpdir(), "crm-notices-test-"));
  t.after(() => rm(directory, { recursive: true, force: true }));
  const modules = join(directory, "node_modules");
  const output = join(directory, "notices.txt");
  for (const [name, version, license] of [["alpha", "1.0.0", "MIT"], ["beta", "2.0.0", "BSD-2-Clause"]]) {
    const packageDirectory = join(modules, name);
    await mkdir(packageDirectory, { recursive: true });
    await writeFile(join(packageDirectory, "package.json"), JSON.stringify({ name, version, license }));
    await writeFile(join(packageDirectory, "LICENSE"), `${name} license`);
  }
  const nestedPackage = join(modules, "alpha", "node_modules", "@scope", "gamma");
  await mkdir(nestedPackage, { recursive: true });
  await writeFile(join(nestedPackage, "package.json"), JSON.stringify({
    name: "@scope/gamma",
    version: "3.0.0",
    license: "Apache-2.0"
  }));
  await writeFile(join(nestedPackage, "LICENSE"), "gamma license");

  const child = spawn(process.execPath, [script, modules, output], {
    windowsHide: true,
    stdio: "ignore"
  });
  const code = await new Promise((resolve, reject) => {
    child.once("error", reject);
    child.once("exit", resolve);
  });
  assert.equal(code, 0);

  const notices = await readFile(output, "utf8");
  assert.match(notices, /alpha 1\.0\.0/);
  assert.match(notices, /beta 2\.0\.0/);
  assert.equal((notices.match(/@scope\/gamma 3\.0\.0/g) ?? []).length, 1);
  assert.match(notices, new RegExp(`\\n${"-".repeat(72)}\\n`));
  assert.doesNotMatch(notices, /NaN/);
});

test("manifest-scoped notices include reachable runtime dependencies and exclude unrelated packages", async (t) => {
  const directory = await mkdtemp(join(tmpdir(), "crm-scoped-notices-test-"));
  t.after(() => rm(directory, { recursive: true, force: true }));
  const modules = join(directory, "installed", "node_modules");
  const manifest = join(directory, "source", "package.json");
  const output = join(directory, "notices.txt");
  await mkdir(join(directory, "source"), { recursive: true });
  await writeFile(manifest, JSON.stringify({ dependencies: { alpha: "1.0.0" } }));
  for (const [name, dependencies = {}] of [["alpha", { beta: "2.0.0" }], ["beta"], ["unrelated"]]) {
    const packageDirectory = join(modules, name);
    await mkdir(packageDirectory, { recursive: true });
    await writeFile(join(packageDirectory, "package.json"), JSON.stringify({
      name,
      version: name === "beta" ? "2.0.0" : "1.0.0",
      license: "MIT",
      dependencies: name === "beta" ? { alpha: "1.0.0" } : dependencies,
      optionalDependencies: name === "alpha" ? { unavailable: "1.0.0" } : {},
    }));
    await writeFile(join(packageDirectory, "LICENSE"), `${name} license`);
  }

  const child = spawn(process.execPath, [
    script,
    join("installed", "node_modules"),
    "notices.txt",
    "--manifest",
    join("source", "package.json"),
  ], {
    cwd: directory,
    windowsHide: true,
    stdio: "ignore",
    timeout: 2000,
  });
  const code = await new Promise((resolve, reject) => {
    child.once("error", reject);
    child.once("exit", resolve);
  });
  assert.equal(code, 0);

  const notices = await readFile(output, "utf8");
  assert.match(notices, /alpha 1\.0\.0/);
  assert.match(notices, /beta 2\.0\.0/);
  assert.doesNotMatch(notices, /unrelated/);
});

test("committed PWA notices match the installed runtime dependency graph", async (t) => {
  const directory = await mkdtemp(join(tmpdir(), "crm-pwa-notices-test-"));
  t.after(() => rm(directory, { recursive: true, force: true }));
  const output = join(directory, "PWA-THIRD-PARTY-NOTICES.txt");
  const child = spawn(process.execPath, [
    script,
    join(repository, "node_modules"),
    output,
    "--manifest",
    join(repository, "apps", "pwa", "package.json"),
  ], {
    windowsHide: true,
    stdio: "ignore",
  });
  const code = await new Promise((resolve, reject) => {
    child.once("error", reject);
    child.once("exit", resolve);
  });
  assert.equal(code, 0);

  const [generated, committed] = await Promise.all([
    readFile(output, "utf8"),
    readFile(join(repository, "apps", "pwa", "public", "legal", "PWA-THIRD-PARTY-NOTICES.txt"), "utf8"),
  ]);
  assert.equal(committed, generated);
});
