/**
 * AS/PLATFORM/2026/003 T003 — Machine-API-key ingestion (paired acceptance).
 *
 * Proves the scoped/hashed/revocable/TTL'd MACHINE KEY authorizes ONLY fincrime
 * signal ingestion, binds the tenant FROM THE KEY (never the payload), preserves
 * the OPAQUE_SUBJECT_REF fence, and is rate-limited PER KEY — with every green
 * paired against a discriminating control (Rule 18) and every durable effect
 * read back from the DB (never trust the status code).
 *
 * METHOD / HONESTY TAGGING (Rule 19):
 *   The REAL, exported components are exercised end-to-end over HTTP:
 *     • requireMachineKey("ingest:fincrime")     — server/lib/fincrime/machine-keys.ts
 *       (bearer parse + sha256(salt+raw) lookup + revoke/expiry/scope + tenant-bind)
 *     • machineIngestRateLimiter                 — server/lib/rate-limiter.ts
 *       (per-key bucket via getClientIdentifier → mk:<keyId>)
 *     • ingestSignalSchema (.strict + OPAQUE_SUBJECT_REF) / computeSourceSignalHash
 *                                                — server/lib/fincrime/ingestion.ts
 *     • storage.ingestFincrimeSignal in withTenantDbPhase — the SAME durable write
 *       the session route uses (runs as aegis_app under the key's tenant GUC).
 *   Only the ~14-line route GLUE is mirrored onto a throwaway in-process Express
 *   app (so we never boot a second listener on :5000); the auth-gate, scope, fence,
 *   tenant-binding, rate-limiter, and storage write are the production objects.
 *   The MACHINE_KEY_SALT here is a PROCESS-LOCAL TEST value (never a real secret;
 *   never printed) — the real prod salt + key-mint are operator HOLD actions.
 *
 * Legs (all paired):
 *   1. MK-INGEST-POSITIVE     — keyA opaque → 201; DB read-back: case tenant==key
 *      tenant, state OPEN, subject_ref == sent opaque token (durable + key-bound).
 *   2. MK-RLS-SUBJECT-ROLE    — the write path runs as current_user='aegis_app'
 *      (RLS-subject, not owner) with current_tenant_id()==key tenant (not hollow).
 *   3. MK-HASH-HYGIENE        — stored key_hash is 64-hex AND != raw key; the mint
 *      return omits keyHash; raw key carries the mk_ prefix.
 *   4. MK-SCOPE-NEGATIVE      — keyB (scope read:reports, no ingest) → 403
 *      insufficient-scope (paired with #1: the 403 is the scope, not a blanket no).
 *   5. MK-SUBJECT-FENCE       — keyA + raw kyc: subject → 400 (fence fires) paired
 *      with #1's opaque 201 (same key, same route — fence is the discriminator).
 *   6. MK-TENANT-PAYLOAD-REJECT — keyA + a tenantId key in the body → 400 (.strict)
 *      paired with #1: the payload can NEVER smuggle a tenant past the DTO.
 *   7. MK-REVOCATION          — keyD valid first (positive), revoke, then 401
 *      revoked (negative-after); DB read-back: revoked_at set.
 *   8. MK-RATE-LIMIT + ISOLATION — keyE: first 60 not-429 (incl a 201), 61st 429;
 *      then keyC (fresh bucket) → not-429 — limiter is per-key, not global/IP.
 *   9. MK-DORMANT-503         — salt removed → keyA → 503 (fail-closed dormant),
 *      paired with #1 (configured → works): the feature is OFF without the salt.
 *
 * Cleanup (Rule 11, DEV): synthetic keys + the cases they created are removed
 * (events/evidence first); immutable audit_chain_entries stay by design.
 * Exit 0 iff ALL legs PASS.
 */

// Process-local TEST salt (>= 16). NEVER a real secret; never printed. Set before
// any use so requireMachineKey/validate/issue (which read process.env at use-time)
// all share it.
process.env.MACHINE_KEY_SALT =
  "mkproof_test_salt_" + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);

import express from "express";
import http from "http";
import { AddressInfo } from "net";
import { sql } from "drizzle-orm";
import {
  requireMachineKey,
  issueMachineKey,
  revokeMachineKey,
} from "../server/lib/fincrime/machine-keys";
import { machineIngestRateLimiter } from "../server/lib/rate-limiter";
import { ingestSignalSchema, computeSourceSignalHash } from "../server/lib/fincrime/ingestion";
import { storage } from "../server/storage";
import { withTenantDbPhase } from "../server/lib/tenant-context";
import { db } from "../server/db";
import { ProofRun, verdict, ownerPool, endOwnerPool, keyClient } from "./lib/proof-harness";

const TENANT = "aegis-sovereign";
const RUN = Date.now();
const TEST_START = new Date();
const createdCaseIds = new Set<string>();
const mintedKeyIds: string[] = [];

/** Faithful mirror of POST /api/fincrime/ingest/machine — REAL middleware + REAL
 *  schema/hash/storage; only the glue is local. Plus a probe route that proves the
 *  write path's effective role+tenant through the SAME requireMachineKey + phase. */
function makeApp(): express.Express {
  const app = express();
  app.set("trust proxy", 1);
  app.use(express.json());

  app.post(
    "/api/fincrime/ingest/machine",
    requireMachineKey("ingest:fincrime"),
    machineIngestRateLimiter,
    async (req: any, res) => {
      try {
        const mk = req.machineKey as { keyId: string; tenantId: string } | undefined;
        const tenantId = mk?.tenantId;
        if (!tenantId) return res.status(500).json({ error: "no-tenant-bound" });
        const parsed = ingestSignalSchema.safeParse(req.body);
        if (!parsed.success) {
          return res
            .status(400)
            .json({ error: "invalid-signal", issues: parsed.error.issues.map((i) => i.path.join(".")) });
        }
        const signal = parsed.data;
        const sourceSignalHash = computeSourceSignalHash(signal);
        const result = await withTenantDbPhase(req, () =>
          storage.ingestFincrimeSignal({
            tenantId,
            actorUserId: `machine:${mk!.keyId}`,
            actorRole: "machine_ingest",
            sourceType: signal.sourceType,
            sourceRef: signal.sourceRef ?? null,
            subjectRef: signal.subjectRef,
            subjectRefKind: signal.subjectRefKind,
            sourceSignalHash,
            signal: signal.signal,
          }),
        );
        return res
          .status(result.created ? 201 : 200)
          .json({ caseId: result.case.id, state: result.case.state, created: result.created });
      } catch (e: any) {
        return res.status(500).json({ error: e?.message || "handler-error" });
      }
    },
  );

  // Harness-only probe: same requireMachineKey gate + same withTenantDbPhase the
  // ingest write uses, returning the effective DB identity so we can prove the
  // durable write is NOT hollow (runs as aegis_app, key-bound tenant).
  app.post("/api/_probe/dbctx", requireMachineKey("ingest:fincrime"), async (req: any, res) => {
    try {
      const out = await withTenantDbPhase(req, async () => {
        const r: any = await db.execute(sql`SELECT current_user AS u, current_tenant_id() AS t`);
        const row = (r.rows ?? r)[0] ?? {};
        return { u: row.u, t: row.t };
      });
      res.json(out);
    } catch (e: any) {
      res.status(500).json({ error: e?.message || "probe-error" });
    }
  });

  return app;
}

function listen(app: express.Express): Promise<{ server: http.Server; port: number }> {
  return new Promise((resolve) => {
    const server = http.createServer(app);
    server.listen(0, "127.0.0.1", () => resolve({ server, port: (server.address() as AddressInfo).port }));
  });
}

const baseSignal = (tag: string, extra: Record<string, unknown> = {}) => ({
  sourceType: "KYT_ALERT",
  subjectRef: `opaque:MKPROOF-${RUN}-${tag}`,
  signal: { proof: tag, run: RUN },
  ...extra,
});

async function main(): Promise<void> {
  const run = new ProofRun("AS/PLATFORM/2026/003 T003 — Machine-API-key ingestion", "DEV");
  const { server, port } = await listen(makeApp());
  const SRV = `http://127.0.0.1:${port}`;

  // Mint the synthetic key set (process-local test salt).
  const keyA = await issueMachineKey({ label: `mkproof-A-${RUN}`, scopes: ["ingest:fincrime"], tenantId: TENANT, ttlHours: 1, issuedBy: "t003-proof" });
  const keyB = await issueMachineKey({ label: `mkproof-B-${RUN}`, scopes: ["read:reports"], tenantId: TENANT, ttlHours: 1, issuedBy: "t003-proof" });
  const keyC = await issueMachineKey({ label: `mkproof-C-${RUN}`, scopes: ["ingest:fincrime"], tenantId: TENANT, ttlHours: 1, issuedBy: "t003-proof" });
  const keyD = await issueMachineKey({ label: `mkproof-D-${RUN}`, scopes: ["ingest:fincrime"], tenantId: TENANT, ttlHours: 1, issuedBy: "t003-proof" });
  const keyE = await issueMachineKey({ label: `mkproof-E-${RUN}`, scopes: ["ingest:fincrime"], tenantId: TENANT, ttlHours: 1, issuedBy: "t003-proof" });
  for (const k of [keyA, keyB, keyC, keyD, keyE]) mintedKeyIds.push((k as any).id);

  const cA = keyClient((keyA as any).rawKey, { server: SRV });
  const cB = keyClient((keyB as any).rawKey, { server: SRV });
  const cC = keyClient((keyC as any).rawKey, { server: SRV });
  const cD = keyClient((keyD as any).rawKey, { server: SRV });
  const cE = keyClient((keyE as any).rawKey, { server: SRV });
  const owner = ownerPool();

  try {
    // ── LEG 1 — MK-INGEST-POSITIVE ─────────────────────────────────────────────
    const subjA = `opaque:MKPROOF-${RUN}-A`;
    const r1 = await cA.post("/api/fincrime/ingest/machine", baseSignal("A"));
    const caseId = r1.json?.caseId as string | undefined;
    if (caseId) createdCaseIds.add(caseId);
    let row1: any = {};
    if (caseId) {
      const q = await owner.query(`SELECT tenant_id, state, subject_ref FROM fincrime_cases WHERE id=$1`, [caseId]);
      row1 = q.rows[0] ?? {};
    }
    const pos =
      r1.status === 201 &&
      row1.tenant_id === TENANT &&
      row1.state === "OPEN" &&
      row1.subject_ref === subjA;
    run.leg({
      id: "MK-INGEST-POSITIVE",
      scenario: "keyA (scope ingest:fincrime) POSTs an opaque-subject KYT_ALERT signal over the machine route",
      expectation: "201; a durable fincrime_cases row exists with tenant_id == the KEY's tenant, state OPEN, subject_ref == the opaque token sent",
      observed: `status=${r1.status} caseId=${caseId} db.tenant=${row1.tenant_id} db.state=${row1.state} db.subject=${row1.subject_ref}`,
      verdict: pos
        ? verdict.verified("machine key minted exactly one OPEN case, tenant bound from the key, opaque subject persisted verbatim")
        : verdict.fail(),
      pass: pos,
      signals: { status: r1.status, dbTenant: row1.tenant_id, dbState: row1.state, subjectMatch: row1.subject_ref === subjA },
    });

    // ── LEG 2 — MK-RLS-SUBJECT-ROLE (write path not hollow) ────────────────────
    const r2 = await cA.post("/api/_probe/dbctx", {});
    const roleOk = r2.status === 200 && r2.json?.u === "aegis_app" && r2.json?.t === TENANT;
    run.leg({
      id: "MK-RLS-SUBJECT-ROLE",
      scenario: "drive the SAME requireMachineKey + withTenantDbPhase used by the write, and read the effective DB identity",
      expectation: "current_user == 'aegis_app' (the non-superuser, non-BYPASSRLS RLS-subject role) AND current_tenant_id() == the key's tenant",
      observed: `status=${r2.status} current_user=${r2.json?.u} current_tenant_id=${r2.json?.t}`,
      verdict: roleOk
        ? verdict.verified("the durable ingest write runs as aegis_app under the key-bound tenant GUC — RLS WITH CHECK is genuinely enforced, not owner-bypassed")
        : verdict.fail(),
      pass: roleOk,
      signals: { currentUser: r2.json?.u, currentTenant: r2.json?.t },
    });

    // ── LEG 3 — MK-HASH-HYGIENE ────────────────────────────────────────────────
    const kq = await owner.query(`SELECT key_hash FROM fincrime_ingest_keys WHERE id=$1`, [(keyA as any).id]);
    const storedHash = kq.rows[0]?.key_hash as string | undefined;
    const rawA = (keyA as any).rawKey as string;
    const hygiene =
      !!storedHash &&
      /^[0-9a-f]{64}$/.test(storedHash) &&
      storedHash !== rawA &&
      (keyA as any).keyHash === undefined &&
      rawA.startsWith("mk_");
    run.leg({
      id: "MK-HASH-HYGIENE",
      scenario: "inspect the persisted key row and the mint return for keyA",
      expectation: "key_hash is a 64-hex SHA-256 and is NOT the raw key; the mint return omits keyHash; the raw key carries the mk_ prefix",
      observed: `hashIs64hex=${storedHash ? /^[0-9a-f]{64}$/.test(storedHash) : false} hash!=raw=${storedHash !== rawA} returnOmitsHash=${(keyA as any).keyHash === undefined} rawPrefix=${rawA.slice(0, 3)}`,
      verdict: hygiene
        ? verdict.verified("only a salted SHA-256 hash is stored; the raw key is never persisted and is shown once with the mk_ prefix")
        : verdict.fail(),
      pass: hygiene,
      signals: { hashIs64hex: storedHash ? /^[0-9a-f]{64}$/.test(storedHash) : false, hashNeRaw: storedHash !== rawA, returnOmitsHash: (keyA as any).keyHash === undefined },
    });

    // ── LEG 4 — MK-SCOPE-NEGATIVE (paired with #1) ─────────────────────────────
    const r4 = await cB.post("/api/fincrime/ingest/machine", baseSignal("B"));
    const scopeNeg = r4.status === 403 && r4.json?.reason === "insufficient-scope";
    run.leg({
      id: "MK-SCOPE-NEGATIVE",
      scenario: "keyB (scope read:reports — NOT ingest:fincrime) POSTs the same well-formed signal that keyA accepted at 201",
      expectation: "403 with reason insufficient-scope — the scope is the gate, not a blanket rejection (keyA with the right scope got 201)",
      observed: `status=${r4.status} reason=${r4.json?.reason}`,
      verdict: scopeNeg
        ? verdict.verified("a key lacking ingest:fincrime is refused at 403 insufficient-scope on the very payload the ingest-scoped key accepted")
        : verdict.fail(),
      pass: scopeNeg,
      signals: { status: r4.status, reason: r4.json?.reason },
    });

    // ── LEG 5 — MK-SUBJECT-FENCE (paired with #1's opaque 201) ─────────────────
    const r5 = await cA.post("/api/fincrime/ingest/machine", { sourceType: "KYT_ALERT", subjectRef: `kyc:RAWNIN-${RUN}`, signal: {} });
    const fence = r5.status === 400;
    run.leg({
      id: "MK-SUBJECT-FENCE",
      scenario: "keyA (correct scope) POSTs a RAW kyc: subject (a non-opaque identifier) — same key/route that accepted an opaque subject at 201",
      expectation: "400 — the OPAQUE_SUBJECT_REF fence rejects any non-(opaque|extref) subject; no raw PII can land on the case spine",
      observed: `status=${r5.status} body=${JSON.stringify(r5.json).slice(0, 120)}`,
      verdict: fence
        ? verdict.verified("the fence fires on a raw kyc: subject (400) while passing the opaque subject in #1 — the subject form is the discriminator")
        : verdict.fail(),
      pass: fence,
      signals: { status: r5.status },
    });

    // ── LEG 6 — MK-TENANT-PAYLOAD-REJECT (paired with #1) ──────────────────────
    const r6 = await cA.post("/api/fincrime/ingest/machine", baseSignal("T", { tenantId: "attacker-tenant" }));
    const tenantReject = r6.status === 400;
    run.leg({
      id: "MK-TENANT-PAYLOAD-REJECT",
      scenario: "keyA POSTs an otherwise-valid signal that ALSO carries a tenantId field in the body",
      expectation: "400 — the .strict() DTO rejects the unknown tenantId key; tenant is bound from the KEY and can never be smuggled via payload",
      observed: `status=${r6.status} body=${JSON.stringify(r6.json).slice(0, 120)}`,
      verdict: tenantReject
        ? verdict.verified("a payload tenantId is a hard 400 (strict DTO) — paired with #1 where the key-bound tenant produced the OPEN case")
        : verdict.fail(),
      pass: tenantReject,
      signals: { status: r6.status },
    });

    // ── LEG 7 — MK-REVOCATION (positive-before / negative-after) ───────────────
    const r7a = await cD.post("/api/fincrime/ingest/machine", baseSignal("D"));
    if (r7a.json?.caseId) createdCaseIds.add(r7a.json.caseId as string);
    await revokeMachineKey((keyD as any).id, "t003-proof");
    const r7b = await cD.post("/api/fincrime/ingest/machine", baseSignal("D"));
    const rq = await owner.query(`SELECT revoked_at FROM fincrime_ingest_keys WHERE id=$1`, [(keyD as any).id]);
    const revokedAtSet = !!rq.rows[0]?.revoked_at;
    const revocation = (r7a.status === 201 || r7a.status === 200) && r7b.status === 401 && r7b.json?.reason === "revoked" && revokedAtSet;
    run.leg({
      id: "MK-REVOCATION",
      scenario: "keyD ingests successfully, is revoked, then attempts the same ingest again",
      expectation: "first call succeeds (201/200); after revoke the same key is 401 reason=revoked; DB read-back shows revoked_at set",
      observed: `before=${r7a.status} after=${r7b.status} reason=${r7b.json?.reason} revoked_at_set=${revokedAtSet}`,
      verdict: revocation
        ? verdict.verified("revocation takes effect on the next request (401 revoked) and is durable (revoked_at) — proven valid-before/revoked-after, not never-valid")
        : verdict.fail(),
      pass: revocation,
      signals: { before: r7a.status, after: r7b.status, reason: r7b.json?.reason, revokedAtSet },
    });

    // ── LEG 8 — MK-RATE-LIMIT + ISOLATION (negative paired with isolation positive)
    const burst: number[] = [];
    for (let i = 0; i < 61; i++) {
      const r = await cE.post("/api/fincrime/ingest/machine", baseSignal("E"));
      burst.push(r.status);
      if (r.json?.caseId) createdCaseIds.add(r.json.caseId as string);
    }
    const first60NoneBlocked = burst.slice(0, 60).every((s) => s !== 429);
    const firstCreated = burst[0] === 201;
    const sixtyFirstBlocked = burst[60] === 429;
    // Isolation positive: a DIFFERENT key with a fresh bucket is NOT blocked.
    const rIso = await cC.post("/api/fincrime/ingest/machine", baseSignal("C"));
    if (rIso.json?.caseId) createdCaseIds.add(rIso.json.caseId as string);
    const isolationOk = rIso.status === 201 || rIso.status === 200;
    const rateOk = first60NoneBlocked && firstCreated && sixtyFirstBlocked && isolationOk;
    run.leg({
      id: "MK-RATE-LIMIT-AND-ISOLATION",
      scenario: "keyE fires 61 ingest calls (limit 60/60s); then keyC (a different key) fires once",
      expectation: "keyE: first 60 pass (incl a 201), the 61st trips 429; keyC: NOT 429 (fresh per-key bucket) — the limiter is keyed on mk:<keyId>, not global/IP",
      observed: `keyE statuses[0]=${burst[0]} [59]=${burst[59]} [60]=${burst[60]} any429-in-first60=${!first60NoneBlocked} keyC=${rIso.status}`,
      verdict: rateOk
        ? verdict.verified("keyE tripped exactly on the 61st while keyC (separate bucket) passed — per-key rate limiting, paired against cross-key isolation")
        : verdict.fail(),
      pass: rateOk,
      signals: { firstStatus: burst[0], sixtyFirst: burst[60], anyEarlyBlock: !first60NoneBlocked, isolation: rIso.status },
    });

    // ── LEG 9 — MK-DORMANT-503 (fail-closed without the salt) ──────────────────
    const savedSalt = process.env.MACHINE_KEY_SALT;
    delete process.env.MACHINE_KEY_SALT;
    const r9 = await cA.post("/api/fincrime/ingest/machine", baseSignal("A"));
    process.env.MACHINE_KEY_SALT = savedSalt;
    const dormant = r9.status === 503;
    run.leg({
      id: "MK-DORMANT-503",
      scenario: "remove MACHINE_KEY_SALT, then a previously-valid key (keyA) attempts ingest",
      expectation: "503 machine-key-auth-not-configured — without the salt the WHOLE feature is dormant (fail-closed), while #1 proves it works when configured",
      observed: `status=${r9.status} body=${JSON.stringify(r9.json).slice(0, 80)}`,
      verdict: dormant
        ? verdict.verified("with the salt absent every key is rejected at 503 — the feature is off-by-default and unusable until the operator provisions the salt")
        : verdict.fail(),
      pass: dormant,
      signals: { status: r9.status },
    });
  } finally {
    server.close();
    // ── Cleanup (Rule 11, DEV): synthetic cases (events/evidence first) + keys ──
    let leftoverCases = -1;
    let leftoverKeys = -1;
    try {
      const o = ownerPool();
      const ids = [...createdCaseIds];
      if (ids.length) {
        await o.query(`DELETE FROM fincrime_case_evidence WHERE case_id = ANY($1::varchar[])`, [ids]).catch(() => {});
        await o.query(`DELETE FROM fincrime_case_events WHERE case_id = ANY($1::varchar[])`, [ids]).catch(() => {});
        await o.query(`DELETE FROM fincrime_cases WHERE id = ANY($1::varchar[])`, [ids]).catch(() => {});
        const cc = await o.query(`SELECT count(*)::int AS n FROM fincrime_cases WHERE id = ANY($1::varchar[])`, [ids]);
        leftoverCases = cc.rows[0]?.n ?? -1;
      } else {
        leftoverCases = 0;
      }
      if (mintedKeyIds.length) {
        await o.query(`DELETE FROM fincrime_ingest_keys WHERE id = ANY($1::varchar[])`, [mintedKeyIds]).catch(() => {});
        const kk = await o.query(`SELECT count(*)::int AS n FROM fincrime_ingest_keys WHERE id = ANY($1::varchar[])`, [mintedKeyIds]);
        leftoverKeys = kk.rows[0]?.n ?? -1;
      } else {
        leftoverKeys = 0;
      }
      // Best-effort: machine_ingest rate-limit telemetry rows from this run.
      await o
        .query(`DELETE FROM rate_limit_events WHERE window_type='machine_ingest' AND created_at >= $1`, [TEST_START.toISOString()])
        .catch(() => {});
    } catch {
      /* harmless in dev */
    }
    console.log(`[cleanup] synthetic fincrime_cases remaining: ${leftoverCases}; synthetic machine keys remaining: ${leftoverKeys}`);
  }

  run.report();
  const { allPass } = run.acceptance();
  await endOwnerPool();
  process.exit(allPass ? 0 : 1);
}

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