#!/usr/bin/env node

import assert from "node:assert/strict";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import {
  readManifest,
  programByName,
  supportedPrograms,
  verifyCanonicalManifest,
} from "./release-policy.mjs";

const root = resolve(import.meta.dirname, "..");
const endpoint = process.env.LOCAL_RPC_URL ?? "http://127.0.0.1:8899";
const url = new URL(endpoint);
assert.ok(
  ["127.0.0.1", "localhost"].includes(url.hostname),
  `refusing non-local RPC endpoint ${endpoint}`,
);
assert.equal(url.protocol, "http:", "local rehearsal intentionally uses plain loopback HTTP");

const dispositionManifest = verifyCanonicalManifest(readManifest());
const programs = supportedPrograms(dispositionManifest)
  .map((name) => ({ name, id: programByName(dispositionManifest, name).programId }))
  .sort((a, b) => a.name.localeCompare(b.name));

async function rpc(method, params) {
  const response = await fetch(endpoint, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
  });
  assert.equal(response.ok, true, `${method} HTTP failure`);
  const body = await response.json();
  assert.equal(body.error, undefined, `${method}: ${JSON.stringify(body.error)}`);
  return body.result;
}

const UPGRADEABLE_LOADER = "BPFLoaderUpgradeab1e11111111111111111111111";
const DISABLED_AUTHORITY_SENTINEL = "11111111111111111111111111111111";
const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

function encodeBase58(bytes) {
  let value = 0n;
  for (const byte of bytes) value = value * 256n + BigInt(byte);
  let encoded = "";
  while (value > 0n) {
    encoded = BASE58_ALPHABET[Number(value % 58n)] + encoded;
    value /= 58n;
  }
  for (const byte of bytes) {
    if (byte !== 0) break;
    encoded = `1${encoded}`;
  }
  return encoded || "1";
}

function parseProgramAccount(bytes, name) {
  assert.equal(bytes.length, 36, `${name} Program account has an unexpected length`);
  assert.equal(bytes.readUInt32LE(0), 2, `${name} is not an upgradeable-loader Program state`);
  return encodeBase58(bytes.subarray(4, 36));
}

function parseDisabledProgramData(bytes, name) {
  assert.ok(bytes.length > 49, `${name} ProgramData account is truncated`);
  assert.equal(bytes.readUInt32LE(0), 3, `${name} is not a ProgramData state`);
  assert.equal(
    bytes[12],
    1,
    `${name} must use Agave's explicit disabled-authority sentinel encoding`,
  );
  const encodedAuthority = encodeBase58(bytes.subarray(13, 45));
  assert.equal(
    encodedAuthority,
    DISABLED_AUTHORITY_SENTINEL,
    `${name} has a usable or unexpected upgrade authority`,
  );
  assert.equal(bytes[45], 0x7f, `${name} ProgramData must start with ELF magic`);
  assert.equal(bytes.subarray(46, 49).toString("ascii"), "ELF", `${name} ProgramData is not ELF`);
  return {
    encodedAuthority,
    slot: bytes.readBigUInt64LE(4).toString(),
  };
}

const genesisHash = await rpc("getGenesisHash", []);
assert.ok(typeof genesisHash === "string" && genesisHash.length > 0);
const evidence = {
  schemaVersion: 1,
  purpose: "ephemeral-local-validator-rehearsal-only",
  evidenceScope: "ephemeral-local-executable-load-only",
  v3DispositionEvidence: "source-only",
  candidateProgramsOnly: true,
  deployableRelease: false,
  deploymentAuthorized: false,
  rpcEndpoint: endpoint,
  externalRpcRequests: 0,
  generatedSignerFiles: 0,
  fundedExternalWallets: 0,
  authorityModel: "agave-test-validator-disabled-upgrade-sentinel",
  upgradeAuthority: null,
  encodedUpgradeAuthoritySentinel: DISABLED_AUTHORITY_SENTINEL,
  genesisHash,
  programs: [],
};

for (const program of programs) {
  const result = await rpc("getAccountInfo", [
    program.id,
    { encoding: "base64", commitment: "confirmed" },
  ]);
  assert.ok(result.value, `${program.name} is absent from the local validator`);
  assert.equal(result.value.executable, true, `${program.name} is not executable`);
  assert.equal(result.value.owner, UPGRADEABLE_LOADER, `${program.name} has an unexpected loader`);
  const programState = Buffer.from(result.value.data[0], "base64");
  const programDataAddress = parseProgramAccount(programState, program.name);
  const programDataResult = await rpc("getAccountInfo", [
    programDataAddress,
    { encoding: "base64", commitment: "confirmed" },
  ]);
  assert.ok(programDataResult.value, `${program.name} ProgramData account is absent`);
  assert.equal(
    programDataResult.value.owner,
    UPGRADEABLE_LOADER,
    `${program.name} ProgramData has an unexpected loader`,
  );
  assert.equal(
    programDataResult.value.executable,
    false,
    `${program.name} ProgramData must not be executable`,
  );
  const programData = parseDisabledProgramData(
    Buffer.from(programDataResult.value.data[0], "base64"),
    program.name,
  );
  evidence.programs.push({
    name: program.name,
    programId: program.id,
    executable: result.value.executable,
    owner: result.value.owner,
    programDataAddress,
    programDataOwner: programDataResult.value.owner,
    programDataSlot: programData.slot,
    upgradeAuthority: null,
    encodedUpgradeAuthoritySentinel: programData.encodedAuthority,
  });
  console.log(
    `${program.name}: executable, disabled upgrade authority, ProgramData=${programDataAddress}`,
  );
}

const evidenceDir = resolve(root, "artifacts", "rehearsal");
mkdirSync(evidenceDir, { recursive: true });
writeFileSync(
  resolve(evidenceDir, "local-validator-evidence.json"),
  `${JSON.stringify(evidence, null, 2)}\n`,
);
console.log(`genesis=${genesisHash}; endpoint=${endpoint}; external RPCs=0`);
