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

/**
 * Baseline security response headers applied to every response.
 *
 * These are deliberately conservative so they cannot break the SPA, which
 * serves same-origin `<iframe>`s (PDF preview, noVNC browser sessions) and
 * follows OAuth via top-level redirects rather than popups:
 *
 * - `X-Content-Type-Options: nosniff` — stop MIME-sniffing a response into
 *   something executable. (Untrusted workspace files layer `Content-Security-Policy:
 *   sandbox` on top via setUntrustedFileResponseHeaders.)
 * - `X-Frame-Options: SAMEORIGIN` — block clickjacking from foreign origins
 *   while still allowing the app's own same-origin iframes.
 * - `Referrer-Policy: strict-origin-when-cross-origin` — never leak the full
 *   path (which can carry task ids) to third-party origins.
 * - `Strict-Transport-Security` — handled per the HSTS policy below, and only
 *   on responses served over TLS (native HTTPS, or a trusted proxy reporting
 *   `X-Forwarded-Proto: https`). Never touched on plain-HTTP localhost dev.
 *
 * Note: `req.secure` reflects `X-Forwarded-Proto` only when Express trusts the
 * proxy. The server enables `trust proxy` together with `auth.secure_cookie`, so
 * an operator terminating TLS at a reverse proxy should set `secure_cookie: true`
 * for the HSTS policy to take effect. Native HTTPS sets `req.secure` intrinsically.
 *
 * ## HSTS policy
 *
 * HSTS is OPT-IN (`server.tls.hsts`, default off). Reasons:
 * - The default deployment uses a self-signed cert, where pinning a browser to
 *   HTTPS for months is pure footgun.
 * - HSTS is sticky on the client: once sent, the browser upgrades http→https
 *   BEFORE any request reaches the server, so turning HTTPS off in the UI can
 *   never un-stick it server-side. That is the "redirects forever" bug.
 *
 * So:
 * - `'enable'` (operator opted in, real cert): emit a normal HSTS max-age. We
 *   intentionally drop `includeSubDomains` — this app pins one host, and the
 *   subdomain pin is the most damaging part of a mistaken HSTS rollout.
 * - `'clear'` (default, TLS on but HSTS not opted in): emit `max-age=0` to
 *   ACTIVELY un-pin any browser that an earlier build/config had pinned. This
 *   is the recovery path — load the app once over HTTPS and the pin is cleared.
 */
export const HSTS_MAX_AGE_SECONDS = 15552000; // 180 days

export type HstsPolicy = 'enable' | 'clear';

export function applySecurityHeaders(req: Request, res: Response, hsts: HstsPolicy = 'clear'): void {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'SAMEORIGIN');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  if (req.secure) {
    res.setHeader(
      'Strict-Transport-Security',
      hsts === 'enable' ? `max-age=${HSTS_MAX_AGE_SECONDS}` : 'max-age=0',
    );
  }
}

/**
 * @param hsts The HSTS policy, or a thunk returning it. A thunk lets the server
 *   register this middleware before the resolved TLS config is available and
 *   read the policy lazily per request (the value is set by listen time).
 */
export function securityHeadersMiddleware(hsts: HstsPolicy | (() => HstsPolicy) = 'clear') {
  return (req: Request, res: Response, next: NextFunction): void => {
    applySecurityHeaders(req, res, typeof hsts === 'function' ? hsts() : hsts);
    next();
  };
}
