// ============================================================================
// VF-W4a — Encryption-at-rest substrate behavioral proof
// AS/PLATFORM/2026/007 Full-Platform Verification Sweep, Wave 4a.
// ----------------------------------------------------------------------------
// VERIFICATION-ONLY. No remediation. All legs DEV (Rule 17). Paired POS/NEG
// (Rule 18). Behavioral proofs run as the real RLS-subject role (aegis_app):
//   - primitive legs (A/B/C) are pure crypto (role-independent) but executed
//     in-process against the same key material the app uses;
//   - PAM write-path leg (D) runs inside withTenantRls() => aegis_app + tenant
//     GUC, driving the REAL savePamSession() and reading the RAW stored column;
//   - TOTP leg (E) drives the REAL initializeTwoFactor(); the db proxy falls
//     back to pooledDb (aegis_app) outside a request, and totp_secrets is a
//     GLOBAL (non-RLS) table, so the write/read execute as aegis_app;
//   - HSM leg (F) drives the REAL verifyWithHSM() to expose hollow verify.
//
// What each leg ESTABLISHES:
//   A  field-encryption.ts  : REAL AES-256-GCM (round-trip + random-IV + GCM tamper-reject)
//   B  pii-encryption.ts    : REAL AES-256-GCM per-context key + nested-HMAC lookup
//                             (round-trip + tamper-reject + WRONG-TENANT-key reject
//                              + HMAC determinism/tenant-isolation/domain-separation)
//   C  encryption-service.ts: REAL AES-256-GCM singleton (round-trip + GCM tamper-reject)
//   D  savePamSession()     : the WRITE PATH itself encrypts (stored col is ciphertext,
//                             not plaintext; decrypts back) — closes the W3c->W4 carry
//   E  initializeTwoFactor(): DEFECT proof — the column NAMED `encryptedSecret`
//                             stores the TOTP secret in PLAINTEXT (F-W3b-2)
//   F  verifyWithHSM()      : DEFECT proof — a FORGED "HSM-SIG:" string verifies
//                             true (hollow prefix-check, not a real signature check)
//
// Clean-up: all synthetic rows removed (PAM via withBypassRls; TOTP via db proxy).
// ============================================================================

import { withTenantRls, withBypassRls } from "../server/db";
import { eq } from "drizzle-orm";

import { encryptField, decryptField, isEncrypted as isFieldEnc } from "../server/lib/field-encryption";
import { encryptPii, decryptPii, computeLookupHash, isEncrypted as isPiiEnc } from "../server/lib/pii-encryption";
import { encryptionService } from "../server/lib/encryption-service";

import { savePamSession } from "../server/lib/privileged-access-db";
import { pamSessions } from "../shared/schema-persistence";

import { initializeTwoFactor } from "../server/lib/two-factor-auth";
import { totpSecrets } from "../shared/schema-wave5";

import { signWithHSM, verifyWithHSM, getKeys } from "../server/lib/hsm-integration";

// ── test fixtures (all synthetic, self-cleaning) ───────────────────────────
const PAM_TENANT = "vf-w4a-pam-tenant";
const PAM_SID = "vf-w4a-pam-session-1";
const PAM_ACCT = "vf-w4a-admin-account";
const PAM_USER = "vf-w4a-operator-user";
const TOTP_UID = "vf-w4a-totp-probe-user";

let pass = 0;
let fail = 0;
function ok(label: string) { console.log(`  \u2705 PASS: ${label}`); pass++; }
function no(label: string, detail?: string) {
  console.error(`  \u274c FAIL: ${label}${detail ? `\n         ${detail}` : ""}`);
  fail++;
}

// Corrupt one char inside the ciphertext body (a few chars from the end so we
// never hit a version prefix or base64 padding), keeping it the same length.
function tamper(s: string): string {
  const i = Math.max(8, s.length - 6);
  const c = s[i];
  const repl = c === "A" ? "B" : "A";
  return s.slice(0, i) + repl + s.slice(i + 1);
}
async function expectThrow(label: string, fn: () => any) {
  try {
    await fn();
    no(label, "expected a throw (integrity reject) but the call returned");
  } catch {
    ok(label);
  }
}

async function legA() {
  console.log("\n— LEG A: field-encryption.ts primitive (AES-256-GCM) —");
  const pt = "field-secret-\u00e5\u00df-9931";
  const ct = encryptField(pt);
  if (ct !== pt && isFieldEnc(ct)) ok("A1 encryptField -> recognized ciphertext, != plaintext");
  else no("A1 encryptField ciphertext", `ct=${ct}`);
  if (decryptField(ct) === pt) ok("A2 decryptField round-trips to original");
  else no("A2 round-trip");
  const ct2 = encryptField(pt);
  if (ct2 !== ct) ok("A3 random IV: two encrypts of same plaintext differ");
  else no("A3 random IV");
  await expectThrow("A4 NEG tampered ciphertext rejected (GCM auth)", () => decryptField(tamper(ct)));
}

async function legB() {
  console.log("\n— LEG B: pii-encryption.ts primitive (per-context key + HMAC) —");
  const ctxA = { type: "tenant" as const, tenantId: "vf-w4a-tenantA" };
  const ctxB = { type: "tenant" as const, tenantId: "vf-w4a-tenantB" };
  const pt = "Nakato KADI 0772-000-111";
  const blob = encryptPii(pt, ctxA);
  if (blob.startsWith("ENC:v1:") && blob !== pt && isPiiEnc(blob)) ok("B1 encryptPii -> ENC:v1: ciphertext, != plaintext");
  else no("B1 encryptPii ciphertext", `blob=${blob}`);
  if (decryptPii(blob, ctxA) === pt) ok("B2 decryptPii(ctxA) round-trips");
  else no("B2 round-trip");
  await expectThrow("B3 NEG tampered ENC:v1 body rejected (GCM auth)", () => decryptPii("ENC:v1:" + tamper(blob.slice(7)), ctxA));
  // wrong-tenant key: ctxB derives a different scrypt key => GCM auth must fail
  await expectThrow("B4 NEG cross-tenant key cannot decrypt (per-tenant isolation)", () => decryptPii(blob, ctxB));

  // HMAC lookup hash
  const h = (v: string, t: string, col: string) => computeLookupHash(v, t, "kyc_customers", col);
  const det1 = h(pt, "vf-w4a-tenantA", "national_id");
  const det2 = h(pt, "vf-w4a-tenantA", "national_id");
  if (det1 === det2 && det1 !== pt) ok("B5 HMAC deterministic (same input+tenant+table+col)");
  else no("B5 HMAC determinism");
  if (h(pt, "vf-w4a-tenantB", "national_id") !== det1) ok("B6 HMAC per-tenant isolated");
  else no("B6 HMAC tenant isolation");
  if (h(pt, "vf-w4a-tenantA", "phone_number") !== det1) ok("B7 HMAC domain-separated by column");
  else no("B7 HMAC domain separation");
  if (h(pt + "x", "vf-w4a-tenantA", "national_id") !== det1) ok("B8 HMAC value-sensitive");
  else no("B8 HMAC value sensitivity");
}

async function legC() {
  console.log("\n— LEG C: encryption-service.ts singleton (AES-256-GCM) —");
  const pt = "integration-api-key-7731";
  const ct = encryptionService.encrypt(pt);
  if (ct !== pt && encryptionService.isEncrypted(ct)) ok("C1 encrypt -> {iv}:{tag}:{ct}, != plaintext");
  else no("C1 encrypt ciphertext", `ct=${ct}`);
  if (encryptionService.decrypt(ct) === pt) ok("C2 decrypt round-trips");
  else no("C2 round-trip");
  await expectThrow("C3 NEG tampered ciphertext rejected (GCM auth)", () => encryptionService.decrypt(tamper(ct)));
}

async function legD() {
  console.log("\n— LEG D: savePamSession() write-path encrypts (as aegis_app, RLS) —");
  try {
    await withTenantRls(PAM_TENANT, async (tdb) => {
      await savePamSession({
        id: PAM_SID, accessRequestId: "vf-w4a-req", accountId: "vf-w4a-acct",
        accountName: PAM_ACCT, userId: "vf-w4a-uid", userName: PAM_USER,
        startTime: new Date(), status: "active", commands: [], riskEvents: [],
        tenantId: PAM_TENANT,
      }, tdb);
      const [raw] = await tdb.select().from(pamSessions).where(eq(pamSessions.id, PAM_SID));
      if (!raw) { no("D0 row written + readable as aegis_app"); return; }
      ok("D0 row written + readable as aegis_app");
      const ctx = { type: "tenant" as const, tenantId: PAM_TENANT };
      if (raw.accountName !== PAM_ACCT && raw.userName !== PAM_USER) ok("D1 stored columns are NOT plaintext");
      else no("D1 stored columns are NOT plaintext", `acct=${raw.accountName} user=${raw.userName}`);
      if (isPiiEnc(raw.accountName) && isPiiEnc(raw.userName)) ok("D2 stored columns are ENC:v1: ciphertext");
      else no("D2 stored columns are ciphertext");
      if (decryptPii(raw.accountName, ctx) === PAM_ACCT && decryptPii(raw.userName, ctx) === PAM_USER)
        ok("D3 ciphertext decrypts back to original (write-path encrypt confirmed)");
      else no("D3 round-trip from stored ciphertext");
    });
  } catch (e) {
    no("LEG D execution", e instanceof Error ? e.message : String(e));
  }
}

async function legE() {
  console.log("\n— LEG E: initializeTwoFactor() DEFECT — plaintext in `encryptedSecret` (F-W3b-2) —");
  try {
    const setup = await initializeTwoFactor(TOTP_UID, "vf-w4a");
    const [raw] = await withBypassRls(async (bdb) =>
      bdb.select().from(totpSecrets).where(eq(totpSecrets.userId, TOTP_UID)),
    );
    if (!raw) { no("E0 totp row written"); return; }
    ok("E0 totp row written");
    // The stored column equals the plaintext base32 secret returned by setup.
    if (raw.encryptedSecret === setup.secret) ok("E1 `encryptedSecret` column == returned PLAINTEXT base32 secret");
    else no("E1 column == plaintext", `stored=${raw.encryptedSecret} setup=${setup.secret}`);
    // And it is NOT actually encrypted (neither ENC:v1: nor {iv}:{tag}:{ct}).
    if (!isPiiEnc(raw.encryptedSecret) && !encryptionService.isEncrypted(raw.encryptedSecret))
      ok("E2 stored secret is NOT encrypted (column name is misleading)");
    else no("E2 stored secret unexpectedly looks encrypted");
    // Backup codes are likewise plaintext JSON.
    const codes = JSON.parse(raw.backupCodes) as string[];
    if (Array.isArray(codes) && codes.length > 0 && codes.every((c) => setup.backupCodes.includes(c)))
      ok("E3 backupCodes stored as PLAINTEXT JSON (match the returned codes)");
    else no("E3 backupCodes plaintext");
  } catch (e) {
    no("LEG E execution", e instanceof Error ? e.message : String(e));
  }
}

async function legF() {
  console.log("\n— LEG F: verifyWithHSM() DEFECT — hollow prefix-check verification —");
  try {
    const keys = getKeys();
    if (keys.length === 0) { no("F0 HSM has pre-seeded keys"); return; }
    ok("F0 HSM has pre-seeded keys");
    const keyId = keys[0].id;
    const real = signWithHSM(keyId, "real-payload");
    if (real.success && real.signature) ok("F1 signWithHSM returns a signature for a valid key (POS)");
    else no("F1 signWithHSM");
    const realVerify = verifyWithHSM(keyId, "real-payload", real.signature!);
    if (realVerify.success && realVerify.valid) ok("F2 a real signature verifies true (POS)");
    else no("F2 real signature verifies");
    // DEFECT: a FORGED string that was never produced by signWithHSM, over
    // DIFFERENT data, verifies true purely because it starts with "HSM-SIG:".
    const forged = "HSM-SIG:" + "de".repeat(48);
    const forgedVerify = verifyWithHSM(keyId, "attacker-substituted-payload", forged);
    if (forgedVerify.success && forgedVerify.valid)
      ok("F3 DEFECT: a FORGED 'HSM-SIG:' string verifies TRUE (verification is hollow)");
    else no("F3 forged signature was (unexpectedly) rejected", JSON.stringify(forgedVerify));
  } catch (e) {
    no("LEG F execution", e instanceof Error ? e.message : String(e));
  }
}

async function cleanup() {
  try {
    await withBypassRls(async (bdb) => {
      await bdb.delete(pamSessions).where(eq(pamSessions.id, PAM_SID));
      await bdb.delete(totpSecrets).where(eq(totpSecrets.userId, TOTP_UID));
    });
    console.log("\n\u2713 cleanup: synthetic PAM + TOTP rows removed");
  } catch (e) {
    console.error("\u26a0 cleanup failed:", e instanceof Error ? e.message : String(e));
  }
}

async function main() {
  console.log("=== VF-W4a encryption-at-rest substrate proof (DEV, aegis_app, Rule 18) ===");
  await legA();
  await legB();
  await legC();
  await legD();
  await legE();
  await legF();
  await cleanup();
  console.log(`\n=== RESULT: ${pass} passed, ${fail} failed ===`);
  process.exit(fail === 0 ? 0 : 1);
}

main().catch((e) => { console.error("FATAL:", e); process.exit(2); });
