/**
 * In-memory sliding-window rate limiter (2026-09-22). One process, one map —
 * exactly right for the single PM2 instance the site runs on; a second
 * instance would need a shared store (Redis) and this one file changes.
 *
 * Keys are caller-chosen ("login:ip:1.2.3.4", "api:1.2.3.4"). Old timestamps
 * are pruned on every hit, and idle keys are swept once a minute so the map
 * cannot grow without bound.
 */
const hits = new Map<string, number[]>()
let lastSweep = Date.now()

export interface RateLimitResult {
  allowed: boolean
  remaining: number
  /** Seconds until the oldest hit in the window expires (when blocked). */
  retryAfterSeconds: number
}

export function rateLimit(key: string, limit: number, windowMs: number): RateLimitResult {
  const now = Date.now()
  if (now - lastSweep > 60_000) {
    for (const [k, list] of hits) if (list.every((t) => now - t > windowMs)) hits.delete(k)
    lastSweep = now
  }
  const list = (hits.get(key) ?? []).filter((t) => now - t < windowMs)
  if (list.length >= limit) {
    hits.set(key, list)
    return { allowed: false, remaining: 0, retryAfterSeconds: Math.max(1, Math.ceil((list[0] + windowMs - now) / 1000)) }
  }
  list.push(now)
  hits.set(key, list)
  return { allowed: true, remaining: limit - list.length, retryAfterSeconds: 0 }
}

/** Look without counting — used to refuse a login attempt that is already locked. */
export function rateLimitStatus(key: string, limit: number, windowMs: number): RateLimitResult {
  const now = Date.now()
  const list = (hits.get(key) ?? []).filter((t) => now - t < windowMs)
  if (list.length >= limit) return { allowed: false, remaining: 0, retryAfterSeconds: Math.max(1, Math.ceil((list[0] + windowMs - now) / 1000)) }
  return { allowed: true, remaining: limit - list.length, retryAfterSeconds: 0 }
}

/** Forget a key — a successful login clears the failed-attempt counters. */
export function rateLimitReset(key: string) {
  hits.delete(key)
}

/** Sign-in policy: 10 failures per username or 30 per IP in 15 minutes → locked for the rest of the window. */
export const LOGIN_LIMITS = { perUser: 10, perIp: 30, windowMs: 15 * 60_000 } as const
/** API policy: 300 requests per IP per minute across /api/*. */
export const API_LIMITS = { perIp: 300, windowMs: 60_000 } as const

/** First hop of X-Forwarded-For, else the direct address, else "unknown". */
export function clientIp(headers: { get(name: string): string | null }): string {
  return headers.get('x-forwarded-for')?.split(',')[0]?.trim() || headers.get('x-real-ip')?.trim() || 'unknown'
}
