// Fixed-window rate limiter for password attempts.
//
// SCOPE: this is per-process, in-memory state. It is effective for a single
// server instance, which is what the local-disk storage model already assumes.
// Running several instances behind a load balancer would give each its own
// counter, multiplying the effective limit by the instance count — moving to a
// shared store (Redis) is a prerequisite for scaling out.

type Bucket = { count: number; resetAt: number }

const buckets = new Map<string, Bucket>()

// Bound the map so a flood of distinct keys cannot grow it without limit.
const MAX_KEYS = 10000

function sweep(now: number) {
  for (const [key, bucket] of buckets) {
    if (bucket.resetAt <= now) buckets.delete(key)
  }
}

export interface RateLimitResult {
  allowed: boolean
  remaining: number
  /** Seconds until the window resets. Suitable for a Retry-After header. */
  retryAfter: number
}

/**
 * Consume one unit against `key`. Returns whether the caller is under the limit.
 */
export function rateLimit(key: string, limit: number, windowMs: number): RateLimitResult {
  const now = Date.now()
  const existing = buckets.get(key)

  if (!existing || existing.resetAt <= now) {
    if (buckets.size >= MAX_KEYS) sweep(now)
    const resetAt = now + windowMs
    buckets.set(key, { count: 1, resetAt })
    return { allowed: true, remaining: limit - 1, retryAfter: Math.ceil(windowMs / 1000) }
  }

  existing.count += 1
  const retryAfter = Math.max(1, Math.ceil((existing.resetAt - now) / 1000))

  return {
    allowed: existing.count <= limit,
    remaining: Math.max(0, limit - existing.count),
    retryAfter,
  }
}

/**
 * Clear the counter for a key — call after a successful authentication so a
 * legitimate user who mistyped a few times is not left throttled.
 */
export function resetRateLimit(key: string) {
  buckets.delete(key)
}

/**
 * Best-effort client identifier. x-forwarded-for is only trustworthy behind a
 * proxy that overwrites it; treat this as a speed bump, not an identity.
 */
export function clientKey(headers: Headers): string {
  const forwarded = headers.get('x-forwarded-for')
  if (forwarded) return forwarded.split(',')[0]!.trim()
  return headers.get('x-real-ip') || 'unknown'
}
