import { createHash } from "node:crypto";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { afterEach, describe, expect, it } from "vitest";

import { compileStaticStickerCatalog } from "../../packages/asset-compiler/src/index.js";
import { getVirtualStickerWindow, type StaticStickerCatalogItem } from "../../packages/static-sticker-catalog/src/index.js";

const roots: string[] = [];

afterEach(() => {
  for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true });
});

function tinyPng(width: number, height: number, fill: number): Buffer {
  const bytes = Buffer.alloc(45, fill);
  Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]).copy(bytes, 0);
  bytes.writeUInt32BE(13, 8);
  Buffer.from("IHDR").copy(bytes, 12);
  bytes.writeUInt32BE(width, 16);
  bytes.writeUInt32BE(height, 20);
  bytes[24] = 8;
  bytes[25] = 6;
  bytes.writeUInt32BE(0, 33);
  Buffer.from("IEND").copy(bytes, 37);
  return bytes;
}

function filesBelow(root: string): string[] {
  return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
    const path = join(root, entry.name);
    return entry.isDirectory() ? filesBelow(path) : [path];
  });
}

function snapshot(root: string) {
  return filesBelow(root).sort().map((path) => ({
    bytes: statSync(path).size,
    mtime_ms: statSync(path).mtimeMs,
    path,
    sha256: createHash("sha256").update(readFileSync(path)).digest("hex"),
  }));
}

function createFixture() {
  const root = mkdtempSync(join(tmpdir(), "dada-wp5-02-"));
  const sourceRoot = join(root, "source");
  const output = mkdtempSync(join(tmpdir(), "dada-wp5-02-output-"));
  roots.push(root, output);
  for (let part = 1; part <= 25; part += 1) {
    const partRoot = join(sourceRoot, `sticker_part${part}`);
    mkdirSync(partRoot, { recursive: true });
    const count = part === 1 ? 4 : 2;
    for (let order = 1; order <= count; order += 1) {
      const bytes = part === 1 && order > 1 ? tinyPng(8, 8, 7) : tinyPng(part + order, part, part + order);
      writeFileSync(join(partRoot, `${String(order).padStart(4, "0")}.png`), bytes);
    }
  }
  return { output, root: sourceRoot };
}

describe("TDD-WP5-CAT-001 ordinary sticker catalog", () => {
  it("compiles every part in stable order, preserving duplicate source files and read-only evidence", () => {
    const fixture = createFixture();
    const before = snapshot(fixture.root);
    const result = compileStaticStickerCatalog({ outputDirectory: fixture.output, releaseVersion: "fixture-v1", sourceRoot: fixture.root });
    const after = snapshot(fixture.root);
    const catalog = JSON.parse(readFileSync(join(fixture.output, "static-sticker-catalog.json"), "utf8")) as { items: StaticStickerCatalogItem[]; count: number };

    expect(after).toEqual(before);
    expect(result.report).toMatchObject({ copied_source_files: 0, source_mutations: 0, source_files_read: 52 });
    expect(catalog.count).toBe(52);
    expect(catalog.items).toHaveLength(52);
    expect(catalog.items.map((item) => item.stable_id)).toEqual(catalog.items.map((_, index) => `STK${String(index + 1).padStart(3, "0")}`));
    expect(catalog.items.slice(0, 4).map((item) => [item.part, item.order])).toEqual([[1, 1], [1, 2], [1, 3], [1, 4]]);
    expect(new Set(catalog.items.filter((item) => item.sha256 === catalog.items[1]?.sha256).map((item) => item.stable_id)).size).toBe(3);
    expect(filesBelow(fixture.output).every((path) => path.endsWith(".json"))).toBe(true);
    expect(filesBelow(fixture.output).some((path) => readFileSync(path, "utf8").includes(fixture.root))).toBe(false);
  });

  it("keeps the virtual DOM bounded to the viewport plus two screen buffers", () => {
    const items = Array.from({ length: 1_407 }, (_, index) => ({ stable_id: `STK${index + 1}`, order: index + 1 } as StaticStickerCatalogItem));
    const window = getVirtualStickerWindow(items, { itemHeight: 112, viewportHeight: 448, columns: 4, overscanScreens: 2, scrollTop: 0 });
    expect(window.items.length).toBeLessThanOrEqual(48);
    expect(window.start).toBe(0);
    expect(window.end).toBe(window.start + window.items.length);
    const lower = getVirtualStickerWindow(items, { itemHeight: 112, viewportHeight: 448, columns: 4, overscanScreens: 2, scrollTop: 112 * 350 });
    expect(lower.items.length).toBeLessThanOrEqual(48);
    expect(lower.items[0]?.stable_id).toBe(items[lower.start]?.stable_id);
  });

  it("rejects non-PNG source files before producing a catalog", () => {
    const fixture = createFixture();
    writeFileSync(join(fixture.root, "sticker_part1", "bad.png"), Buffer.from("not png"));
    expect(() => compileStaticStickerCatalog({ outputDirectory: fixture.output, releaseVersion: "fixture-v1", sourceRoot: fixture.root })).toThrow(/PNG/i);
    expect(existsSync(join(fixture.output, "static-sticker-catalog.json"))).toBe(false);
  });
});
