#!/usr/bin/env node

import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
import { pathToFileURL } from 'node:url';

import { readReleaseIdentity, sameReleaseIdentity } from './e2e-evidence.mjs';

export const REQUIRED_E2E_PHASES = Object.freeze([
  'campaign',
  'multiplayer',
  'replay',
  'mobile'
]);

function isNonEmptyObject(value) {
  return value !== null
    && typeof value === 'object'
    && !Array.isArray(value)
    && Object.keys(value).length > 0;
}

export function verifyPublicationEvidence(evidence, currentIdentity) {
  assert.ok(evidence && typeof evidence === 'object' && !Array.isArray(evidence),
    'full E2E evidence must be a JSON object');
  assert.equal(evidence.schema, 4, 'full E2E evidence must use schema 4');
  assert.ok(sameReleaseIdentity(evidence.releaseIdentity, currentIdentity),
    'top-level E2E release identity does not match the current artifacts');
  assert.equal(evidence.run?.status, 'passed', 'latest E2E run did not pass');
  assert.equal(evidence.run?.deployedReleaseVerified, true,
    'latest E2E run did not verify the deployed release');
  assert.equal(evidence.run?.releaseIdentitySha256, currentIdentity.identitySha256,
    'latest E2E run identity does not match the current artifacts');

  assert.ok(evidence.evidence && typeof evidence.evidence === 'object'
    && !Array.isArray(evidence.evidence), 'phase evidence map is missing');
  assert.ok(evidence.phaseChecks && typeof evidence.phaseChecks === 'object'
    && !Array.isArray(evidence.phaseChecks), 'phase check map is missing');

  for (const phase of REQUIRED_E2E_PHASES) {
    assert.ok(Object.hasOwn(evidence.evidence, phase),
      `required E2E phase evidence is missing: ${phase}`);
    assert.ok(isNonEmptyObject(evidence.evidence[phase]),
      `required E2E phase evidence is empty: ${phase}`);
    assert.ok(Object.hasOwn(evidence.phaseChecks, phase),
      `required E2E phase check is missing: ${phase}`);
    assert.ok(sameReleaseIdentity(evidence.phaseChecks[phase]?.releaseIdentity, currentIdentity),
      `E2E phase ${phase} does not match the current artifacts`);
  }

  return currentIdentity;
}

export async function verifyEvidenceFile({
  evidencePath = new URL('../.selfcheck/full-e2e.json', import.meta.url),
  currentIdentity
} = {}) {
  const [rawEvidence, releaseIdentity] = await Promise.all([
    fs.readFile(evidencePath, 'utf8'),
    currentIdentity ? Promise.resolve(currentIdentity) : readReleaseIdentity()
  ]);
  let evidence;
  try {
    evidence = JSON.parse(rawEvidence);
  } catch (error) {
    throw new Error(`full E2E evidence is not valid JSON: ${error.message}`);
  }
  return verifyPublicationEvidence(evidence, releaseIdentity);
}

async function main() {
  const identity = await verifyEvidenceFile();
  process.stdout.write(`E2E publication evidence verified for ${identity.identitySha256}\n`);
}

const invokedPath = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : null;
if (invokedPath === import.meta.url) {
  main().catch((error) => {
    process.stderr.write(`verify-e2e-evidence: ${error.message}\n`);
    process.exitCode = 1;
  });
}
