import { describe, expect, it } from 'vitest';
import { describeWait, LOGIN_MAX_FAILURES, LOGIN_WINDOW_MS, LoginThrottle, throttleKeys } from './login-throttle';

describe('LoginThrottle', () => {
  it('blocks a key after 10 failures in 15 minutes, even for the right password', () => {
    const t = new LoginThrottle();
    const keys = throttleKeys('host', '203.0.113.7', 'Owner@Bar.test');
    for (let i = 0; i < LOGIN_MAX_FAILURES; i++) {
      expect(t.check(keys, i * 1000).allowed).toBe(true);
      t.recordFailure(keys, i * 1000);
    }
    const blocked = t.check(keys, 10_000);
    expect(blocked.allowed).toBe(false);
    expect(blocked.retryAfterMs).toBe(LOGIN_WINDOW_MS - 1000);
  });

  it('unblocks once the window has passed', () => {
    const t = new LoginThrottle({ maxFailures: 2, windowMs: 60_000 });
    const keys = ['k'];
    t.recordFailure(keys, 0);
    t.recordFailure(keys, 1_000);
    expect(t.check(keys, 30_000).allowed).toBe(false);
    expect(t.check(keys, 61_001).allowed).toBe(true);
  });

  it('counts per e-mail across IPs and per IP across e-mails', () => {
    const t = new LoginThrottle({ maxFailures: 3, windowMs: 60_000 });
    // Spraying one account from many addresses trips the e-mail key.
    for (let i = 0; i < 3; i++) t.recordFailure(throttleKeys('admin', `198.51.100.${i}`, 'admin@x.test'), i);
    expect(t.check(throttleKeys('admin', '198.51.100.99', 'ADMIN@x.test'), 10).allowed).toBe(false);
    // One address trying many accounts trips the IP key.
    for (let i = 0; i < 3; i++) t.recordFailure(throttleKeys('admin', '192.0.2.1', `u${i}@x.test`), i);
    expect(t.check(throttleKeys('admin', '192.0.2.1', 'fresh@x.test'), 10).allowed).toBe(false);
    // Host and admin counters are separate.
    expect(t.check(throttleKeys('host', '192.0.2.1', 'admin@x.test'), 10).allowed).toBe(true);
  });

  it('does not count checks or successes', () => {
    const t = new LoginThrottle({ maxFailures: 1, windowMs: 60_000 });
    for (let i = 0; i < 50; i++) expect(t.check(['k'], i).allowed).toBe(true);
  });

  it('describes waits in minutes', () => {
    expect(describeWait(10_000)).toBe('about a minute');
    expect(describeWait(14 * 60_000 + 1)).toBe('about 15 minutes');
  });
});
