/**
 * FU-053 Layer 1 (T001) verification probe.
 *
 * Substantiates the tenant-middleware GUC writer refactor (architect-approved
 * Option (c) strict variant, 2026-05-29). Run with:
 *
 *   npx tsx scripts/fu053-t001-verify.ts
 *
 * Coverage (extended §9):
 *   1. Positive control — GUC set in the request tx is visible to a subsequent
 *      query issued through the `db` proxy within the same request.
 *   2. Negative control A — concurrent requests for different tenants do NOT
 *      bleed context into each other (AsyncLocalStorage isolation).
 *   3. Negative control B — a query with NO active request context sees an
 *      empty `app.current_tenant_id` (pooled fallback, no stale value).
 *   4. Fail-closed 400 — invalid tenantId format is rejected, handler never runs.
 *   5. Fail-closed 503 — pool.connect failure is surfaced, handler never runs.
 *   6. Rollback-vs-audit-survival (Q4) — an app write inside a request that ends
 *      5xx is rolled back, while the independent audit-chain write persists.
 */
import { EventEmitter } from "node:events";
import { sql } from "drizzle-orm";
import { db, auditDb, pool } from "../server/db";
import { tenantMiddleware } from "../server/lib/tenant-middleware";
import { chainAuditLog, verifyChainIntegrity } from "../server/lib/audit-chain";

let pass = 0;
let fail = 0;
const log = (label: string, ok: boolean, detail = "") => {
  console.log(`${ok ? "PASS" : "FAIL"} — ${label}${detail ? ": " + detail : ""}`);
  ok ? pass++ : fail++;
};

/** Minimal mock Express request. */
function mockReq(tenantId?: string): any {
  return {
    headers: tenantId ? { "x-tenant-id": tenantId } : {},
    session: undefined,
  };
}

/** Minimal mock Express response built on EventEmitter (finish/close). */
class MockRes extends EventEmitter {
  statusCode = 200;
  headersSent = false;
  body: any = undefined;
  status(code: number) {
    this.statusCode = code;
    return this;
  }
  json(obj: any) {
    this.body = obj;
    this.headersSent = true;
    return this;
  }
}

/** Read the current tenant GUC through the active runner (proxy). */
async function readTid(): Promise<string> {
  const res: any = await db.execute(
    sql`SELECT current_setting('app.current_tenant_id', true) AS tid`,
  );
  return res.rows?.[0]?.tid ?? "";
}

/**
 * Drive one request through the middleware. `handler` runs in the tenant
 * context; its return value is captured. `finalStatus` is the response status
 * used to settle the request (>=500 → rollback).
 */
function driveRequest<T>(
  tenantId: string | undefined,
  handler: () => Promise<T>,
  finalStatus = 200,
): Promise<{ res: MockRes; result?: T; handlerRan: boolean }> {
  const req = mockReq(tenantId);
  const res = new MockRes();
  let handlerRan = false;
  let result: T | undefined;

  const next = () => {
    handlerRan = true;
    void (async () => {
      try {
        result = await handler();
      } finally {
        res.statusCode = finalStatus;
        res.emit("finish");
      }
    })();
  };

  return tenantMiddleware(req, res as any, next).then(() => ({
    res,
    result,
    handlerRan,
  }));
}

(async () => {
  // ── Test 1: positive control ────────────────────────────────────────────
  console.log("\n=== Test 1: positive control — GUC visible within request ===");
  {
    const { result } = await driveRequest("tenant_alpha", async () => readTid());
    log("subsequent in-request query sees the request tenantId", result === "tenant_alpha",
      `got '${result}'`);
  }

  // ── Test 2: concurrent isolation ─────────────────────────────────────────
  console.log("\n=== Test 2: negative control A — concurrent isolation ===");
  {
    const seen: Record<string, string> = {};
    const mk = (t: string) =>
      driveRequest(t, async () => {
        // small interleaving delay so the two contexts overlap
        await new Promise((r) => setTimeout(r, 25));
        const tid = await readTid();
        seen[t] = tid;
        return tid;
      });
    await Promise.all([mk("tenant_one"), mk("tenant_two"), mk("tenant_three")]);
    const isolated =
      seen["tenant_one"] === "tenant_one" &&
      seen["tenant_two"] === "tenant_two" &&
      seen["tenant_three"] === "tenant_three";
    log("each concurrent request sees only its own tenantId", isolated,
      JSON.stringify(seen));
  }

  // ── Test 3: no-context empty ─────────────────────────────────────────────
  console.log("\n=== Test 3: negative control B — no context → empty GUC ===");
  {
    const res: any = await auditDb.execute(
      sql`SELECT current_setting('app.current_tenant_id', true) AS tid`,
    );
    const tid = res.rows?.[0]?.tid ?? "";
    log("pooled query with no request context sees empty tenant GUC", tid === "" || tid == null,
      `got '${tid}'`);
  }

  // ── Test 4: fail-closed 400 on invalid tenantId ──────────────────────────
  console.log("\n=== Test 4: fail-closed 400 — invalid tenantId format ===");
  {
    const req = mockReq("bad tenant id!! spaces & symbols");
    const res = new MockRes();
    let handlerRan = false;
    await tenantMiddleware(req, res as any, (() => { handlerRan = true; }) as any);
    log("invalid tenantId → 400", res.statusCode === 400, `status=${res.statusCode}`);
    log("handler never runs on 400", handlerRan === false);
  }

  // ── Test 5: fail-closed 503 on pool.connect failure ──────────────────────
  console.log("\n=== Test 5: fail-closed 503 — pool.connect failure ===");
  {
    const origConnect = pool.connect.bind(pool);
    (pool as any).connect = () => Promise.reject(new Error("simulated pool exhaustion"));
    const req = mockReq("tenant_x");
    const res = new MockRes();
    let handlerRan = false;
    try {
      await tenantMiddleware(req, res as any, (() => { handlerRan = true; }) as any);
    } finally {
      (pool as any).connect = origConnect;
    }
    log("pool.connect failure → 503", res.statusCode === 503, `status=${res.statusCode}`);
    log("handler never runs on 503", handlerRan === false);
  }

  // ── Test 6: rollback-vs-audit-survival (Q4) ──────────────────────────────
  console.log("\n=== Test 6: Q4 — app write rolls back, audit survives ===");
  {
    const marker = `fu053-t001-${Date.now()}-${Math.random().toString(36).slice(2)}`;
    // dedicated probe table on the pooled path (independent of request tx)
    await auditDb.execute(
      sql`CREATE TABLE IF NOT EXISTS fu053_probe (marker text)`,
    );

    const chainBefore = await verifyChainIntegrity();

    // Drive a request that writes an app row (through the request tx) AND an
    // audit-chain entry (independent path), then ends 5xx → request rolls back.
    await driveRequest(
      "tenant_rollback",
      async () => {
        await db.execute(
          sql`INSERT INTO fu053_probe (marker) VALUES (${marker})`,
        );
        await chainAuditLog({
          id: marker,
          action: "FU053_T001_ROLLBACK_PROBE",
          userId: null,
          resource: `fu053-probe:${marker}`,
          details: JSON.stringify({ marker }),
          timestamp: new Date().toISOString(),
        });
      },
      500, // force rollback
    );

    // App write must be gone (rolled back). Check from the pooled path.
    const appRow: any = await auditDb.execute(
      sql`SELECT count(*)::int AS n FROM fu053_probe WHERE marker = ${marker}`,
    );
    const appCount = appRow.rows?.[0]?.n ?? -1;
    log("app write rolled back (0 rows persist)", appCount === 0, `count=${appCount}`);

    // Audit entry must persist (independent commit).
    const auditRow: any = await auditDb.execute(
      sql`SELECT count(*)::int AS n FROM audit_chain_entries WHERE source_audit_id = ${marker}`,
    );
    const auditCount = auditRow.rows?.[0]?.n ?? -1;
    log("audit-chain entry survived the rollback", auditCount === 1, `count=${auditCount}`);

    const chainAfter = await verifyChainIntegrity();
    // The dev chain may carry pre-existing historical breakage unrelated to
    // T001 (verifyChainIntegrity walks the entire chain from genesis). The
    // in-scope claim is that THIS probe's tail-appended entry introduced no NEW
    // break: the integrity verdict and break point must be unchanged, and the
    // chain must have grown by exactly one entry.
    const noNewBreak =
      chainBefore.valid === chainAfter.valid &&
      chainBefore.brokenAt === chainAfter.brokenAt &&
      chainAfter.totalEntries === chainBefore.totalEntries + 1;
    log("rollback probe introduced no NEW chain break (tail entry links cleanly)", noNewBreak,
      `before{valid=${chainBefore.valid},brokenAt=${chainBefore.brokenAt},n=${chainBefore.totalEntries}} ` +
      `after{valid=${chainAfter.valid},brokenAt=${chainAfter.brokenAt},n=${chainAfter.totalEntries}}`);

    // cleanup probe table
    await auditDb.execute(sql`DROP TABLE IF EXISTS fu053_probe`);
  }

  console.log(`\n──────── FU-053 T001 probe: ${pass} passed, ${fail} failed ────────`);
  process.exit(fail === 0 ? 0 : 1);
})().catch((err) => {
  console.error("PROBE CRASHED:", err);
  process.exit(2);
});
