import type { Request, Response, NextFunction } from 'express';

// Resolve the public base URL of the API for an incoming request. With
// Express's `trust proxy` set in server.ts, `req.protocol` honors
// X-Forwarded-Proto and X-Forwarded-Host (or the Host header) reflects the
// public-facing origin even behind a reverse proxy. Falls back to the
// PUBLIC_API_URL env var, then '' (relative).
export function publicBaseFromReq(req?: Request): string {
  if (req) {
    const xfHost = req.headers['x-forwarded-host'];
    const host = (typeof xfHost === 'string' ? xfHost.split(',')[0].trim() : null) || req.get('host');
    if (host) return `${req.protocol}://${host}`;
  }
  const env = process.env.PUBLIC_API_URL;
  if (env) return env.replace(/\/+$/, '');
  return '';
}

// Resolve the public base URL of the WEB APP — the origin that serves the
// SPA routes like `/decide/:token` and `/pass/:shortId`. This is distinct from
// `publicBaseFromReq`, which returns the *API* origin (the Host of the incoming
// request); in these deployments the web app and API live on different
// subdomains (web.gp… vs api.gp…), so an email/WhatsApp Approve link must point
// at the web origin, not the API one.
//
// Resolution order (first hit wins), designed to work per-environment without
// hardcoding a domain:
//   1. APP_URL env — explicit override; also covers server-initiated flows
//      (cron) that have no request.
//   2. The request's Origin header, when it's an http(s) origin present in the
//      FRONTEND_URL allowlist. A cross-origin call from the web app (e.g. the
//      reception scanner hitting the API) carries the correct per-env web
//      origin here, so local/staging/production each resolve to their own
//      domain with zero extra config. Non-web schemes (capacitor://) are
//      ignored — they can't be used as a clickable link base.
//   3. The last http(s) non-localhost entry in FRONTEND_URL — deployments
//      append their real public domain to that list, so this favours the
//      production/staging host over dev/tunnel entries earlier in the list.
//   4. localhost — dev fallback of last resort.
export function webAppBaseUrl(req?: Request): string {
  const clean = (u: string) => u.replace(/\/+$/, '');
  if (process.env.APP_URL) return clean(process.env.APP_URL);

  const allow = (process.env.FRONTEND_URL || '')
    .split(',')
    .map((s) => s.trim())
    .filter(Boolean);

  const origin = req?.get('origin');
  if (origin && /^https?:\/\//i.test(origin) && allow.includes(origin)) {
    return clean(origin);
  }

  const publicEntries = allow.filter(
    (u) => /^https?:\/\//i.test(u) && !/localhost|127\.0\.0\.1/i.test(u),
  );
  if (publicEntries.length) return clean(publicEntries[publicEntries.length - 1]);

  return 'http://localhost:3200';
}

// Walks a JSON value and prepends the request's public base to any string
// that's a relative `/uploads/...` path. Promotes the path to
// `/api/uploads/...` so production reverse proxies that only forward
// `/api/*` to Node still hit the static handler. Mutates in place for
// arrays/objects and returns the (possibly rewritten) value. Anything
// already absolute (http://, https://, data:, blob:) or non-string is left
// untouched.
function rewriteUploadsInPlace(value: unknown, base: string): unknown {
  if (typeof value === 'string') {
    if (value.startsWith('/api/uploads/')) return `${base}${value}`;
    if (value.startsWith('/uploads/')) return `${base}/api${value}`;
    return value;
  }
  if (Array.isArray(value)) {
    for (let i = 0; i < value.length; i++) value[i] = rewriteUploadsInPlace(value[i], base);
    return value;
  }
  if (value && typeof value === 'object') {
    const obj = value as Record<string, unknown>;
    for (const k of Object.keys(obj)) obj[k] = rewriteUploadsInPlace(obj[k], base);
    return obj;
  }
  return value;
}

// Express middleware: makes every `res.json(...)` automatically rewrite any
// `/uploads/...` strings in the payload to absolute URLs pointing at the
// current request's public origin. This covers both new writes and rows
// inserted before saveUpload started emitting absolute URLs — no SQL
// backfill needed when domains change.
//
// Only rewrites when the request itself is HTTPS (req.secure honours
// X-Forwarded-Proto via `trust proxy` in server.ts). Reason: in dev the web
// app is served over HTTPS via Vite's basicSsl plugin while the API stays
// on plain HTTP. Returning `http://localhost:4200/uploads/...` would make
// the browser block `fetch()` as active mixed content (this broke the
// "Download PNG" button on the walk-in QR page). Leaving the URL relative
// in that case lets the Vite proxy keep serving the file over the web
// app's own HTTPS origin.
export function rewriteUploadUrlsMiddleware(req: Request, res: Response, next: NextFunction) {
  if (!req.secure) { next(); return; }
  const base = publicBaseFromReq(req);
  if (!base) { next(); return; }
  const originalJson = res.json.bind(res);
  res.json = (body: unknown) => originalJson(rewriteUploadsInPlace(body, base));
  next();
}
