/**
 * Login rate limiting. Deployment and activation of this guard are separate
 * from the auth logic itself: the use case only knows the interface.
 */

export type RateLimitDecision = { allowed: true } | { allowed: false; retryAfterMs: number };

export interface LoginRateLimiter {
  /** Called before attempting authentication. */
  consume(key: string): RateLimitDecision;
  /** Called after a failed authentication attempt. */
  recordFailure(key: string): void;
  /** Called after a successful login. */
  reset(key: string): void;
}

interface Entry {
  failures: number;
  lockedUntil: number;
}

export interface InMemoryRateLimiterOptions {
  maxFailures?: number;
  cooldownMs?: number;
  now?: () => number;
}

/**
 * Per-key consecutive-failure limiter. In-memory per instance: acceptable for
 * the single-process monolith; swap for a Redis-backed impl behind the same
 * interface when horizontal scaling arrives.
 */
export class InMemoryLoginRateLimiter implements LoginRateLimiter {
  private readonly maxFailures: number;
  private readonly cooldownMs: number;
  private readonly now: () => number;
  private readonly entries = new Map<string, Entry>();

  constructor(options: InMemoryRateLimiterOptions = {}) {
    this.maxFailures = options.maxFailures ?? 10;
    this.cooldownMs = options.cooldownMs ?? 15 * 60 * 1000;
    this.now = options.now ?? Date.now;
  }

  consume(key: string): RateLimitDecision {
    const entry = this.entries.get(key);
    if (!entry) {
      return { allowed: true };
    }
    if (entry.lockedUntil > this.now()) {
      return { allowed: false, retryAfterMs: entry.lockedUntil - this.now() };
    }
    if (entry.lockedUntil !== 0 && entry.lockedUntil <= this.now()) {
      // Cooldown expired: start clean.
      this.entries.delete(key);
    }
    return { allowed: true };
  }

  recordFailure(key: string): void {
    const entry = this.entries.get(key);
    const failures = (entry?.failures ?? 0) + 1;
    const lockedUntil = failures >= this.maxFailures ? this.now() + this.cooldownMs : 0;
    this.entries.set(key, { failures, lockedUntil });
  }

  reset(key: string): void {
    this.entries.delete(key);
  }
}
