/**
 * scripts/outbound-pull-verify.ts
 *
 * Standalone demonstration + paired positive/negative verification of the GENERIC
 * outbound-pull mechanism (server/lib/outbound-pull.ts).
 *
 * DEV-ONLY. Spins up a REAL local HTTP server that stands in for "a customer
 * system", and drives the real executor against it over real sockets + real
 * fetch. Proves the MECHANISM end-to-end:
 *   connect → SSRF-guard → authenticate → pull → parse → validate → land →
 *   handle-every-failure-explicitly.
 *
 * Rule 18 discipline: every denial/failure assertion is paired with a positive
 * assertion in the same run, so a green is never hollow (a deny-all would fail
 * the paired positive).
 *
 * SSRF note: the production SSRF guard (assertSafeOutboundUrl) correctly blocks
 * loopback / RFC1918 / cloud-metadata addresses. The functional cases therefore
 * inject a TEST-ONLY guard that allows the one loopback mock origin and delegates
 * everything else to the real guard. The SSRF cases use the DEFAULT (real) guard
 * with no override, proving the real guard blocks dangerous URLs for real.
 *
 * Run: npx tsx scripts/outbound-pull-verify.ts
 */

import http from "http";
import { assertSafeOutboundUrl } from "../server/lib/siem-forwarder";
import {
  defineConnection,
  executePull,
  executePullAndLand,
  type ValidatorResult,
  type PullResult,
} from "../server/lib/outbound-pull";

// ─── Mock "customer system" ────────────────────────────────────────────────────

const GOOD_BEARER = "good-bearer-token-xyz";
const GOOD_APIKEY = "good-api-key-abc";
const BASIC_USER = "alice";
const BASIC_PASS = "s3cret-pass";
const OAUTH_CLIENT_ID = "client-id-123";
const OAUTH_CLIENT_SECRET = "client-secret-456";

const mintedTokens = new Set<string>();

function isAuthorized(headers: http.IncomingHttpHeaders): boolean {
  const auth = headers["authorization"];
  const apiKey = headers["x-api-key"];
  if (typeof apiKey === "string" && apiKey === GOOD_APIKEY) return true;
  if (typeof auth === "string") {
    if (auth === `Bearer ${GOOD_BEARER}`) return true;
    if (auth.startsWith("Bearer ") && mintedTokens.has(auth.slice("Bearer ".length))) return true;
    if (auth.startsWith("Basic ")) {
      const decoded = Buffer.from(auth.slice("Basic ".length), "base64").toString();
      if (decoded === `${BASIC_USER}:${BASIC_PASS}`) return true;
    }
  }
  return false;
}

const SAMPLE_TXNS = [
  { id: "tx-1", amount: 100.5, currency: "UGX", ts: "2026-06-11T08:00:00Z" },
  { id: "tx-2", amount: 42.0, currency: "UGX", ts: "2026-06-11T08:05:00Z" },
];

function startMockServer(): Promise<{ server: http.Server; origin: string }> {
  return new Promise((resolve) => {
    const server = http.createServer((req, res) => {
      const url = new URL(req.url ?? "/", "http://127.0.0.1");

      if (req.method === "POST" && url.pathname === "/oauth/token") {
        let body = "";
        req.on("data", (c) => (body += c));
        req.on("end", () => {
          const form = new URLSearchParams(body);
          if (
            form.get("grant_type") === "client_credentials" &&
            form.get("client_id") === OAUTH_CLIENT_ID &&
            form.get("client_secret") === OAUTH_CLIENT_SECRET
          ) {
            const token = `minted-${Date.now()}-${Math.random().toString(36).slice(2)}`;
            mintedTokens.add(token);
            res.writeHead(200, { "Content-Type": "application/json" });
            res.end(JSON.stringify({ access_token: token, token_type: "Bearer", expires_in: 3600 }));
          } else {
            res.writeHead(401, { "Content-Type": "application/json" });
            res.end(JSON.stringify({ error: "invalid_client" }));
          }
        });
        return;
      }

      if (req.method === "GET" && !isAuthorized(req.headers)) {
        res.writeHead(401, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ error: "unauthorized" }));
        return;
      }

      if (req.method === "GET" && url.pathname === "/data") {
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify(SAMPLE_TXNS));
        return;
      }
      if (req.method === "GET" && url.pathname === "/malformed") {
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end("this is { not valid json");
        return;
      }
      if (req.method === "GET" && url.pathname === "/badshape") {
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify([{ foo: "no id or amount here" }]));
        return;
      }

      res.writeHead(404, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ error: "not_found" }));
    });
    server.listen(0, "127.0.0.1", () => {
      const addr = server.address();
      const port = typeof addr === "object" && addr ? addr.port : 0;
      resolve({ server, origin: `http://127.0.0.1:${port}` });
    });
  });
}

// ─── Validator (the per-customer data contract — supplied by the caller) ───────

interface Txn { id: string; amount: number; currency?: string; ts?: string }

function validateTxns(raw: unknown): ValidatorResult<Txn> {
  if (!Array.isArray(raw)) return { ok: false, reason: "expected a JSON array" };
  const records: Txn[] = [];
  for (const r of raw) {
    if (!r || typeof r !== "object") return { ok: false, reason: "item is not an object" };
    const o = r as Record<string, unknown>;
    if (typeof o.id !== "string") return { ok: false, reason: "item missing string id" };
    if (typeof o.amount !== "number") return { ok: false, reason: "item missing numeric amount" };
    records.push({ id: o.id, amount: o.amount, currency: o.currency as string, ts: o.ts as string });
  }
  return { ok: true, records };
}

// ─── Tiny assertion harness ────────────────────────────────────────────────────

let pass = 0;
let fail = 0;
const failures: string[] = [];
function check(name: string, cond: boolean, detail?: string) {
  if (cond) {
    pass++;
    console.log(`  PASS  ${name}`);
  } else {
    fail++;
    failures.push(name + (detail ? ` — ${detail}` : ""));
    console.log(`  FAIL  ${name}${detail ? ` — ${detail}` : ""}`);
  }
}

async function main() {
  const { server, origin } = await startMockServer();
  // Test-only guard: allow the loopback mock origin, delegate everything else to the REAL guard.
  const testGuard = (url: string) =>
    url.startsWith(origin) ? { safe: true } : assertSafeOutboundUrl(url);

  try {
    console.log(`\nMock customer system at ${origin}\n`);

    // ── PAIR A — Bearer ──────────────────────────────────────────────────────
    console.log("PAIR A — bearer auth (positive + negative)");
    const aGood = defineConnection(
      { name: "A-good", tenantId: "t1", baseUrl: origin, authMethod: "bearer", secret: { token: GOOD_BEARER } },
      { assertSafeUrl: testGuard },
    );
    check("A: defineConnection(bearer) ok", aGood.ok);
    if (aGood.ok) {
      const landed: Txn[] = [];
      const r = await executePullAndLand<Txn>(
        aGood.config,
        { path: "/data" },
        (recs) => { landed.push(...recs); },
        { validate: validateTxns, assertSafeUrl: testGuard },
      );
      check("A+: pull+land succeeds", r.ok, !r.ok ? r.error : undefined);
      check("A+: data validated", r.ok && r.validated === true);
      check("A+: 2 records landed into sink", landed.length === 2, `got ${landed.length}`);
      // Encryption-at-rest (Rule 1): credential is ciphertext, plaintext absent.
      check("A: credential stored encrypted (ENC: prefix)", aGood.config.encryptedCredential.startsWith("ENC:"));
      check("A: plaintext token NOT present in stored credential", !aGood.config.encryptedCredential.includes(GOOD_BEARER));
    }
    const aBad = defineConnection(
      { name: "A-bad", tenantId: "t1", baseUrl: origin, authMethod: "bearer", secret: { token: "WRONG" } },
      { assertSafeUrl: testGuard },
    );
    if (aBad.ok) {
      const r = await executePull(aBad.config, { path: "/data" }, { validate: validateTxns, assertSafeUrl: testGuard });
      check("A-: wrong bearer → auth_failed", !r.ok && r.error === "auth_failed", !r.ok ? `${r.error}` : "unexpected ok");
    }

    // ── PAIR B — API key header ──────────────────────────────────────────────
    console.log("PAIR B — api-key-header auth");
    const bGood = defineConnection(
      { name: "B-good", tenantId: "t1", baseUrl: origin, authMethod: "api_key_header", apiKeyHeaderName: "X-Api-Key", secret: { key: GOOD_APIKEY } },
      { assertSafeUrl: testGuard },
    );
    check("B: defineConnection(api_key_header) ok", bGood.ok, bGood.ok ? undefined : bGood.reason);
    if (bGood.ok) {
      const r = await executePull<Txn>(bGood.config, { path: "/data" }, { validate: validateTxns, assertSafeUrl: testGuard });
      check("B+: api-key pull succeeds", r.ok && r.recordCount === 2, !r.ok ? r.error : undefined);
    }
    const bBad = defineConnection(
      { name: "B-bad", tenantId: "t1", baseUrl: origin, authMethod: "api_key_header", apiKeyHeaderName: "X-Api-Key", secret: { key: "nope" } },
      { assertSafeUrl: testGuard },
    );
    if (bBad.ok) {
      const r = await executePull(bBad.config, { path: "/data" }, { assertSafeUrl: testGuard });
      check("B-: wrong api-key → auth_failed", !r.ok && r.error === "auth_failed", !r.ok ? r.error : "unexpected ok");
    }

    // ── PAIR C — Basic ───────────────────────────────────────────────────────
    console.log("PAIR C — basic auth");
    const cGood = defineConnection(
      { name: "C-good", tenantId: "t1", baseUrl: origin, authMethod: "basic", basicUsername: BASIC_USER, secret: { password: BASIC_PASS } },
      { assertSafeUrl: testGuard },
    );
    check("C: defineConnection(basic) ok", cGood.ok, cGood.ok ? undefined : cGood.reason);
    if (cGood.ok) {
      const r = await executePull<Txn>(cGood.config, { path: "/data" }, { validate: validateTxns, assertSafeUrl: testGuard });
      check("C+: basic pull succeeds", r.ok && r.recordCount === 2, !r.ok ? r.error : undefined);
    }
    const cBad = defineConnection(
      { name: "C-bad", tenantId: "t1", baseUrl: origin, authMethod: "basic", basicUsername: BASIC_USER, secret: { password: "wrong" } },
      { assertSafeUrl: testGuard },
    );
    if (cBad.ok) {
      const r = await executePull(cBad.config, { path: "/data" }, { assertSafeUrl: testGuard });
      check("C-: wrong basic password → auth_failed", !r.ok && r.error === "auth_failed", !r.ok ? r.error : "unexpected ok");
    }

    // ── PAIR D — OAuth2 client credentials ───────────────────────────────────
    console.log("PAIR D — oauth2 client-credentials");
    const dGood = defineConnection(
      { name: "D-good", tenantId: "t1", baseUrl: origin, authMethod: "oauth2_client_credentials", oauthTokenUrl: `${origin}/oauth/token`, oauthClientId: OAUTH_CLIENT_ID, secret: { clientSecret: OAUTH_CLIENT_SECRET } },
      { assertSafeUrl: testGuard },
    );
    check("D: defineConnection(oauth2) ok", dGood.ok, dGood.ok ? undefined : dGood.reason);
    if (dGood.ok) {
      const r = await executePull<Txn>(dGood.config, { path: "/data" }, { validate: validateTxns, assertSafeUrl: testGuard });
      check("D+: oauth2 token minted + pull succeeds", r.ok && r.recordCount === 2, !r.ok ? r.error : undefined);
    }
    const dBad = defineConnection(
      { name: "D-bad", tenantId: "t1", baseUrl: origin, authMethod: "oauth2_client_credentials", oauthTokenUrl: `${origin}/oauth/token`, oauthClientId: OAUTH_CLIENT_ID, secret: { clientSecret: "wrong-secret" } },
      { assertSafeUrl: testGuard },
    );
    check("D: defineConnection(oauth2 bad-secret) ok at define time", dBad.ok, dBad.ok ? undefined : dBad.reason);
    if (dBad.ok) {
      const r = await executePull(dBad.config, { path: "/data" }, { assertSafeUrl: testGuard });
      check("D-: bad client secret → token_acquisition_failed", !r.ok && r.error === "token_acquisition_failed", !r.ok ? r.error : "unexpected ok");
    }

    // ── PAIR E — parse error (paired positive = A+ valid JSON) ────────────────
    console.log("PAIR E — malformed JSON");
    if (aGood.ok) {
      const r = await executePull(aGood.config, { path: "/malformed" }, { validate: validateTxns, assertSafeUrl: testGuard });
      check("E-: malformed body → parse_error", !r.ok && r.error === "parse_error", !r.ok ? r.error : "unexpected ok");
    }

    // ── PAIR F — validation error (paired positive = A+ validated true) ───────
    console.log("PAIR F — schema validation");
    if (aGood.ok) {
      const r = await executePull(aGood.config, { path: "/badshape" }, { validate: validateTxns, assertSafeUrl: testGuard });
      check("F-: bad-shape body → validation_error", !r.ok && r.error === "validation_error", !r.ok ? r.error : "unexpected ok");
    }

    // ── PAIR G — landing sink failure (paired positive = A+ landed) ───────────
    console.log("PAIR G — landing sink failure");
    if (aGood.ok) {
      const r = await executePullAndLand<Txn>(
        aGood.config,
        { path: "/data" },
        () => { throw new Error("sink boom"); },
        { validate: validateTxns, assertSafeUrl: testGuard },
      );
      check("G-: sink throws → landing_failed", !r.ok && r.error === "landing_failed", !r.ok ? r.error : "unexpected ok");
    }

    // ── PAIR H — SSRF guard (REAL default guard, no override) ──────────────────
    console.log("PAIR H — SSRF guard (real default guard)");
    // H+ positive control: real guard ALLOWS a safe external URL (not deny-all).
    check("H+: real guard allows safe external https URL", assertSafeOutboundUrl("https://api.customer-bank.example/v1/data").safe === true);
    // H- 1: executePull with DEFAULT guard against the loopback mock → blocked.
    if (aGood.ok) {
      const r = await executePull(aGood.config, { path: "/data" }, { validate: validateTxns }); // no assertSafeUrl → real guard
      check("H-1: real guard blocks loopback pull → blocked_url", !r.ok && r.error === "blocked_url", !r.ok ? r.error : "unexpected ok");
    }
    // H- 2: defineConnection with DEFAULT guard against cloud-metadata IP → blocked.
    const hMeta = defineConnection({ name: "H-meta", tenantId: "t1", baseUrl: "http://169.254.169.254", authMethod: "none" });
    check("H-2: real guard blocks cloud-metadata baseUrl", !hMeta.ok && hMeta.error === "blocked_url", hMeta.ok ? "unexpected ok" : hMeta.reason);
    // H- 3: defineConnection with DEFAULT guard against RFC1918 IP → blocked.
    const hPriv = defineConnection({ name: "H-priv", tenantId: "t1", baseUrl: "http://10.1.2.3/internal", authMethod: "none" });
    check("H-3: real guard blocks private-range baseUrl", !hPriv.ok && hPriv.error === "blocked_url", hPriv.ok ? "unexpected ok" : hPriv.reason);

    // ── PAIR J — config validation (positive + negative) ──────────────────────
    console.log("PAIR J — config validation");
    const jBad = defineConnection({ name: "J-bad", tenantId: "t1", baseUrl: "https://api.customer-bank.example", authMethod: "bearer" }); // missing secret.token
    check("J-: bearer without token → config_error", !jBad.ok && jBad.error === "config_error", jBad.ok ? "unexpected ok" : jBad.reason);
    const jGood = defineConnection({ name: "J-good", tenantId: "t1", baseUrl: "https://api.customer-bank.example", authMethod: "bearer", secret: { token: "x" } });
    check("J+: bearer with token + safe external URL → ok", jGood.ok);
    check("J+: 'none' auth stores empty credential (no secret)",
      (() => { const n = defineConnection({ name: "J-none", tenantId: "t1", baseUrl: "https://api.customer-bank.example", authMethod: "none" }); return n.ok && n.config.encryptedCredential === ""; })());

    // ── PAIR K — architect-finding hardening (F1 decrypt / F2 require-validator / F3 prod seam) ──
    console.log("PAIR K — architect-finding hardening");
    if (aGood.ok) {
      // F2 negative: landing without a validator is refused BEFORE any sink runs.
      let sinkCalled = false;
      const kNoVal = await executePullAndLand<Txn>(
        aGood.config,
        { path: "/data" },
        () => { sinkCalled = true; },
        { assertSafeUrl: testGuard }, // intentionally no validate
      );
      check("K-1 (F2): executePullAndLand without validator → config_error", !kNoVal.ok && kNoVal.error === "config_error", !kNoVal.ok ? kNoVal.error : "unexpected ok");
      check("K-1 (F2): sink NOT called when validator missing", sinkCalled === false);

      // F2 positive control: WITH a validator, records land in the sink.
      let landed: Txn[] = [];
      const kVal = await executePullAndLand<Txn>(
        aGood.config,
        { path: "/data" },
        (recs) => { landed = recs; },
        { validate: validateTxns, assertSafeUrl: testGuard },
      );
      check("K-1 (F2) positive: with validator → lands 2 records", kVal.ok && landed.length === 2, !kVal.ok ? kVal.error : undefined);

      // F1 negative: a corrupted credential surfaces decrypt_failed, never an unhandled throw.
      const tampered = { ...aGood.config, encryptedCredential: "ENC:not-a-valid-ciphertext-blob" };
      const kDec = await executePull<Txn>(tampered, { path: "/data" }, { validate: validateTxns, assertSafeUrl: testGuard });
      check("K-2 (F1): corrupted credential → decrypt_failed (no throw)", !kDec.ok && kDec.error === "decrypt_failed", !kDec.ok ? kDec.error : "unexpected ok");

      // F1 positive control: the intact credential still decrypts and pulls.
      const kDecOk = await executePull<Txn>(aGood.config, { path: "/data" }, { validate: validateTxns, assertSafeUrl: testGuard });
      check("K-2 (F1) positive: intact credential → pull succeeds", kDecOk.ok && kDecOk.recordCount === 2, !kDecOk.ok ? kDecOk.error : undefined);

      // F3 negative: in production the permissive test guard is IGNORED → real guard blocks loopback.
      const savedEnv = process.env.NODE_ENV;
      try {
        process.env.NODE_ENV = "production";
        const kProd = await executePull<Txn>(aGood.config, { path: "/data" }, { validate: validateTxns, assertSafeUrl: testGuard });
        check("K-3 (F3): prod ignores executePull test seam → real guard blocks loopback (blocked_url)", !kProd.ok && kProd.error === "blocked_url", !kProd.ok ? kProd.error : "unexpected ok");
        // F3 symmetry: defineConnection's seam is disabled in prod too.
        const kProdDef = defineConnection(
          { name: "K-prod-def", tenantId: "t1", baseUrl: origin, authMethod: "none" },
          { assertSafeUrl: testGuard },
        );
        check("K-3 (F3): prod ignores defineConnection test seam → real guard blocks loopback (blocked_url)", !kProdDef.ok && kProdDef.error === "blocked_url", kProdDef.ok ? "unexpected ok" : kProdDef.reason);
      } finally {
        process.env.NODE_ENV = savedEnv;
      }

      // F3 positive control: outside production the test guard IS honored → loopback pull succeeds.
      const kDev = await executePull<Txn>(aGood.config, { path: "/data" }, { validate: validateTxns, assertSafeUrl: testGuard });
      check("K-3 (F3) positive: non-prod honors test seam → loopback pull succeeds", kDev.ok && kDev.recordCount === 2, !kDev.ok ? kDev.error : undefined);
    }

    console.log(`\n──────────────────────────────────────────────`);
    console.log(`RESULT: ${pass} passed, ${fail} failed`);
    if (fail > 0) {
      console.log(`FAILURES:\n  - ${failures.join("\n  - ")}`);
    }
  } finally {
    server.close();
  }

  process.exit(fail === 0 ? 0 : 1);
}

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