import { describe, expect, it } from 'vitest';
import { InMemoryLoginRateLimiter } from '../application/login-rate-limiter.js';

describe('InMemoryLoginRateLimiter', () => {
  it('allows attempts below the failure threshold', () => {
    const limiter = new InMemoryLoginRateLimiter({ maxFailures: 3 });
    expect(limiter.consume('a@b.c')).toEqual({ allowed: true });
    limiter.recordFailure('a@b.c');
    limiter.recordFailure('a@b.c');
    expect(limiter.consume('a@b.c')).toEqual({ allowed: true });
  });

  it('blocks with retryAfterMs after reaching the threshold', () => {
    let now = 1_000_000;
    const limiter = new InMemoryLoginRateLimiter({
      maxFailures: 10,
      cooldownMs: 900_000,
      now: () => now,
    });

    for (let i = 0; i < 10; i += 1) {
      expect(limiter.consume('a@b.c').allowed).toBe(true);
      limiter.recordFailure('a@b.c');
    }

    const blocked = limiter.consume('a@b.c');
    expect(blocked.allowed).toBe(false);
    if (!blocked.allowed) {
      expect(blocked.retryAfterMs).toBe(900_000);
    }

    // Time passes but not enough.
    now += 60_000;
    expect(limiter.consume('a@b.c').allowed).toBe(false);
  });

  it('re-allows attempts once the cooldown expires', () => {
    let now = 1_000_000;
    const limiter = new InMemoryLoginRateLimiter({
      maxFailures: 2,
      cooldownMs: 1_000,
      now: () => now,
    });

    limiter.recordFailure('a@b.c');
    limiter.recordFailure('a@b.c');
    expect(limiter.consume('a@b.c').allowed).toBe(false);

    now += 1_001;
    expect(limiter.consume('a@b.c')).toEqual({ allowed: true });
  });

  it('resets the counter on successful login', () => {
    const limiter = new InMemoryLoginRateLimiter({ maxFailures: 2 });
    limiter.recordFailure('a@b.c');
    limiter.reset('a@b.c');
    limiter.recordFailure('a@b.c');
    // Only one failure since reset: still allowed.
    expect(limiter.consume('a@b.c')).toEqual({ allowed: true });
  });

  it('tracks keys independently', () => {
    const limiter = new InMemoryLoginRateLimiter({ maxFailures: 1 });
    limiter.recordFailure('a@b.c');
    expect(limiter.consume('a@b.c').allowed).toBe(false);
    expect(limiter.consume('other@b.c')).toEqual({ allowed: true });
  });
});
