const fs = require("node:fs");
const path = require("node:path");
const { spawnSync, execFileSync } = require("node:child_process");

function parseArgs(argv) {
  const out = { fixtures: "/fixtures", outDir: "/tmp/pdf-fixtures-out" };
  for (let i = 2; i < argv.length; i++) {
    const a = argv[i];
    if (a === "--fixtures") out.fixtures = argv[++i];
    else if (a === "--out") out.outDir = argv[++i];
  }
  return out;
}

function readManifestSHA() {
  const p = "/opt/forgejo-pdf/manifest.json";
  const b = fs.readFileSync(p, "utf8");
  const j = JSON.parse(b);
  if (!j.manifest_sha || typeof j.manifest_sha !== "string") {
    throw new Error("manifest.json missing manifest_sha");
  }
  return j.manifest_sha;
}

function extractKnownUniqueString(md) {
  const m = md.match(/KNOWN_UNIQUE_STRING:\s*([A-Za-z0-9_\-\.]+)/);
  return m ? m[1] : null;
}

function containsMermaid(md) {
  return /```mermaid[\s\S]*?```/m.test(md);
}

function run(cmd, args, opts = {}) {
  const res = spawnSync(cmd, args, { encoding: "utf8", ...opts });
  return res;
}

function main() {
  const { fixtures, outDir } = parseArgs(process.argv);
  fs.mkdirSync(outDir, { recursive: true });

  const manifestSHA = readManifestSHA();

  const pdfConfig = {
    pdf: {
      determinism: "strict",
      timestamp: "commit_time",
      typography: "professional",
      mermaid: { strategy: "balanced", caption: false },
      orphansWidows: { enforce: true },
      footer: { enabled: true }
    }
  };

  const files = fs
    .readdirSync(fixtures)
    .filter((f) => f.endsWith(".md"))
    .sort();

  if (files.length === 0) {
    throw new Error("no fixture markdown files found");
  }

  for (const f of files) {
    const mdPath = path.join(fixtures, f);
    const md = fs.readFileSync(mdPath, "utf8");
    const expected = extractKnownUniqueString(md);
    if (!expected) throw new Error(`fixture missing KNOWN_UNIQUE_STRING: ${f}`);

    const input = {
      markdown: md,
      repoMeta: {
        owner: "fixture",
        repo: "forgejo-pdf",
        path: f,
        repoID: 1,
        commitSHA: "0123456789abcdef0123456789abcdef01234567",
        commitTimeRFC3339: "2020-01-02T03:04:05Z"
      },
      config: pdfConfig,
      manifestSHA
    };

    const jobDir = fs.mkdtempSync(path.join(outDir, "job-"));
    const inPath = path.join(jobDir, "input.json");
    const outPath = path.join(jobDir, "output.pdf");
    fs.writeFileSync(inPath, JSON.stringify(input), "utf8");

    const res = run("node", ["src/index.js", "--in", inPath, "--out", outPath], {
      cwd: "/opt/forgejo-pdf"
    });
    if (res.status !== 0) {
      throw new Error(`worker failed for ${f}: ${res.stderr.trim() || res.stdout.trim()}`);
    }

    const logs = res.stderr
      .split("\n")
      .map((l) => l.trim())
      .filter(Boolean)
      .map((l) => {
        try {
          return JSON.parse(l);
        } catch {
          return null;
        }
      })
      .filter(Boolean);

    const done = logs.findLast ? logs.findLast((l) => l.event === "done") : logs.reverse().find((l) => l.event === "done");
    if (!done) throw new Error(`missing done log for ${f}`);
    if (done.blocked_requests !== 0) throw new Error(`blocked_requests != 0 for ${f}`);

    const hasMermaid = containsMermaid(md);
    if (hasMermaid && (!Number.isFinite(done.mermaid_count) || done.mermaid_count < 1)) {
      throw new Error(`expected mermaid_count >= 1 for ${f}`);
    }
    if (!hasMermaid && done.mermaid_count !== 0) {
      throw new Error(`expected mermaid_count == 0 for ${f}`);
    }

    execFileSync("qpdf", ["--check", outPath], { stdio: "inherit" });

    const text = execFileSync("pdftotext", [outPath, "-"], { encoding: "utf8" });
    const normalized = text.replace(/\s+/g, "");
    const expectedNorm = expected.replace(/\s+/g, "");
    if (!normalized.includes(expectedNorm)) {
      throw new Error(`pdftotext missing expected marker for ${f}: ${expected}`);
    }
  }
}

main();
