import { ethers } from 'ethers';
import { readFileSync } from 'fs';
import { join } from 'path';
import {
  BaseSepoliaV3Rehearsal, BaseSepoliaV3RehearsalManifest, BaseSepoliaV3ArtifactEvidenceAttestation,
  ObjectiveRuleClassV3, assertObjectiveRuleV3, canonicalManifestContentHash,
  parseCanonicalEvidenceJson, validateBaseSepoliaV3Manifest, validateBaseSepoliaV3ManifestJson,
} from '../src/baseSepoliaV3Rehearsal';
import { verifyTestOnlyBaseSepoliaV3Attestation } from './helpers/baseSepoliaV3AttestationTestSeam';

const address = (suffix: string) => `0x${suffix.padStart(40, '0')}`;
const hash = (byte: string) => `0x${byte.repeat(64)}`;
const prohibited = ['behavioral', 'flourishing', 'reputation', 'loyalty', 'quality', 'belief', 'biometric', 'profiling', 'surveillance'];
const excluded = ['rewards', 'x402', 'StreetCred', 'mandate', 'validation', 'ERC-8004', 'escrow', 'Solana', 'cross-chain'];

const manifest = (): BaseSepoliaV3RehearsalManifest => {
  const result = {
    schema: 'vaultfire.base-sepolia-v3-rehearsal-manifest.v1' as const, status: 'READY_FOR_REHEARSAL' as const,
    network: { name: 'Base Sepolia', chainId: 84532 as const, productionFundsProhibited: true as const, mainnetRejected: true as const, rpcPolicy: 'READ_ONLY_BASE_SEPOLIA_ONLY' as const },
    candidate: { sourceCommit: 'dc91aa9a8a04bb583206bb5629d2d560f1344c11', preRemediationContractsTree: null, currentContractsSourceSha256: '11'.repeat(32), sourceArchiveSha256: '22'.repeat(32), compiler: 'solc 0.8.24+commit.e11b9ed9' as const, foundry: '1.7.1', settings: { viaIR: true as const, optimizerRuns: 200 as const, evmVersion: 'paris' as const, bytecodeHash: 'none' as const }, artifactEvidenceBundleSha256: '33'.repeat(32) },
    objectiveRuleSchema: { version: 1 as const, allowedClasses: [] as [], prohibitedCategories: prohibited, opaquePolicyHashesRejected: true as const, mutualConsentSettlementOnly: true as const, disputesChangeCustody: false as const },
    contracts: {
      MultisigGovernanceV3: { address: address('1'), creationCodeHash: hash('1'), runtimeCodeHash: hash('2'), constructorArguments: '0x' },
      BaseSepoliaRehearsalAccessControllerV3: { address: address('2'), creationCodeHash: hash('3'), runtimeCodeHash: hash('4'), constructorArguments: '0x12' },
      ThresholdConsensusV3: { address: address('3'), creationCodeHash: hash('5'), runtimeCodeHash: hash('6'), constructorArguments: '0x' },
      DisputeRegistryV3: { address: address('4'), creationCodeHash: hash('7'), runtimeCodeHash: hash('8'), constructorArguments: '0x' },
      SafeBondFactoryV3: { address: address('5'), creationCodeHash: hash('9'), runtimeCodeHash: hash('a'), constructorArguments: '0x' },
      PartnershipBondFactoryV3: { address: address('6'), creationCodeHash: hash('b'), runtimeCodeHash: hash('c'), constructorArguments: '0x' },
      AccountabilityBondFactoryV3: { address: address('7'), creationCodeHash: hash('d'), runtimeCodeHash: hash('e'), constructorArguments: '0x' },
    },
    roles: { governanceSigners: [], guardian: null, designPartner: null, treasury: null },
    capsAtomic: { global: null, perPosition: null, partnershipPerParty: null, partnershipPerBond: null, accountabilityPerBond: null },
    orderedHumanAuthorizedCalls: [], enabledSurface: ['bonds-only', 'native-principal-only', 'mutual-consent-settlement-only'], excludedSurface: excluded, artifactHash: '',
  };
  result.artifactHash = canonicalManifestContentHash(result as BaseSepoliaV3RehearsalManifest);
  return result as BaseSepoliaV3RehearsalManifest;
};
const attestation = (value: BaseSepoliaV3RehearsalManifest): BaseSepoliaV3ArtifactEvidenceAttestation => ({
  schema: 'vaultfire.base-sepolia-v3-artifact-attestation.v2', validator: 'validate_v3_candidate_manifest.py',
  manifestArtifactHash: value.artifactHash, artifactEvidenceBundleSha256: value.candidate.artifactEvidenceBundleSha256 as string,
  validatorSourceSha256: '44'.repeat(32), validatorTranscriptSha256: '55'.repeat(32),
  deploymentVerificationStatementSha256: '66'.repeat(32), domain: 'vaultfire.base-sepolia-v3.ready-attestation.v2', chainId: 84532, keyId: 'test-only', algorithm: 'Ed25519', signature: '0x1234',
});

describe('Base Sepolia V3 rehearsal path', () => {
  it('uses the same canonical JSON hash as the Python blocked manifest validator', () => {
    const blocked = JSON.parse(readFileSync(join(__dirname, '../../../pr11/base-sepolia-rehearsal/base_sepolia_v3_candidate_manifest.json'), 'utf8')) as BaseSepoliaV3RehearsalManifest;
    expect(canonicalManifestContentHash(blocked)).toEqual(blocked.artifactHash);
  });

  it('accepts the exact Python-signed test-only v2 fixture and rejects every field tamper', () => {
    const fixturePath = join(__dirname, 'fixtures/base-sepolia-v3-attestation-v2.test.json');
    const publicKeyPath = join(__dirname, 'fixtures/base-sepolia-v3-attestation-v2.test-public-key.hex');
    const raw = readFileSync(fixturePath, 'utf8');
    const publicKey = readFileSync(publicKeyPath, 'utf8').trim();
    expect(verifyTestOnlyBaseSepoliaV3Attestation(raw, publicKey)).toBe(true);
    const parsed = JSON.parse(raw) as Record<string, unknown>;
    for (const field of Object.keys(parsed)) {
      const tampered = { ...parsed, [field]: field === 'chainId' ? 1 : 'tampered' };
      expect(verifyTestOnlyBaseSepoliaV3Attestation(JSON.stringify(tampered), publicKey)).toBe(false);
    }
    expect(verifyTestOnlyBaseSepoliaV3Attestation(raw, '00'.repeat(32))).toBe(false);
  });

  it('rejects blocked, malformed, content-tampered, and unattested manifests', () => {
    const blocked = manifest(); blocked.status = 'BLOCKED_UNTIL_HUMAN_ROLES_AND_EXACT_024_ARTIFACTS';
    expect(() => validateBaseSepoliaV3Manifest(blocked, attestation(blocked))).toThrow(/non-ready/);
    const malformed = manifest(); malformed.contracts.BaseSepoliaRehearsalAccessControllerV3.address = ethers.ZeroAddress; malformed.artifactHash = canonicalManifestContentHash(malformed);
    expect(() => validateBaseSepoliaV3Manifest(malformed, attestation(malformed))).toThrow(/AccessController/);
    const tampered = manifest(); tampered.contracts.SafeBondFactoryV3.address = address('99');
    expect(() => validateBaseSepoliaV3Manifest(tampered, attestation(tampered))).toThrow(/tampered/);
    expect(() => validateBaseSepoliaV3Manifest(manifest())).toThrow(/pinned production release-attester/);
  });

  it('matches the shared Python canonicalization corpus', () => {
    const corpus = JSON.parse(readFileSync(join(__dirname, '../../../pr11/base-sepolia-rehearsal/tests/canonicalization_corpus.json'), 'utf8')) as { name: string; mutation: string; expected: string }[];
    for (const testCase of corpus) {
      const value = manifest() as unknown as Record<string, unknown>;
      if (testCase.mutation === 'non_bmp') value['bad😀'] = true;
      else if (testCase.mutation === 'extra_candidate') (value.candidate as Record<string, unknown>).unlisted = true;
      else if (testCase.mutation === 'missing_caps') delete (value.capsAtomic as Record<string, unknown>).global;
      else if (testCase.mutation === 'prohibited_drift') (value.objectiveRuleSchema as Record<string, unknown>).prohibitedCategories = ['behavioral'];
      else if (testCase.mutation === 'excluded_drift') value.excludedSurface = ['Solana'];
      else throw new Error(testCase.name);
      if (testCase.mutation !== 'non_bmp') (value as unknown as BaseSepoliaV3RehearsalManifest).artifactHash = canonicalManifestContentHash(value as unknown as BaseSepoliaV3RehearsalManifest);
      expect(() => validateBaseSepoliaV3Manifest(value as unknown as BaseSepoliaV3RehearsalManifest, attestation(value as unknown as BaseSepoliaV3RehearsalManifest))).toThrow(new RegExp(testCase.expected));
    }
  });

  it('maps named contracts only after validation and deeply freezes runtime state', async () => {
    let calls = 0; const provider = { getNetwork: async () => { calls += 1; return { chainId: 84532n }; } } as unknown as ethers.Provider;
    const value = manifest(); await expect(BaseSepoliaV3Rehearsal.connect(provider, value, attestation(value))).rejects.toThrow(/pinned production release-attester/);
    expect(calls).toBe(0);
  });

  it('refuses provider access without a pinned attester and permits only on-chain sentinel (0,0)', async () => {
    const provider = { getNetwork: async () => ({ chainId: 8453n }) } as unknown as ethers.Provider; const value = manifest();
    await expect(BaseSepoliaV3Rehearsal.connect(provider, value, attestation(value))).rejects.toThrow(/pinned production release-attester/);
    expect(() => assertObjectiveRuleV3(1, 0)).toThrow(/sentinel/); expect(() => assertObjectiveRuleV3(ObjectiveRuleClassV3.None, 1)).toThrow(/sentinel/);
    expect(() => assertObjectiveRuleV3(ObjectiveRuleClassV3.None, 0)).not.toThrow();
  });
  it('rejects duplicate or noncanonical raw READY evidence before digesting', () => {
    const raw = JSON.stringify(manifest());
    expect(() => parseCanonicalEvidenceJson(raw, 'manifest')).toThrow(/canonical/);
    expect(() => validateBaseSepoliaV3ManifestJson('{"status":"BLOCKED","status":"READY_FOR_REHEARSAL"}')).toThrow(/duplicate/);
  });

});
