/**
 * Jest mirror of tests/v3-launch-console.test.mjs for the /sign/v3-launch console.
 *
 * The .mjs file is the dependency-free source of truth (`node --test`); this file
 * re-asserts the same safety invariants inside the Jest suite that CI runs. It is
 * intentionally CommonJS and avoids importing the ESM data generator, so it needs
 * no ESM/Jest interop: evidence drift is checked here by re-hashing the recorded
 * source files instead.
 */
const { createHash } = require('crypto');
const fs = require('fs');
const path = require('path');

const guards = require('../app/sign/v3-launch/launchGuards.js');

const siteRoot = path.resolve(__dirname, '..');
const releaseRoot = path.resolve(siteRoot, '..', '..');
const read = (relative) => fs.readFileSync(path.join(siteRoot, relative), 'utf8');

const consoleData = JSON.parse(read('app/sign/v3-launch/data/v3-launch-console-data.json'));
const pageSource = read('app/sign/v3-launch/V3LaunchConsole.tsx');
const routeSource = read('app/sign/v3-launch/page.tsx');
const walletContextSource = read('app/lib/WalletAuthContext.tsx');
const pageCode = pageSource.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');

describe('V3 launch console — wallet allowlisting', () => {
  test('accepts only the evidenced deployer, case-insensitively', () => {
    expect(guards.isAuthorizedDeployer(guards.AUTHORIZED_BASE_DEPLOYER)).toBe(true);
    expect(guards.isAuthorizedDeployer(guards.AUTHORIZED_BASE_DEPLOYER.toLowerCase())).toBe(true);
    expect(guards.AUTHORIZED_BASE_DEPLOYER).toBe('0xfA15Ee28939B222B0448261A22156070f0A7813C');
    expect(guards.AUTHORIZED_BASE_DEPLOYER_LABEL).toBe('ghostkey.base.eth');
  });

  test('fails closed for every other value', () => {
    for (const value of [null, undefined, '', '   ', 'ghostkey.base.eth', '0x0000000000000000000000000000000000000000']) {
      expect(guards.isAuthorizedDeployer(value)).toBe(false);
    }
  });

  test('never authorizes the compromised or audit-only burner address', () => {
    for (const address of ['0xA054f831B562e729F8D268291EBde1B2EDcFb84F', '0x5a03C962227Dd478B5CD2B9f09E81105096154ad']) {
      expect(guards.isAuthorizedDeployer(address)).toBe(false);
      expect(guards.isRejectedForV3Authority(address)).toBe(true);
    }
  });

  test('Base App / Coinbase Wallet is the canonical surface', () => {
    expect(guards.CANONICAL_WALLETS).toEqual(['base', 'coinbase']);
    expect(guards.NON_CANONICAL_WALLETS).toEqual(['metamask', 'walletconnect']);
  });
});

describe('V3 launch console — chain gating', () => {
  test('requires Base 8453', () => {
    expect(guards.BASE_CHAIN_ID).toBe(8453);
    expect(guards.BASE_CHAIN_ID_HEX).toBe('0x2105');
    expect(guards.isBaseChain(8453)).toBe(true);
    expect(guards.isBaseChain('0x2105')).toBe(true);
    for (const bad of [1, 84532, null, undefined, 'base', '8453garbage', '0x2105garbage']) {
      expect(guards.isBaseChain(bad)).toBe(false);
    }
  });

  test('wrong chain keeps the operator view locked with a stated reason', () => {
    const access = guards.evaluateOperatorAccess({ address: guards.AUTHORIZED_BASE_DEPLOYER, chainId: 84532 });
    expect(access.unlocked).toBe(false);
    expect(access.blockers.join(' ')).toMatch(/8453/);
  });
});

describe('V3 launch console — fail-closed signing', () => {
  test('no transaction package and no approval artifact means no signing', () => {
    const signing = guards.evaluateSigningAvailability({
      access: { unlocked: true },
      transactionPackagePresent: consoleData.approvals.transactionPackagePresent,
      approvalArtifactPresent: consoleData.approvals.approvalArtifactPresent,
      approvalPhraseRequired: consoleData.approvals.baseReadOnlyPhrase,
    });
    expect(consoleData.approvals.transactionPackagePresent).toBe(false);
    expect(consoleData.approvals.approvalArtifactPresent).toBe(false);
    expect(signing.canSign).toBe(false);
    expect(signing.controlsRendered).toBe(false);
    expect(signing.missing.length).toBeGreaterThanOrEqual(2);
    expect(signing.missing.join(' ')).toMatch(/verified immutable transaction package/);
    expect(signing.missing.join(' ')).toMatch(/approval artifact/);
  });

  test('the page contains no signing, sending, broadcast, payment, or activation call', () => {
    for (const forbidden of [
      'eth_sendTransaction',
      'personal_sign',
      'signTypedData',
      'wallet_sendCalls',
      'sendTransaction',
      'createPaymentHeader',
      'deployContract',
      '/api/x402',
      'fetch(',
    ]) {
      expect(pageCode).not.toContain(forbidden);
    }
  });

  test('the page has no private-key, seed-phrase, or keystore field', () => {
    for (const forbidden of ['type="password"', 'privateKey', 'seedPhrase', 'unlock(']) {
      expect(pageCode).not.toContain(forbidden);
    }
    expect((pageCode.match(/<input/g) || []).length).toBe(1);
    expect([...pageCode.matchAll(/name="([^"]+)"/g)].map((m) => m[1])).toEqual(['publicAddressFormatCheck']);
    expect(guards.screenPublicRoleInput(`0x${'a'.repeat(64)}`).safe).toBe(false);
    expect(
      guards.screenPublicRoleInput('legal winner thank year wave sausage worth useful legal winner thank yellow').safe,
    ).toBe(false);
  });
});

describe('V3 launch console — truthful state labels', () => {
  test('four tracks, none deployed or activated', () => {
    expect(consoleData.tracks.map((t) => t.id)).toEqual([
      'base-read-only-bootstrap',
      'base-capped-partner-pilot',
      'solana-v3',
      'x402-v3',
    ]);
    for (const track of consoleData.tracks) {
      expect(track.deployedAddresses).toBeNull();
      expect(track.deploymentTransactions).toBeNull();
      expect(['prepared', 'blocked']).toContain(guards.resolveTrackState(track));
    }
  });

  test('no deployed address or transaction hash is claimed anywhere', () => {
    for (const value of Object.values(consoleData.verifiedDeployments.baseV3Addresses)) {
      expect(value).toBeNull();
    }
    expect(consoleData.verifiedDeployments.transactionHashes).toEqual([]);
    expect(pageSource).toContain('Deployed addresses recorded: none');
    expect(routeSource).toContain('robots: { index: false, follow: false }');
  });

  test('x402 V3 stays opt-in with V2 default and Solana stays blocked', () => {
    const byId = Object.fromEntries(consoleData.tracks.map((t) => [t.id, t]));
    expect(byId['x402-v3'].flags.v2Default).toBe(true);
    expect(byId['x402-v3'].flags.routeActivationOffered).toBe(false);
    expect(byId['x402-v3'].flags.paymentOffered).toBe(false);
    expect(byId['x402-v3'].flags.bazaarMutationOffered).toBe(false);
    expect(byId['solana-v3'].flags.deployed).toBe(false);
    expect(byId['solana-v3'].flags.signingOffered).toBe(false);
  });

  test('role inputs are requested with no invented values', () => {
    expect(consoleData.roleInputs.requiredCounts.governanceSignerAddresses).toBe(5);
    expect(consoleData.roleInputs.requiredCounts.guardianAddresses).toBe(1);
    expect(consoleData.roleInputs.requiredCounts.treasuryAddresses).toBe(1);
    expect(consoleData.roleInputs.requiredCounts.signerCustodyDisclosures).toBe(5);
    for (const signer of consoleData.roleInputs.emptyTemplate.governance.signers) {
      expect(signer.address).toBeNull();
    }
  });
});

describe('V3 launch console — provenance and provider retention', () => {
  test('recorded evidence hashes still match the source files', () => {
    for (const source of consoleData.provenance.sources) {
      const absolute = path.join(releaseRoot, source.path);
      if (!fs.existsSync(absolute)) continue; // sources live outside the published site bundle
      const buffer = fs.readFileSync(absolute);
      expect(createHash('sha256').update(buffer).digest('hex')).toBe(source.sha256);
      expect(buffer.length).toBe(source.bytes);
    }
    expect(consoleData.provenance.independentHumanAuditComplete).toBe(false);
  });

  test('current-status corrections retain historical provenance and do not unlock x402', () => {
    const byId = Object.fromEntries(consoleData.tracks.map((track) => [track.id, track]));
    const baseBlockers = Object.fromEntries(byId['base-read-only-bootstrap'].blockers.map((blocker) => [blocker.id, blocker]));
    const x402Blockers = Object.fromEntries(byId['x402-v3'].blockers.map((blocker) => [blocker.id, blocker]));

    expect(baseBlockers['V2-ABI-001']).toBeUndefined();
    expect(x402Blockers['X402-VALID-AFTER']).toBeUndefined();
    expect(
      byId['x402-v3'].facts.find((fact) => fact.label === 'Signed validAfter bound and tested')?.value,
    ).toBe('true');
    expect(
      byId['x402-v3'].missingArtifacts.some((artifact) => artifact.includes('validAfter')),
    ).toBe(false);
    expect(x402Blockers['X402-BASE-SETTLEMENT'].provenance.basis).toBe('historical-manifest-not-corrected');
    expect(consoleData.provenance.currentStatusCorrections.claimsNotMade).toEqual(
      expect.arrayContaining(['audit completion', 'deployment readiness', 'pilot readiness', 'payment readiness']),
    );
  });

  test('wallet context retains the connected provider and never falls back to window.ethereum', () => {
    expect(walletContextSource).toContain("source = 'coinbase'");
    expect(walletContextSource).toContain("source = 'walletconnect'");
    expect(walletContextSource).toContain('retainProvider(wp as Eip1193Provider, source)');
    const switchBlock = walletContextSource.slice(
      walletContextSource.indexOf('const requestChainSwitch'),
      walletContextSource.indexOf('const unlock'),
    );
    expect(switchBlock).toContain('providerRef.current');
    expect(switchBlock).not.toContain('window.ethereum');
    expect(switchBlock).toContain('sessionEpochRef.current !== sessionEpoch');
    expect(switchBlock).toContain('Wallet did not switch to chain');
    expect(walletContextSource).toContain('const unsubscribeProvider');
    expect(walletContextSource).toContain('if (providerRef.current !== wp) return;');
    const unlockBlock = walletContextSource.slice(
      walletContextSource.lastIndexOf('const unlock'),
      walletContextSource.indexOf('return (', walletContextSource.lastIndexOf('const unlock')),
    );
    expect(unlockBlock).toContain('unsubscribeProvider();');
    expect(unlockBlock).toContain('retainProvider(null, null);');
    expect(unlockBlock).toContain('setChainId(null)');
    expect(pageCode).not.toContain('ethereum');
  });
});
