/**
 * Rate limit store — sliding window counter.
 *
 * Default: in-memory Map (single-instance).
 * When REDIS_URL is set: uses Redis sorted sets for horizontal scaling.
 *
 * Each key tracks request timestamps within the window. Old entries are
 * pruned on every check.
 */

import type { RateLimitEntry, RateLimitResult, RateLimitRule } from './types.js';

// ── In-Memory Store ──────────────────────────────────────────

const store = new Map<string, RateLimitEntry>();

/**
 * Check (and record) a request against the sliding window for the given key.
 * Returns whether the request is allowed and remaining quota.
 */
export function checkAndRecord(key: string, rule: RateLimitRule): RateLimitResult {
  const now = Date.now();
  const windowMs = rule.windowSeconds * 1000;
  const windowStart = now - windowMs;

  // Get or create entry
  let entry = store.get(key);
  if (!entry) {
    entry = { timestamps: [] };
    store.set(key, entry);
  }

  // Prune expired timestamps
  entry.timestamps = entry.timestamps.filter(t => t > windowStart);

  // Check limit
  if (entry.timestamps.length >= rule.maxRequests) {
    const oldestInWindow = entry.timestamps[0];
    const resetAt = new Date(oldestInWindow + windowMs).toISOString();
    const retryAfterMs = oldestInWindow + windowMs - now;
    return {
      allowed: false,
      limit: rule.maxRequests,
      remaining: 0,
      resetAt,
      retryAfterMs: Math.max(0, retryAfterMs),
    };
  }

  // Record this request
  entry.timestamps.push(now);

  const resetAt = new Date(now + windowMs).toISOString();
  return {
    allowed: true,
    limit: rule.maxRequests,
    remaining: rule.maxRequests - entry.timestamps.length,
    resetAt,
  };
}

/**
 * Reset the rate limit for a specific key (e.g., after a successful auth).
 */
export function reset(key: string): void {
  store.delete(key);
}

/**
 * Get current state without recording a request (read-only check).
 */
export function peek(key: string, rule: RateLimitRule): RateLimitResult {
  const now = Date.now();
  const windowMs = rule.windowSeconds * 1000;
  const windowStart = now - windowMs;

  const entry = store.get(key);
  if (!entry) {
    return {
      allowed: true,
      limit: rule.maxRequests,
      remaining: rule.maxRequests,
      resetAt: new Date(now + windowMs).toISOString(),
    };
  }

  const active = entry.timestamps.filter(t => t > windowStart);
  const remaining = Math.max(0, rule.maxRequests - active.length);

  return {
    allowed: remaining > 0,
    limit: rule.maxRequests,
    remaining,
    resetAt: new Date(now + windowMs).toISOString(),
  };
}

/** Clear all entries (for testing). */
export function clearAll(): void {
  store.clear();
}
