// ============================================================================
// T004a Pre-Prod Verification Gates
// Three mandatory tests from T004A_DESIGN_BRIEF.md §10
// ALL THREE must PASS before any PII column value is written in encrypted form.
// Run: npx tsx scripts/t004a-pii-encryption-gates.ts
// ============================================================================

import {
  encryptPii,
  decryptPii,
  computeLookupHash,
  canonical,
  isEncrypted,
  parseVersion,
} from "../server/lib/pii-encryption";

let passed = 0;
let failed = 0;

function assert(label: string, condition: boolean, detail?: string): void {
  if (condition) {
    console.log(`  ✅ PASS: ${label}`);
    passed++;
  } else {
    console.log(`  ❌ FAIL: ${label}${detail ? ` — ${detail}` : ""}`);
    failed++;
  }
}

function assertNotEqual(label: string, a: string, b: string): void {
  assert(label, a !== b, a === b ? `both values identical (${a.slice(0, 16)}...)` : undefined);
}

// ============================================================================
// Gate 1 — D-B per-tenant isolation negative test (HMAC construction correctness)
// Proves: B-1 nested HMAC construction is genuinely per-tenant — same value in
// two different tenants produces different hashes. This is the non-isolation WBSI
// detection probe.
// ============================================================================

console.log("\n=== Gate 1: HMAC per-tenant isolation ===");

const testValue = "NIN-12345678";
const tenantA   = "tenant-alpha";
const tenantB   = "tenant-beta";
const TABLE     = "kyc_customers";
const COLUMN    = "national_id";

const hashA1 = computeLookupHash(testValue, tenantA, TABLE, COLUMN);
const hashA2 = computeLookupHash(testValue, tenantA, TABLE, COLUMN);
const hashB1 = computeLookupHash(testValue, tenantB, TABLE, COLUMN);

assert(
  "Same (value, tenant) → same hash on re-run (determinism)",
  hashA1 === hashA2,
  `run1=${hashA1.slice(0,8)}... run2=${hashA2.slice(0,8)}...`
);

assertNotEqual(
  "Same value, different tenants → DIFFERENT hashes (per-tenant isolation)",
  hashA1,
  hashB1
);

const hashColKyc = computeLookupHash(testValue, tenantA, "kyc_customers", "national_id");
const hashColUbo = computeLookupHash(testValue, tenantA, "ubo_registrations", "owner_national_id");

assertNotEqual(
  "Same (tenant, value), different table.column → DIFFERENT hashes (per-column differentiation)",
  hashColKyc,
  hashColUbo
);

console.log(`  [diagnostic] tenantA hash : ${hashA1}`);
console.log(`  [diagnostic] tenantB hash : ${hashB1}`);
console.log(`  [diagnostic] kyc col hash : ${hashColKyc}`);
console.log(`  [diagnostic] ubo col hash : ${hashColUbo}`);

// ============================================================================
// Gate 2 — canonical() consistency and collision test
// Proves: canonical() maps all formatting variants of the same ID to the same
// output AND does not collapse two distinct IDs to the same output.
// ============================================================================

console.log("\n=== Gate 2: canonical() consistency and collision ===");

const c1 = canonical("NIN-12345678");
const c2 = canonical("NIN 12345678");
const c3 = canonical("nin12345678");
const c4 = canonical("  NIN-12345678  ");

console.log(`  [canonical form] "${c1}"`);

assert(
  "canonical('NIN-12345678') === canonical('NIN 12345678')  [dash vs space]",
  c1 === c2,
  `c1="${c1}" c2="${c2}"`
);
assert(
  "canonical('NIN-12345678') === canonical('nin12345678')   [case normalisation]",
  c1 === c3,
  `c1="${c1}" c3="${c3}"`
);
assert(
  "canonical('NIN-12345678') === canonical('  NIN-12345678  ')  [whitespace trim]",
  c1 === c4,
  `c1="${c1}" c4="${c4}"`
);

const d1 = canonical("NIN-12345678");
const d2 = canonical("NIN-12345679");
const d3 = canonical("NIN-23456789");

assertNotEqual(
  "canonical('NIN-12345678') ≠ canonical('NIN-12345679')  [no false-collapse, last digit differs]",
  d1,
  d2
);
assertNotEqual(
  "canonical('NIN-12345678') ≠ canonical('NIN-23456789')  [no false-collapse, distinct IDs]",
  d1,
  d3
);

// Lookup consistency: format variants of the same value must produce the same hash
const hashFmt1 = computeLookupHash("NIN-12345678", tenantA, TABLE, COLUMN);
const hashFmt2 = computeLookupHash("NIN 12345678", tenantA, TABLE, COLUMN);
const hashFmt3 = computeLookupHash("nin12345678",  tenantA, TABLE, COLUMN);

assert(
  "lookup('NIN-12345678') === lookup('NIN 12345678')  [format-variant lookup consistency]",
  hashFmt1 === hashFmt2
);
assert(
  "lookup('NIN-12345678') === lookup('nin12345678')   [format-variant lookup consistency]",
  hashFmt1 === hashFmt3
);

// Distinct values produce distinct hashes
const hashDistinct1 = computeLookupHash("NIN-12345678", tenantA, TABLE, COLUMN);
const hashDistinct2 = computeLookupHash("NIN-12345679", tenantA, TABLE, COLUMN);
assertNotEqual(
  "lookup('NIN-12345678') ≠ lookup('NIN-12345679')  [distinct IDs → distinct hashes]",
  hashDistinct1,
  hashDistinct2
);

// ============================================================================
// Gate 3 — D-F version-tag parse round-trip test
// Proves: ENC:v1:{base64} format round-trips correctly — sentinel detected,
// version parsed, decrypt returns original plaintext.
// ============================================================================

console.log("\n=== Gate 3: version-tag round-trip ===");

const plaintext  = "NIN-12345678";
const ctxTenant  = { type: "tenant" as const, tenantId: "aegis-sovereign" };
const ctxPlatform = { type: "platform" as const };
const ctxUser    = { type: "user" as const, userId: "user-abc123" };

const encTenant   = encryptPii(plaintext, ctxTenant);
const decTenant   = decryptPii(encTenant, ctxTenant);

console.log(`  [encrypted sample] ${encTenant.slice(0, 40)}...`);

assert(
  "encrypted.startsWith('ENC:v1:')  [format sentinel + version tag]",
  encTenant.startsWith("ENC:v1:"),
  `got prefix: "${encTenant.slice(0, 12)}"`
);
assert("isEncrypted(encrypted) === true",  isEncrypted(encTenant) === true);
assert("isEncrypted(plaintext) === false", isEncrypted(plaintext) === false);
assert(
  "decrypted === plaintext  [round-trip correct]",
  decTenant === plaintext,
  `got "${decTenant}"`
);
assert(
  "parseVersion('ENC:v1:...') === 'v1'",
  parseVersion(encTenant) === "v1",
  `got "${parseVersion(encTenant)}"`
);
assert(
  "parseVersion(plaintext) === null  [graceful non-match]",
  parseVersion(plaintext) === null,
  `got "${parseVersion(plaintext)}"`
);

// Randomised IV: two encryptions of same plaintext must differ
const enc2 = encryptPii(plaintext, ctxTenant);
assertNotEqual(
  "Two encryptions of same plaintext differ  [random IV, no deterministic ciphertext]",
  encTenant,
  enc2
);

// Platform context round-trip
const encPlatform = encryptPii("Jane Doe", ctxPlatform);
const decPlatform = decryptPii(encPlatform, ctxPlatform);
assert(
  "Platform-context encrypt/decrypt round-trip correct",
  decPlatform === "Jane Doe",
  `got "${decPlatform}"`
);

// User context round-trip (biometric_profiles path)
const encUser = encryptPii('{"keystrokes":[120,130,110]}', ctxUser);
const decUser = decryptPii(encUser, ctxUser);
assert(
  "User-context encrypt/decrypt round-trip correct  [biometric_profiles path]",
  decUser === '{"keystrokes":[120,130,110]}',
  `got "${decUser}"`
);

// Dual-mode reader: plaintext passthrough
const passthrough = decryptPii("some-legacy-plaintext", ctxTenant);
assert(
  "decryptPii(plaintext) passthrough unchanged  [dual-mode reader, pre-T004b rows]",
  passthrough === "some-legacy-plaintext",
  `got "${passthrough}"`
);

// Cross-context decryption must fail (auth-tag mismatch = AES-GCM integrity)
console.log("\n=== Bonus: cross-context integrity protection ===");

const encForTenantA = encryptPii("sensitive-value", { type: "tenant", tenantId: "tenant-alpha" });
let crossTenantThrew = false;
try {
  decryptPii(encForTenantA, { type: "tenant", tenantId: "tenant-beta" });
} catch {
  crossTenantThrew = true;
}
assert(
  "decryptPii with wrong tenantId throws  [AES-GCM auth-tag mismatch = key isolation proven]",
  crossTenantThrew
);

const encForTenantB = encryptPii("sensitive-value", { type: "tenant", tenantId: "tenant-beta" });
let platformCrossThrew = false;
try {
  decryptPii(encForTenantB, { type: "platform" });
} catch {
  platformCrossThrew = true;
}
assert(
  "decryptPii tenant-encrypted value with platform context throws  [context isolation]",
  platformCrossThrew
);

// ============================================================================
// Summary
// ============================================================================

console.log("\n=== SUMMARY ===");
console.log(`PASS: ${passed}  FAIL: ${failed}`);

if (failed === 0) {
  console.log(
    "\n✅ ALL GATES PASS — primitive is correct.\n" +
    "Surface these results verbatim for advisor read.\n" +
    "HOLD: do NOT wire any PII table until advisor has confirmed the gate results."
  );
} else {
  console.log(
    `\n❌ ${failed} GATE(S) FAILED — do NOT wire any PII table.\n` +
    "Diagnose and fix the failing assertions before proceeding."
  );
  process.exit(1);
}
