/**
 * T004a DSAR Requests verification — full template (i) run
 * Checks: (a) round-trip, (b) ciphertext on disk + IV randomness,
 *          (c) plaintext-passthrough, (d) nullable columns
 *
 * Run: npx tsx scripts/t004a-dsar-verify.ts
 */

import { withBypassRls } from "../server/db";
import { dsarRequests } from "../shared/schema-integrations";
import { encryptPii, decryptPii, isEncrypted } from "../server/lib/pii-encryption";
import { eq } from "drizzle-orm";
import { randomBytes } from "crypto";

const TENANT_ID = "t004a-dsar-verify-tenant";
const TEST_NAME  = "Alice Nakamukwatwa";
const TEST_EMAIL = "alice.nakamukwatwa@verify.test";
const TEST_PHONE = "+256701234567";
const TEST_NID   = "CM90120250001";
const ctx = { type: "tenant" as const, tenantId: TENANT_ID };

let passCount = 0;
let failCount = 0;

function pass(label: string, detail = "") {
  console.log(`  ✅ PASS: ${label}${detail ? " — " + detail : ""}`);
  passCount++;
}

function fail(label: string, detail = "") {
  console.error(`  ❌ FAIL: ${label}${detail ? " — " + detail : ""}`);
  failCount++;
}

const testRefs: string[] = [];

async function cleanup() {
  await withBypassRls(async (bdb) => {
    for (const ref of testRefs) {
      await bdb.delete(dsarRequests).where(eq(dsarRequests.reference, ref));
    }
  });
}

async function main() {
  console.log("\n=== T004a DSAR Requests — Template (i) Verification Suite ===\n");

  await cleanup();

  // ──────────────────────────────────────────────────────────────────────────
  // Check (a) + (b-ciphertext): New-write round-trip + stored as ENC:v1:
  // ──────────────────────────────────────────────────────────────────────────
  console.log("--- Check (a)+(b): Round-trip + ciphertext-on-disk ---");

  const refA = `DSAR-VERIFY-A-${randomBytes(3).toString("hex").toUpperCase()}`;
  testRefs.push(refA);

  await withBypassRls(async (bdb) => {
    await bdb.insert(dsarRequests).values({
      reference:         refA,
      tenantId:          TENANT_ID,
      subjectName:       encryptPii(TEST_NAME,  ctx),
      subjectEmail:      encryptPii(TEST_EMAIL, ctx),
      subjectPhone:      encryptPii(TEST_PHONE, ctx),
      subjectNationalId: encryptPii(TEST_NID,   ctx),
      requestType:       "access",
      requestChannel:    "portal",
      slaDeadline:       new Date(Date.now() + 30 * 86400_000),
      status:            "received",
    });
  });

  // Read raw stored values (Drizzle returns exactly what is stored in DB)
  const rawA = await withBypassRls(async (bdb) => {
    const [row] = await bdb.select().from(dsarRequests).where(eq(dsarRequests.reference, refA));
    return row;
  });

  if (!rawA) {
    fail("Row A not found after insert");
  } else {
    // (b) — all four PII columns stored as ENC:v1: ciphertext
    if (rawA.subjectName.startsWith("ENC:v1:")) {
      pass(`(b) subjectName stored as ciphertext: "${rawA.subjectName.slice(0, 28)}…"`);
    } else {
      fail("(b) subjectName NOT stored as ciphertext", `value: "${rawA.subjectName}"`);
    }

    if (rawA.subjectEmail.startsWith("ENC:v1:")) {
      pass(`(b) subjectEmail stored as ciphertext: "${rawA.subjectEmail.slice(0, 28)}…"`);
    } else {
      fail("(b) subjectEmail NOT stored as ciphertext", `value: "${rawA.subjectEmail}"`);
    }

    if (rawA.subjectPhone?.startsWith("ENC:v1:")) {
      pass(`(b) subjectPhone stored as ciphertext: "${rawA.subjectPhone.slice(0, 28)}…"`);
    } else {
      fail("(b) subjectPhone NOT stored as ciphertext", `value: "${rawA.subjectPhone}"`);
    }

    if (rawA.subjectNationalId?.startsWith("ENC:v1:")) {
      pass(`(b) subjectNationalId stored as ciphertext: "${rawA.subjectNationalId.slice(0, 28)}…"`);
    } else {
      fail("(b) subjectNationalId NOT stored as ciphertext", `value: "${rawA.subjectNationalId}"`);
    }

    // (a) — decrypt and confirm plaintext
    const decName  = decryptPii(rawA.subjectName,  ctx);
    const decEmail = decryptPii(rawA.subjectEmail, ctx);
    const decPhone = decryptPii(rawA.subjectPhone!, ctx);
    const decNid   = decryptPii(rawA.subjectNationalId!, ctx);

    if (decName === TEST_NAME) {
      pass(`(a) subjectName round-trip correct ("${decName}")`);
    } else {
      fail("(a) subjectName round-trip wrong", `expected "${TEST_NAME}", got "${decName}"`);
    }

    if (decEmail === TEST_EMAIL) {
      pass(`(a) subjectEmail round-trip correct ("${decEmail}")`);
    } else {
      fail("(a) subjectEmail round-trip wrong", `expected "${TEST_EMAIL}", got "${decEmail}"`);
    }

    if (decPhone === TEST_PHONE) {
      pass(`(a) subjectPhone round-trip correct ("${decPhone}")`);
    } else {
      fail("(a) subjectPhone round-trip wrong", `expected "${TEST_PHONE}", got "${decPhone}"`);
    }

    if (decNid === TEST_NID) {
      pass(`(a) subjectNationalId round-trip correct ("${decNid}")`);
    } else {
      fail("(a) subjectNationalId round-trip wrong", `expected "${TEST_NID}", got "${decNid}"`);
    }

    // (b) IV randomness — two in-memory encryptions of same plaintext must differ
    const enc1 = encryptPii(TEST_NAME, ctx);
    const enc2 = encryptPii(TEST_NAME, ctx);
    if (enc1 !== enc2) {
      pass("(b) Two encryptions of same plaintext differ (random IV confirmed in memory)");
    } else {
      fail("(b) IV randomness — two in-memory encryptions IDENTICAL — deterministic leak");
    }

    // (b) IV randomness — two DB writes of same plaintext must produce different stored ciphertext
    const refA2 = `DSAR-VERIFY-A2-${randomBytes(3).toString("hex").toUpperCase()}`;
    testRefs.push(refA2);

    await withBypassRls(async (bdb) => {
      await bdb.insert(dsarRequests).values({
        reference:      refA2,
        tenantId:       TENANT_ID,
        subjectName:    encryptPii(TEST_NAME,  ctx),
        subjectEmail:   encryptPii(TEST_EMAIL, ctx),
        requestType:    "access",
        requestChannel: "portal",
        slaDeadline:    new Date(Date.now() + 30 * 86400_000),
        status:         "received",
      });
    });

    const rawA2 = await withBypassRls(async (bdb) => {
      const [row] = await bdb.select().from(dsarRequests).where(eq(dsarRequests.reference, refA2));
      return row;
    });

    if (rawA2 && rawA.subjectName !== rawA2.subjectName) {
      pass("(b) Two DB writes of same plaintext → different stored ciphertext (IV randomness verified on actual stored values)");
    } else {
      fail("(b) IV randomness on stored values", "Two DB rows have identical ciphertext for same plaintext");
    }
  }

  // ──────────────────────────────────────────────────────────────────────────
  // Check (c): Existing-plaintext-row passthrough (dual-mode reader)
  // ──────────────────────────────────────────────────────────────────────────
  console.log("\n--- Check (c): Plaintext passthrough (pre-T004b row simulation) ---");

  const refC = `DSAR-VERIFY-C-${randomBytes(3).toString("hex").toUpperCase()}`;
  testRefs.push(refC);

  // Insert WITHOUT encryptPii() — simulates a row written before T004b migration
  await withBypassRls(async (bdb) => {
    await bdb.insert(dsarRequests).values({
      reference:         refC,
      tenantId:          TENANT_ID,
      subjectName:       TEST_NAME,    // raw plaintext — no encryptPii()
      subjectEmail:      TEST_EMAIL,
      subjectPhone:      TEST_PHONE,
      subjectNationalId: TEST_NID,
      requestType:       "erasure",
      requestChannel:    "portal",
      slaDeadline:       new Date(Date.now() + 30 * 86400_000),
      status:            "received",
    });
  });

  const rawC = await withBypassRls(async (bdb) => {
    const [row] = await bdb.select().from(dsarRequests).where(eq(dsarRequests.reference, refC));
    return row;
  });

  if (!rawC) {
    fail("Row C not found after plaintext insert");
  } else {
    // Confirm raw stored value is plaintext (not ENC:v1:)
    if (!isEncrypted(rawC.subjectName)) {
      pass(`(c) Raw stored subjectName is plaintext (isEncrypted=false): "${rawC.subjectName}"`);
    } else {
      fail("(c) Raw stored value should be plaintext", `got: "${rawC.subjectName}"`);
    }

    // decryptPii() must pass plaintext through unchanged
    const ptName  = decryptPii(rawC.subjectName,  ctx);
    const ptEmail = decryptPii(rawC.subjectEmail, ctx);
    const ptPhone = decryptPii(rawC.subjectPhone!, ctx);
    const ptNid   = decryptPii(rawC.subjectNationalId!, ctx);

    if (ptName === TEST_NAME) {
      pass(`(c) decryptPii passes plaintext subjectName through unchanged ("${ptName}")`);
    } else {
      fail("(c) Plaintext passthrough — subjectName", `got: "${ptName}"`);
    }

    if (ptEmail === TEST_EMAIL) {
      pass("(c) decryptPii passes plaintext subjectEmail through unchanged");
    } else {
      fail("(c) Plaintext passthrough — subjectEmail", `got: "${ptEmail}"`);
    }

    if (ptPhone === TEST_PHONE) {
      pass("(c) decryptPii passes plaintext subjectPhone through unchanged");
    } else {
      fail("(c) Plaintext passthrough — subjectPhone", `got: "${ptPhone}"`);
    }

    if (ptNid === TEST_NID) {
      pass("(c) decryptPii passes plaintext subjectNationalId through unchanged");
    } else {
      fail("(c) Plaintext passthrough — subjectNationalId", `got: "${ptNid}"`);
    }

    // Confirm read path did NOT mutate stored value (read doesn't trigger re-encrypt)
    const rawCPost = await withBypassRls(async (bdb) => {
      const [row] = await bdb.select().from(dsarRequests).where(eq(dsarRequests.reference, refC));
      return row;
    });

    if (rawCPost && !isEncrypted(rawCPost.subjectName)) {
      pass("(c) Stored value NOT mutated by read — still plaintext after decryptPii() call");
    } else {
      fail("(c) Read path mutated stored value", `now: "${rawCPost?.subjectName}"`);
    }
  }

  // ──────────────────────────────────────────────────────────────────────────
  // Check (d): Nullable columns — NULL phone and nationalId stored as NULL
  // ──────────────────────────────────────────────────────────────────────────
  console.log("\n--- Check (d): Nullable columns ---");

  const refD = `DSAR-VERIFY-D-${randomBytes(3).toString("hex").toUpperCase()}`;
  testRefs.push(refD);

  await withBypassRls(async (bdb) => {
    await bdb.insert(dsarRequests).values({
      reference:      refD,
      tenantId:       TENANT_ID,
      subjectName:    encryptPii(TEST_NAME,  ctx),
      subjectEmail:   encryptPii(TEST_EMAIL, ctx),
      // subjectPhone and subjectNationalId intentionally omitted (nullable)
      requestType:    "access",
      requestChannel: "portal",
      slaDeadline:    new Date(Date.now() + 30 * 86400_000),
      status:         "received",
    });
  });

  const rawD = await withBypassRls(async (bdb) => {
    const [row] = await bdb.select().from(dsarRequests).where(eq(dsarRequests.reference, refD));
    return row;
  });

  if (!rawD) {
    fail("Row D not found after nullable insert");
  } else {
    if (rawD.subjectPhone === null) {
      pass("(d) NULL subjectPhone stored as NULL (not encrypted empty string)");
    } else {
      fail("(d) NULL subjectPhone", `got: "${rawD.subjectPhone}"`);
    }

    if (rawD.subjectNationalId === null) {
      pass("(d) NULL subjectNationalId stored as NULL");
    } else {
      fail("(d) NULL subjectNationalId", `got: "${rawD.subjectNationalId}"`);
    }

    // decryptPii passthrough is safe for nulls — but the wiring uses conditional
    // so decryptDsarRow(row) on a null-phone row must return null for phone
    const decPhone = rawD.subjectPhone ? decryptPii(rawD.subjectPhone, ctx) : rawD.subjectPhone;
    const decNid   = rawD.subjectNationalId ? decryptPii(rawD.subjectNationalId, ctx) : rawD.subjectNationalId;

    if (decPhone === null) {
      pass("(d) decryptDsarRow pattern: null subjectPhone → null (not crash)");
    } else {
      fail("(d) decryptDsarRow pattern returned non-null for null phone", `got: "${decPhone}"`);
    }

    if (decNid === null) {
      pass("(d) decryptDsarRow pattern: null subjectNationalId → null (not crash)");
    } else {
      fail("(d) decryptDsarRow pattern returned non-null for null NID", `got: "${decNid}"`);
    }
  }

  // ──────────────────────────────────────────────────────────────────────────
  // Cleanup + summary
  // ──────────────────────────────────────────────────────────────────────────
  await cleanup();
  console.log(`\n[cleanup] Deleted ${testRefs.length} test reference(s)`);

  console.log("\n══════════════════════════════════════════════════════");
  const allPass = failCount === 0;
  console.log(`T004a DSAR Requests — RESULT: ${allPass ? "✅ ALL PASS" : "❌ SOME FAIL"}`);
  console.log(`  ${passCount}/${passCount + failCount} checks passed`);
  console.log("══════════════════════════════════════════════════════\n");

  if (!allPass) {
    process.exit(1);
  }
}

main().catch(err => {
  console.error("\nFATAL:", err);
  process.exit(1);
});
