import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';

import {
  REQUIRED_E2E_PHASES,
  verifyEvidenceFile,
  verifyPublicationEvidence
} from '../tools/verify-e2e-evidence.mjs';
import { readReleaseIdentity } from '../tools/e2e-evidence.mjs';

function passedEvidence(identity) {
  return {
    schema: 4,
    releaseIdentity: identity,
    run: {
      status: 'passed',
      deployedReleaseVerified: true,
      releaseIdentitySha256: identity.identitySha256,
      requestedPhases: ['mobile'],
      completedPhases: ['mobile'],
      skippedPhases: ['campaign', 'multiplayer', 'replay']
    },
    evidence: Object.fromEntries(REQUIRED_E2E_PHASES.map((phase) => [phase, { phase }])),
    phaseChecks: Object.fromEntries(REQUIRED_E2E_PHASES.map((phase) => [phase, {
      checkedAt: '2026-07-15T00:00:00.000Z',
      releaseIdentity: identity
    }]))
  };
}

test('publication accepts retained phase evidence bound to the exact current release', async () => {
  const identity = await readReleaseIdentity();
  const evidence = passedEvidence(identity);
  assert.equal(verifyPublicationEvidence(evidence, identity), identity);
});

test('publication rejects incomplete, failed, unverified, or stale evidence', async (t) => {
  const identity = await readReleaseIdentity();
  const staleIdentity = structuredClone(identity);
  staleIdentity.identitySha256 = '0'.repeat(64);

  const fixtures = [
    ['old schema', (value) => { value.schema = 3; }, /schema 4/],
    ['stale top-level identity', (value) => { value.releaseIdentity = staleIdentity; }, /top-level/],
    ['failed latest run', (value) => { value.run.status = 'failed'; }, /did not pass/],
    ['unverified deployment', (value) => { value.run.deployedReleaseVerified = false; }, /deployed release/],
    ['stale latest run identity', (value) => { value.run.releaseIdentitySha256 = '0'.repeat(64); }, /latest E2E run identity/],
    ['missing campaign evidence', (value) => { delete value.evidence.campaign; }, /evidence is missing: campaign/],
    ['empty replay evidence', (value) => { value.evidence.replay = {}; }, /evidence is empty: replay/],
    ['missing mobile check', (value) => { delete value.phaseChecks.mobile; }, /phase check is missing: mobile/],
    ['stale multiplayer check', (value) => {
      value.phaseChecks.multiplayer.releaseIdentity = staleIdentity;
    }, /phase multiplayer does not match/]
  ];

  for (const [name, mutate, expected] of fixtures) {
    await t.test(name, () => {
      const evidence = passedEvidence(identity);
      mutate(evidence);
      assert.throws(() => verifyPublicationEvidence(evidence, identity), expected);
    });
  }
});

test('file verifier reads schema-4 JSON and rejects malformed evidence', async (t) => {
  const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'q4-e2e-evidence-'));
  t.after(() => fs.rm(directory, { recursive: true, force: true }));
  const identity = await readReleaseIdentity();
  const evidencePath = path.join(directory, 'full-e2e.json');

  await fs.writeFile(evidencePath, `${JSON.stringify(passedEvidence(identity))}\n`);
  assert.equal(await verifyEvidenceFile({ evidencePath, currentIdentity: identity }), identity);

  await fs.writeFile(evidencePath, '{not-json\n');
  await assert.rejects(
    verifyEvidenceFile({ evidencePath, currentIdentity: identity }),
    /not valid JSON/
  );
});
