/**
 * Domain-aware redirect helpers.
 *
 * The passenger portal is served from more than one public domain
 * (e.g. https://bookingedr.et and https://passenger.edrsc.com). Payment
 * providers and Fayda need a browser-facing return/redirect URL, and that URL
 * must live on the SAME domain the user is currently browsing — otherwise the
 * user is bounced to the "other" site mid-flow.
 *
 * We derive the target domain from the incoming request's Origin (falling back
 * to Referer), but only trust it when it matches an explicit allowlist
 * (PAYMENT_REDIRECT_ALLOWED_ORIGINS) so a spoofed Origin can't turn these into
 * an open redirect. When the origin is absent or not allowlisted, the
 * configured (env) URL is left untouched.
 *
 * NOTE: this only rebases the ORIGIN (scheme + host + port). The path/query of
 * the configured URL is preserved, so the redirect always keeps its known
 * suffix (e.g. /booking/payment/telebirr/success). For Fayda this matters — the
 * full redirect_uri, host included, must be pre-registered with eSignet for
 * BOTH domains, or the login is rejected.
 */

/** Normalize to a bare origin ("https://host[:port]"), or null if unparseable. */
function toOrigin(value?: string | null): string | null {
  if (!value) return null;
  try {
    return new URL(value.trim()).origin;
  } catch {
    return null;
  }
}

/** Allowlisted origins from PAYMENT_REDIRECT_ALLOWED_ORIGINS (comma-separated). */
function allowedOrigins(): string[] {
  return (process.env.PAYMENT_REDIRECT_ALLOWED_ORIGINS ?? "")
    .split(",")
    .map((o) => toOrigin(o))
    .filter((o): o is string => o !== null);
}

/**
 * Pick the origin the request came from, restricted to the allowlist. Sources
 * are tried in order of trustworthiness:
 *   1. `origin`  — the Origin header (most reliable on cross-origin requests).
 *   2. `referer` — Referer fallback (browsers omit Origin on some same-site
 *      navigations but usually still send Referer).
 *   3. `frontendBaseUrl` — the X-Frontend-Base-URL header the portal sets
 *      explicitly, used as a last resort when neither of the above is present.
 * Returns null when none is present or allowlisted — callers then keep the
 * configured URL.
 */
export function resolveAllowedOrigin(
  origin?: string | null,
  referer?: string | null,
  frontendBaseUrl?: string | null,
): string | null {
  const candidate =
    toOrigin(origin) ?? toOrigin(referer) ?? toOrigin(frontendBaseUrl);
  if (!candidate) return null;
  return allowedOrigins().includes(candidate) ? candidate : null;
}

/**
 * Return `configuredUrl` with its origin swapped to `allowedOrigin`, preserving
 * path, query and hash. If `allowedOrigin` is null or either URL is unparseable,
 * the configured URL is returned unchanged.
 */
export function rebaseUrlOrigin(
  configuredUrl: string | undefined,
  allowedOrigin: string | null,
): string | undefined {
  if (!configuredUrl || !allowedOrigin) return configuredUrl;
  try {
    const target = new URL(configuredUrl);
    const base = new URL(allowedOrigin);
    target.protocol = base.protocol;
    target.host = base.host;
    return target.toString();
  } catch {
    return configuredUrl;
  }
}
