/**
 * Tiny in-process sliding-window rate limiter.
 *
 * Per Node process — fine for a single Next.js standalone server, which is
 * the only deployment shape we ship. If the app ever runs behind multiple
 * replicas, swap the Map for a shared Redis-backed bucket; the public API
 * here stays the same.
 *
 * No deps: a Map of `key -> sorted timestamps[]`. Each call drops
 * timestamps older than the window, then either records the new request
 * and returns ok, or returns blocked with retry-after seconds.
 *
 * Memory: O(active-keys * limit). At a 5-per-15min ceiling and even
 * thousands of distinct IPs, this is trivially small.
 */

interface Window {
  /** Max events allowed per `windowMs` per `key`. */
  limit: number;
  /** Window length in ms. */
  windowMs: number;
}

const buckets = new Map<string, number[]>();

export function rateLimit(
  key: string,
  { limit, windowMs }: Window,
): { ok: true } | { ok: false; retryAfterSec: number } {
  const now = Date.now();
  const cutoff = now - windowMs;
  const hits = (buckets.get(key) ?? []).filter((t) => t > cutoff);

  if (hits.length >= limit) {
    const oldest = hits[0];
    const retryAfterSec = Math.max(1, Math.ceil((oldest + windowMs - now) / 1000));
    buckets.set(key, hits);
    return { ok: false, retryAfterSec };
  }

  hits.push(now);
  buckets.set(key, hits);
  return { ok: true };
}

/**
 * Best-effort client IP extraction for rate-limit keys.
 *
 * `X-Forwarded-For` is a client-appendable, comma-separated list: each proxy
 * APPENDS the address it observed. A direct client can therefore forge any
 * number of leftmost entries — using `xff.split(',')[0]` (the leftmost) lets
 * an attacker rotate a fake IP per request and defeat the limiter entirely.
 *
 * In a StartOS deployment the Next.js server sits behind exactly one trusted
 * proxy hop, so the RIGHTMOST entry is the address that proxy actually saw —
 * the only value the client cannot spoof. We key off that. (If the proxy
 * overwrites rather than appends XFF, the list has a single entry and
 * rightmost == leftmost, so this is also correct in that case.) If XFF is
 * absent (direct access in dev), fall back to `x-real-ip`, then to the
 * literal "unknown" key so the limiter still applies as a global cap.
 *
 * Assumes a single trusted hop; if the deployment ever grows additional
 * trusted proxies, count that many entries in from the right instead.
 */
export function clientIpFromHeaders(headers: Headers): string {
  const xff = headers.get('x-forwarded-for');
  if (xff) {
    const parts = xff
      .split(',')
      .map((p) => p.trim())
      .filter(Boolean);
    if (parts.length > 0) return parts[parts.length - 1];
  }
  const real = headers.get('x-real-ip');
  if (real) {
    const trimmed = real.trim();
    if (trimmed) return trimmed;
  }
  return 'unknown';
}
