#!/usr/bin/env node

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { execFileSync } from "node:child_process";
import {
  copyFileSync,
  mkdirSync,
  mkdtempSync,
  readFileSync,
  rmSync,
  writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import {
  extractSourceInterface,
  assertIdlInterface,
} from "./interface-policy.mjs";
import {
  programByName,
  readManifest,
  supportedPrograms,
  verifyCanonicalManifest,
} from "./release-policy.mjs";
import { verifyCommittedCandidate } from "./verify-release-scope.mjs";

const root = resolve(import.meta.dirname, "..");
const repositoryRoot = resolve(root, "../..");
const relativePackage = "repos/vaultfire-solana";
const outputDirectory = join(root, "target", "idl");
const manifest = verifyCanonicalManifest(readManifest());
const programs = supportedPrograms(manifest);
const sourceCommit = verifyCommittedCandidate();
const temporaryRoot = mkdtempSync(join(tmpdir(), "vaultfire-solana-idl-"));
const archive = join(temporaryRoot, "candidate.tar");
const temporaryPackage = join(temporaryRoot, relativePackage);
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");

assert.equal(programs.length, 6, "expected exactly six supported candidates");

function supportedSourceHashes(packageRoot) {
  return Object.fromEntries(
    programs.flatMap((name) => {
      const program = programByName(manifest, name);
      return [program.sourcePath, program.cargoManifestPath].map((path) => [
        path,
        sha256(readFileSync(join(packageRoot, path))),
      ]);
    }),
  );
}

try {
  execFileSync(
    "git",
    [
      "archive",
      "--format=tar",
      "--output",
      archive,
      sourceCommit,
      relativePackage,
    ],
    { cwd: repositoryRoot, stdio: "inherit" },
  );
  execFileSync("tar", ["-xf", archive, "-C", temporaryRoot], {
    stdio: "inherit",
  });
  rmSync(archive, { force: true });

  const originalHashes = supportedSourceHashes(root);
  const temporaryHashes = supportedSourceHashes(temporaryPackage);
  assert.deepEqual(
    temporaryHashes,
    originalHashes,
    "archived supported source does not match the exact commit",
  );

  const temporaryAnchorToml = join(temporaryPackage, "Anchor.toml");
  const localnetMap = programs
    .map((name) => {
      const program = programByName(manifest, name);
      return `${name} = "${program.programId}"`;
    })
    .join("\n");
  writeFileSync(
    temporaryAnchorToml,
    `${readFileSync(temporaryAnchorToml, "utf8").trimEnd()}\n\n` +
      "# Temporary verifier-owned map. It exists only in this disposable copy.\n" +
      "[programs.localnet]\n" +
      `${localnetMap}\n`,
  );

  const temporaryOutput = join(temporaryPackage, "target", "idl");
  mkdirSync(temporaryOutput, { recursive: true });
  const verified = [];
  for (const name of programs) {
    const program = programByName(manifest, name);
    const output = join(temporaryOutput, `${name}.json`);
    execFileSync(
      "anchor",
      [
        "idl",
        "build",
        "--program-name",
        name,
        "--out",
        output,
        "--",
        "--locked",
        "--offline",
      ],
      {
        cwd: temporaryPackage,
        stdio: "inherit",
        env: {
          ...process.env,
          CARGO_TARGET_DIR: join(temporaryRoot, "cargo-target"),
        },
      },
    );
    const bytes = readFileSync(output);
    const idl = JSON.parse(bytes);
    assert.equal(
      idl.address,
      program.programId,
      `${name} IDL program ID mismatch`,
    );
    const source = readFileSync(join(root, program.sourcePath), "utf8");
    assertIdlInterface(idl, program, extractSourceInterface(source));
    verified.push({
      name,
      programId: program.programId,
      sha256: sha256(bytes),
    });
  }

  assert.deepEqual(
    supportedSourceHashes(temporaryPackage),
    temporaryHashes,
    "IDL generation changed supported source in the disposable copy",
  );
  verifyCommittedCandidate(sourceCommit);
  assert.deepEqual(
    supportedSourceHashes(root),
    originalHashes,
    "IDL generation changed canonical supported source",
  );

  rmSync(outputDirectory, { recursive: true, force: true });
  mkdirSync(outputDirectory, { recursive: true });
  for (const { name } of verified) {
    copyFileSync(
      join(temporaryOutput, `${name}.json`),
      join(outputDirectory, `${name}.json`),
    );
  }
  verifyCommittedCandidate(sourceCommit);
  console.log(
    JSON.stringify(
      {
        sourceCommit,
        isolatedArchive: true,
        sourceMutationDetected: false,
        idls: verified,
      },
      null,
      2,
    ),
  );
} finally {
  rmSync(temporaryRoot, { recursive: true, force: true });
}
