import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";

import {
  createRuntimeAssetManifest,
  deployRuntimeAssetPlan,
  readRuntimeAssetManifest,
  serializeRuntimeAssetManifest,
} from "../../scripts/lib/runtime-assets.mjs";

test("committed P0-A runtime manifest covers the frozen first-version binary assets", () => {
  const manifest = readRuntimeAssetManifest("config/runtime-assets-manifest.json");
  assert.deepEqual(manifest.counts, {
    dynamic_fonts: 7,
    dynamic_images: 8,
    font_panel_items: 11,
    static_stickers: 1407,
  });
  assert.equal(manifest.entries.length, 1433);
  assert.doesNotMatch(serializeRuntimeAssetManifest(manifest), /[A-Za-z]:[\\/]/);
});

test("runtime asset deployment creates verified hardlinks and a path-free manifest", async (t) => {
  const root = await mkdtemp(join(tmpdir(), "dada-runtime-assets-"));
  t.after(() => rm(root, { force: true, recursive: true }));
  const sourceRoot = join(root, "source");
  const assetRoot = join(root, "assets");
  const sourcePath = join(sourceRoot, "sticker.png");
  const bytes = Buffer.from("runtime asset fixture");
  await mkdir(sourceRoot);
  await writeFile(sourcePath, bytes);
  const entry = {
    assetId: "STK001",
    mimeType: "image/png",
    relativePath: "p0a-static-v1/STK001.png",
    resourceVersion: "p0a-static-v1",
    rootRef: "p0a_runtime_assets",
    sha256: createHash("sha256").update(bytes).digest("hex"),
  };
  const manifest = createRuntimeAssetManifest({
    counts: { dynamic_fonts: 0, dynamic_images: 0, font_panel_items: 0, static_stickers: 1 },
    entries: [entry],
  });

  await deployRuntimeAssetPlan({ assetRoot, manifest, resources: [{ entry, sourcePath }] });

  const targetPath = join(assetRoot, entry.relativePath);
  const [sourceStat, targetStat] = await Promise.all([stat(sourcePath), stat(targetPath)]);
  assert.equal(sourceStat.ino, targetStat.ino);
  assert.deepEqual(await readFile(targetPath), bytes);
  const writtenManifest = await readFile(join(assetRoot, "manifest.json"), "utf8");
  assert.deepEqual(JSON.parse(writtenManifest), manifest);
  assert.doesNotMatch(writtenManifest, /[A-Za-z]:[\\/]/);
});

test("runtime asset deployment refuses a mismatched existing target", async (t) => {
  const root = await mkdtemp(join(tmpdir(), "dada-runtime-assets-conflict-"));
  t.after(() => rm(root, { force: true, recursive: true }));
  const sourcePath = join(root, "source.png");
  const assetRoot = join(root, "assets");
  const targetPath = join(assetRoot, "p0a-static-v1", "STK001.png");
  await mkdir(join(assetRoot, "p0a-static-v1"), { recursive: true });
  await writeFile(sourcePath, "expected");
  await writeFile(targetPath, "unexpected");
  const entry = {
    assetId: "STK001",
    mimeType: "image/png",
    relativePath: "p0a-static-v1/STK001.png",
    resourceVersion: "p0a-static-v1",
    rootRef: "p0a_runtime_assets",
    sha256: createHash("sha256").update("expected").digest("hex"),
  };
  const manifest = createRuntimeAssetManifest({
    counts: { dynamic_fonts: 0, dynamic_images: 0, font_panel_items: 0, static_stickers: 1 },
    entries: [entry],
  });

  assert.throws(
    () => deployRuntimeAssetPlan({ assetRoot, manifest, resources: [{ entry, sourcePath }] }),
    /asset_target_conflict/,
  );
});
