// static-rewrite-demo — a standalone qID Connect server for the very common
// "CDN-hosted static page + a small backend on another host" pattern.
//
// Your page lives on a CDN (Vercel/Netlify/Cloudflare Pages/…). This tiny
// server runs anywhere (a $5 VM, a container). A SAME-ORIGIN rewrite of
// `/qid/*` on the CDN forwards to this server, so the browser, the wallets,
// and the session cookie all live on the page's own origin — which is what
// makes the first-party HttpOnly session cookie work. See ./README.md for the
// rewrite recipes (Vercel / Netlify / Caddy / nginx) and why same-origin is
// mandatory.
//
// Runs on bun (bun:sqlite) or Node 20+ (node:sqlite). Durable SQLite state:
// nonces + poll secrets + accounts survive restarts, so a single instance —
// or several sharing this DB file on a shared volume — never wrongly reports
// a fresh request "expired".
//
//   QID_ORIGIN=https://www.yoursite.com \
//   QID_SESSION_SECRET=$(openssl rand -hex 24) \
//   QID_DB=./qid.sqlite  bun server.mjs
//
// SPDX-License-Identifier: MIT

import { createServer } from "node:http";
import { mkdirSync } from "node:fs";
import { dirname } from "node:path";
import {
  createQidConnect,
  SqliteNonceStore,
  SqliteAccounts,
} from "@qid/connect-server";
import { qidMiddleware } from "@qid/connect-server/express";

const HOST = process.env.QID_HOST || "127.0.0.1";
const PORT = Number(process.env.QID_PORT || 8890);

// CRITICAL: the origin must be the host users actually land on, AFTER any
// apex↔www or http→https redirect your CDN does. If your site 308-redirects
// example.com → www.example.com, this must be https://www.example.com, or
// /verify rejects every proof with bad_origin. Open your live site and read
// `location.origin` in the console — that exact string goes here.
const ORIGIN = process.env.QID_ORIGIN || "";
const SECRET = process.env.QID_SESSION_SECRET || "";
const DB_PATH = process.env.QID_DB || "./qid.sqlite";

if (!/^https?:\/\/[^/]+$/.test(ORIGIN)) {
  console.error("QID_ORIGIN must be an exact origin like https://www.example.com (no path, no trailing slash).");
  process.exit(1);
}
if (SECRET.length < 32) {
  console.error("QID_SESSION_SECRET must be 32+ random chars. Refusing to start.");
  process.exit(1);
}

// A boot-time sanity check: warn if the configured origin itself redirects
// elsewhere (the apex/www trap). Non-fatal — just saves hours of "bad_origin".
try {
  const head = await fetch(ORIGIN, { method: "HEAD", redirect: "manual" });
  const loc = head.headers.get("location");
  if (head.status >= 300 && head.status < 400 && loc) {
    console.warn(
      `WARNING: QID_ORIGIN ${ORIGIN} redirects (${head.status}) to ${loc}. ` +
      `Proofs must bind to the FINAL origin users land on — set QID_ORIGIN to that host, ` +
      `or /verify will reject with bad_origin.`
    );
  }
} catch { /* offline at boot: skip the check */ }

// bun:sqlite when on bun, node:sqlite (Node 20+) otherwise. Either gives a
// Database with .exec/.prepare that the Sqlite* stores use.
let db;
if (typeof Bun !== "undefined") {
  const { Database } = await import("bun:sqlite");
  mkdirSync(dirname(DB_PATH), { recursive: true });
  db = new Database(DB_PATH);
} else {
  const { DatabaseSync } = await import("node:sqlite");
  mkdirSync(dirname(DB_PATH), { recursive: true });
  db = new DatabaseSync(DB_PATH);
}
db.exec("PRAGMA journal_mode=WAL");

const qid = createQidConnect({
  origin: ORIGIN,
  sessionSecret: SECRET,
  nonceStore: new SqliteNonceStore(db),
  accounts: new SqliteAccounts(db),
});

// basePath: this service receives the FULL /qid/* path from the proxy (unlike
// Express's app.use("/qid", …) which strips it). Required for a raw node:http
// mount — see middleware.js.
const qidRoutes = qidMiddleware(qid, { basePath: "/qid" });

const server = createServer((req, res) => {
  const path = (req.url || "/").split("?")[0];
  if (path === "/healthz") {
    res.setHeader("Content-Type", "text/plain");
    res.setHeader("Cache-Control", "no-store");
    return res.end("ok\n");
  }
  if (path === "/qid" || path.startsWith("/qid/")) return qidRoutes(req, res);
  res.statusCode = 404;
  res.end("not found\n");
});

server.listen(PORT, HOST, () => {
  console.log(`qID Connect server on http://${HOST}:${PORT}  origin=${ORIGIN}  db=${DB_PATH}`);
});
