import { createServer, type Server } from 'http';

/** Build the redirect target from a PINNED host — never the request Host header. */
export function buildRedirectLocation(pinnedHost: string, httpsPort: number, reqUrl: string): string {
  const safePath = reqUrl.replace(/[\r\n ]/g, '');
  const hostPort = httpsPort === 443 ? pinnedHost : `${pinnedHost}:${httpsPort}`;
  return `https://${hostPort}${safePath.startsWith('/') ? safePath : `/${safePath}`}`;
}

/**
 * Extract a safe host from a request `Host` header for a scheme-upgrade redirect:
 * strip whitespace/CRLF, drop the port, and accept only a plausible
 * hostname / IPv4 / [IPv6] token. Returns null if the header is missing or does
 * not look like a valid host (caller then falls back to the pinned host).
 *
 * Used only in `preferRequestHost` mode (see createHttpRedirectServer).
 */
export function safeHostFromHeader(hostHeader: string | undefined): string | null {
  if (!hostHeader) return null;
  const cleaned = hostHeader.replace(/[\r\n\t ]/g, '');
  if (!cleaned) return null;
  let host = cleaned;
  if (host.startsWith('[')) {
    // IPv6 literal: [addr] or [addr]:port — keep the brackets, drop any port.
    const end = host.indexOf(']');
    if (end === -1) return null;
    host = host.slice(0, end + 1);
    if (!/^\[[0-9A-Fa-f:]+\]$/.test(host)) return null;
    return host;
  }
  const colon = host.indexOf(':');
  if (colon !== -1) host = host.slice(0, colon);
  if (!host) return null;
  // Hostname or IPv4: letters, digits, dot, hyphen only.
  if (!/^[A-Za-z0-9.-]+$/.test(host)) return null;
  return host;
}

export interface RedirectServerOpts {
  httpsPort: number;
  pinnedHost: string;
  /**
   * When true AND the request carries a usable Host header, redirect to that
   * host (a scheme upgrade to the same host the client already used) instead of
   * the pinned host. Enabled ONLY when no redirect_host is configured and the
   * bind host is a wildcard (0.0.0.0 / ::), where there is no single correct
   * host to pin. Defaults to false, preserving the strict pinned-host behavior
   * (the open-redirect guard) for every other deployment.
   */
  preferRequestHost?: boolean;
}

export function createHttpRedirectServer(opts: RedirectServerOpts): Server {
  return createServer((req, res) => {
    let host = opts.pinnedHost;
    if (opts.preferRequestHost) {
      const fromHeader = safeHostFromHeader(req.headers.host);
      if (fromHeader) host = fromHeader;
    }
    const location = buildRedirectLocation(host, opts.httpsPort, req.url ?? '/');
    // 302 (temporary), NOT 301: this redirect is controlled by a runtime toggle
    // (server.tls.http_redirect). A 301 is cached by browsers as permanent, so
    // a visited http:// URL would keep redirecting even after the operator turns
    // the redirect off. 302 is re-checked each time, so disabling it takes effect.
    res.writeHead(302, { Location: location });
    res.end();
  });
}
