/**
 * enc-probe-dev-gate.ts — Functional dev gate for Encryption Probe Probes 3–18
 *
 * Design rationale (Rule 6 / Rule 14 discipline):
 *   This gate calls the ACTUAL /api/internal/encryption-probe HTTP route through
 *   the full Express middleware stack.  This is the ONLY correct approach because:
 *
 *   BC-4: the probe relies on tenantMiddleware setting app.current_tenant_id via
 *   AsyncLocalStorage BEFORE any db.* call fires.  A standalone script that imports
 *   db directly resolves to pooledDb (appPool, aegis_app, no GUC) — service-function
 *   INSERTs would fail RLS WITH CHECK and read-backs would return 0 rows.
 *
 *   The previous gate failure (pre-history: RLS diagnostic used ddlPool.query instead
 *   of db.execute and masked the empty-USING predicate bug) is avoided here because
 *   the route's db.execute calls run through the ALS-pinned client exactly as they
 *   would in production.
 *
 * What this gate proves:
 *   P3–P18:  each service function writes to its table; db.execute reads back the row
 *            via the (result as any).rows?.[0] pattern; classifyPrefix detects ENC:v1:
 *            on every wired column; cleanup removes each row.
 *   Residual check: 0 rows remain across all 17 marker tables after cleanup — proves
 *                   every COUNT query in the sweep is syntactically/semantically correct
 *                   (a broken query would either false-positive-abort or false-negative-
 *                   miss and leave residuals > 0).
 *   P18 isActive:   createUser INSERT accepts isActive:false; encryption fires regardless
 *                   of the isActive value.  isActive suppression confirmed at two sites
 *                   in the pre-gate read:
 *                     schema.ts:20  — boolean("is_active").notNull().default(true)
 *                     routes.ts:1009 — if (!user.isActive) { return 403 }
 *
 * Prerequisites:
 *   ENCRYPTION_PROBE_ENABLED=true — already set in dev environment.
 *   BOU_ADMIN_USERNAME + BOU_ADMIN_PASSWORD — available as env secrets.
 *   Dev server running on port 5000.
 *
 * Exit codes:  0 = all probes PASS + residualRows=0 + cleanupFailures=0
 *              1 = any failure or unexpected error
 */

const BASE_URL = "http://localhost:5000";

// ── helpers ────────────────────────────────────────────────────────────────

function redact(v: string | undefined): string {
  if (!v) return "(not set)";
  const len = v.length;
  return `(length=${len}, first-char-code=${v.charCodeAt(0)})`;
}

async function login(): Promise<{ cookie: string }> {
  // BOU_ADMIN_USERNAME/PASSWORD are Replit-managed secrets available to the server
  // process but NOT to standalone tsx scripts.  Fall back to shell-accessible env vars.
  const username =
    process.env.BOU_ADMIN_USERNAME ??
    process.env.AEGIS_ADMIN_USERNAME ??
    "admin"; // confirmed username from DB check
  const password =
    process.env.BOU_ADMIN_PASSWORD ??
    process.env.AEGIS_ADMIN_PASSWORD;
  if (!password) {
    throw new Error(
      "No admin password found in environment. " +
      `BOU_ADMIN_PASSWORD=${redact(process.env.BOU_ADMIN_PASSWORD)} ` +
      `AEGIS_ADMIN_PASSWORD=${redact(process.env.AEGIS_ADMIN_PASSWORD)}`,
    );
  }

  const res = await fetch(`${BASE_URL}/api/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username, password }),
    // Node 24 fetch follows redirects by default; login returns 200 on success
  });

  const body = (await res.json()) as Record<string, unknown>;

  if (!res.ok) {
    throw new Error(`Login HTTP ${res.status}: ${JSON.stringify(body)}`);
  }
  if (body.requiresTOTP) {
    throw new Error("Login blocked: TOTP 2FA required for admin — enrol TOTP or disable 2FA in dev DB before running gate");
  }
  if (!body.success) {
    throw new Error(`Login returned success=false: ${JSON.stringify(body)}`);
  }

  // Extract the first Set-Cookie value (connect.sid=...; Path=/; ...)
  const setCookie = res.headers.get("set-cookie");
  if (!setCookie) {
    throw new Error("Login succeeded but no Set-Cookie header — session not established");
  }
  const cookie = setCookie.split(";")[0]; // "connect.sid=s%3A..."
  if (!cookie.startsWith("connect.sid=")) {
    throw new Error(`Unexpected cookie name in Set-Cookie: ${cookie.substring(0, 30)}`);
  }
  return { cookie };
}

async function getCsrfToken(cookie: string): Promise<string> {
  const res = await fetch(`${BASE_URL}/api/csrf-token`, {
    headers: { Cookie: cookie },
  });
  if (!res.ok) {
    throw new Error(`GET /api/csrf-token HTTP ${res.status}`);
  }
  const body = (await res.json()) as { csrfToken?: string };
  if (!body.csrfToken) {
    throw new Error(`/api/csrf-token returned no csrfToken field: ${JSON.stringify(body)}`);
  }
  return body.csrfToken;
}

type ProbeEntry = {
  probe: string;
  result: "PASS" | "FAIL";
  encryptedPrefixMatch: boolean;
  observedPrefixClass: string;
  detail?: string;
  cleanupFailed?: boolean;
};

type ProbeResponse = {
  runId: string;
  environment: string;
  invoker: string;
  tenantId: string;
  elapsedMs: number;
  probes: ProbeEntry[];
  summary: {
    total: number;
    passed: number;
    failed: number;
    cleanupFailures: number;
    staleMarkersFound: number;
    residualRows: number;
    allPass: boolean;
  };
};

async function callProbeEndpoint(cookie: string, csrfToken: string): Promise<ProbeResponse> {
  const res = await fetch(`${BASE_URL}/api/internal/encryption-probe`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Cookie: cookie,
      "x-csrf-token": csrfToken,
    },
    body: JSON.stringify({}),
  });

  if (res.status === 409) {
    throw new Error("Probe endpoint returned 409 — a prior run is already in progress. Wait and retry.");
  }
  if (res.status === 404) {
    throw new Error(
      "Probe endpoint returned 404 — ENCRYPTION_PROBE_ENABLED is not 'true' in the dev environment. " +
      `Current value check: env says '${process.env.ENCRYPTION_PROBE_ENABLED}'`,
    );
  }
  if (!res.ok) {
    const text = await res.text();
    throw new Error(`Probe endpoint HTTP ${res.status}: ${text.substring(0, 300)}`);
  }

  return (await res.json()) as ProbeResponse;
}

// ── display ────────────────────────────────────────────────────────────────

function printProbeTable(probes: ProbeEntry[]): void {
  const col1 = 60;
  console.log(
    `\n${"PROBE".padEnd(col1)}  ${"RESULT".padEnd(6)}  ENC_MATCH  PREFIX_CLASS`,
  );
  console.log("─".repeat(100));
  for (const p of probes) {
    const icon    = p.result === "PASS" ? "✓" : "✗";
    const pfx     = p.observedPrefixClass.padEnd(18);
    const match   = p.encryptedPrefixMatch ? "true " : "false";
    const probeTxt = p.probe.length > col1 ? p.probe.substring(0, col1 - 1) + "…" : p.probe.padEnd(col1);
    console.log(`${icon} ${probeTxt}  ${p.result.padEnd(6)}  ${match}      ${pfx}`);
    if (p.detail && p.result === "FAIL") {
      console.log(`  DETAIL: ${p.detail}`);
    }
    if (p.cleanupFailed) {
      console.log(`  ⚠ cleanup failed for this probe — row may remain in DB`);
    }
  }
  console.log("─".repeat(100));
}

// ── main ───────────────────────────────────────────────────────────────────

async function main(): Promise<void> {
  console.log("══════════════════════════════════════════════════════════════════");
  console.log("enc-probe-dev-gate — Encryption Probe Functional Dev Gate");
  console.log("══════════════════════════════════════════════════════════════════");
  console.log(`Target:  ${BASE_URL}`);
  console.log(`Time:    ${new Date().toISOString()}`);
  console.log();

  // P18 isActive pre-gate note (confirmed from source before this script was written)
  console.log("P18 isActive pre-gate evidence (source-confirmed, not inferred):");
  console.log("  schema.ts:20  — boolean('is_active').notNull().default(true)");
  console.log("  routes.ts:1009 — if (!user.isActive) { return res.status(403) }");
  console.log("  createUser in storage.ts:272 — db.insert(users).values(dbInsert).returning()");
  console.log("  => isActive:false flows into INSERT; login gate fires on read.");
  console.log("  P18 probe: createUser({isActive:false, fullName:'PII…'}) → read full_name → ENC:v1:");
  console.log();

  // Step 1: Login
  console.log("Step 1 — Login as admin (credentials from env, not printed) …");
  let cookie: string;
  try {
    ({ cookie } = await login());
    console.log("  ✓ Login succeeded — session cookie obtained");
  } catch (err) {
    console.error(`  ✗ Login FAILED: ${(err as Error).message}`);
    process.exit(1);
  }

  // Step 2: CSRF token
  console.log("Step 2 — Fetch CSRF token …");
  let csrfToken: string;
  try {
    csrfToken = await getCsrfToken(cookie);
    console.log("  ✓ CSRF token obtained");
  } catch (err) {
    console.error(`  ✗ CSRF fetch FAILED: ${(err as Error).message}`);
    process.exit(1);
  }

  // Step 3: Run probe endpoint
  console.log("Step 3 — POST /api/internal/encryption-probe (may take 10–30 s) …");
  let result: ProbeResponse;
  try {
    result = await callProbeEndpoint(cookie, csrfToken);
    console.log(`  ✓ Probe endpoint responded — runId=${result.runId} env=${result.environment} tenantId=${result.tenantId} elapsedMs=${result.elapsedMs}`);
  } catch (err) {
    console.error(`  ✗ Probe call FAILED: ${(err as Error).message}`);
    process.exit(1);
  }

  // Step 4: Display results
  printProbeTable(result.probes);

  const s = result.summary;
  console.log(`\nSUMMARY`);
  console.log(`  total:             ${s.total}`);
  console.log(`  passed:            ${s.passed}`);
  console.log(`  failed:            ${s.failed}`);
  console.log(`  cleanupFailures:   ${s.cleanupFailures}`);
  console.log(`  staleMarkersFound: ${s.staleMarkersFound}`);
  console.log(`  residualRows:      ${s.residualRows}`);
  console.log(`  allPass:           ${s.allPass}`);

  // Step 5: Evaluate gate criteria
  console.log();
  const gateItems: Array<{ label: string; ok: boolean; note: string }> = [
    {
      label: "All probes PASS",
      ok: s.failed === 0,
      note: s.failed > 0 ? `${s.failed} probe(s) failed — see FAIL rows above` : `${s.passed}/${s.total} passed`,
    },
    {
      label: "cleanupFailures = 0",
      ok: s.cleanupFailures === 0,
      note: s.cleanupFailures > 0 ? `${s.cleanupFailures} cleanup(s) failed — probe rows may remain in DB` : "all probe rows cleaned up",
    },
    {
      label: "residualRows = 0",
      ok: s.residualRows === 0,
      note:
        s.residualRows === 0
          ? "0 probe rows remain across all 17 marker tables — all COUNT queries confirmed correct"
          : `${s.residualRows} probe row(s) remain — inspect + manually DELETE before next run`,
    },
    {
      label: "tenantId resolved (not 'default')",
      ok: result.tenantId !== "default" && !!result.tenantId,
      note:
        result.tenantId && result.tenantId !== "default"
          ? `tenantId='${result.tenantId}' — tenantMiddleware GUC writer fired correctly`
          : `tenantId='${result.tenantId}' — GUC writer did not resolve session tenant; check T002.5 Deliverable 7`,
    },
  ];

  let anyFail = false;
  console.log("GATE CRITERIA:");
  for (const item of gateItems) {
    const icon = item.ok ? "✓" : "✗";
    console.log(`  ${icon} ${item.label.padEnd(38)} — ${item.note}`);
    if (!item.ok) anyFail = true;
  }

  // P18-specific evidence in the result
  const p18 = result.probes.find(p => /Probe 18|P18|user.*isActive|isActive.*user/i.test(p.probe));
  if (p18) {
    console.log();
    console.log("P18 isActive suppression probe result:");
    console.log(`  result:               ${p18.result}`);
    console.log(`  encryptedPrefixMatch: ${p18.encryptedPrefixMatch}`);
    console.log(`  observedPrefixClass:  ${p18.observedPrefixClass}`);
    if (p18.detail) console.log(`  detail:               ${p18.detail}`);
    if (p18.result === "PASS" && p18.encryptedPrefixMatch) {
      console.log("  ✓ createUser with isActive:false wrote ENC:v1: prefix on full_name");
      console.log("    => isActive column is real, INSERT accepted false value, encryption wiring fired");
    }
  } else {
    console.log("\n  ⚠ P18 probe not found in results — check probe numbering (expected 'P18' or 'isActive' in probe name)");
  }

  console.log();
  if (!anyFail && s.allPass) {
    console.log("══════════════════════════════════════════════════════════════════");
    console.log("DEV GATE: ✓ PASS — all criteria met");
    console.log("  Probes 3–18 (incl. 16b empty-string truthy-guard) + residual check functionally confirmed in dev.");
    console.log("  db.execute (result as any).rows?.[0] pattern exercised through");
    console.log("  the full tenantMiddleware → ALS → pinned-client path.");
    console.log("  Ready to surface publish readiness to advisor per Rule 13.");
    console.log("══════════════════════════════════════════════════════════════════");
    process.exit(0);
  } else {
    console.log("══════════════════════════════════════════════════════════════════");
    console.log("DEV GATE: ✗ FAIL — one or more criteria not met (see above)");
    console.log("══════════════════════════════════════════════════════════════════");
    process.exit(1);
  }
}

main().catch(err => {
  console.error("Unexpected error in gate script:", err);
  process.exit(1);
});
