import { describe, it, expect, beforeEach } from "vitest";
import { loginRateLimiter } from "../../utils/login-rate-limiter.js";

// The limiter is a shared singleton, so each test uses unique ip/username keys
// and resets them to stay isolated.
describe("loginRateLimiter login attempts", () => {
  let ip: string;
  let username: string;
  let counter = 0;

  beforeEach(() => {
    counter += 1;
    ip = `10.0.0.${counter}`;
    username = `user${counter}`;
    loginRateLimiter.resetAttempts(ip, username);
  });

  it("starts unlocked with the full attempt budget", () => {
    expect(loginRateLimiter.isLocked(ip, username).locked).toBe(false);
    expect(loginRateLimiter.getRemainingAttempts(ip, username)).toBe(5);
  });

  it("decrements remaining attempts on each failure", () => {
    loginRateLimiter.recordFailedAttempt(ip, username);
    expect(loginRateLimiter.getRemainingAttempts(ip, username)).toBe(4);
    loginRateLimiter.recordFailedAttempt(ip, username);
    expect(loginRateLimiter.getRemainingAttempts(ip, username)).toBe(3);
  });

  it("locks the account after 5 failed attempts", () => {
    for (let i = 0; i < 5; i++) {
      loginRateLimiter.recordFailedAttempt(ip, username);
    }
    const result = loginRateLimiter.isLocked(ip, username);
    expect(result.locked).toBe(true);
    expect(result.remainingTime).toBeGreaterThan(0);
    expect(loginRateLimiter.getRemainingAttempts(ip, username)).toBe(0);
  });

  it("clears the lock and counters on reset (successful login)", () => {
    for (let i = 0; i < 5; i++) {
      loginRateLimiter.recordFailedAttempt(ip, username);
    }
    expect(loginRateLimiter.isLocked(ip, username).locked).toBe(true);

    loginRateLimiter.resetAttempts(ip, username);
    expect(loginRateLimiter.isLocked(ip, username).locked).toBe(false);
    expect(loginRateLimiter.getRemainingAttempts(ip, username)).toBe(5);
  });

  it("locks by IP even without a username", () => {
    const soloIp = `192.168.1.${counter}`;
    for (let i = 0; i < 5; i++) {
      loginRateLimiter.recordFailedAttempt(soloIp);
    }
    expect(loginRateLimiter.isLocked(soloIp).locked).toBe(true);
    loginRateLimiter.resetAttempts(soloIp);
  });
});

describe("loginRateLimiter TOTP attempts", () => {
  let userId: string;
  let counter = 0;

  beforeEach(() => {
    counter += 1;
    userId = `totp-user${counter}`;
    loginRateLimiter.resetTOTPAttempts(userId);
  });

  it("locks TOTP after 5 failures and resets cleanly", () => {
    expect(loginRateLimiter.isTOTPLocked(userId).locked).toBe(false);
    for (let i = 0; i < 5; i++) {
      loginRateLimiter.recordFailedTOTPAttempt(userId);
    }
    expect(loginRateLimiter.isTOTPLocked(userId).locked).toBe(true);
    expect(loginRateLimiter.getRemainingTOTPAttempts(userId)).toBe(0);

    loginRateLimiter.resetTOTPAttempts(userId);
    expect(loginRateLimiter.isTOTPLocked(userId).locked).toBe(false);
    expect(loginRateLimiter.getRemainingTOTPAttempts(userId)).toBe(5);
  });
});

describe("loginRateLimiter password-reset-code attempts", () => {
  let username: string;
  let counter = 0;

  beforeEach(() => {
    counter += 1;
    username = `reset-user${counter}`;
    loginRateLimiter.resetResetCodeAttempts(username);
  });

  it("locks reset codes after 5 failures and resets cleanly", () => {
    expect(loginRateLimiter.isResetCodeLocked(username).locked).toBe(false);
    for (let i = 0; i < 5; i++) {
      loginRateLimiter.recordResetCodeAttempt(username);
    }
    expect(loginRateLimiter.isResetCodeLocked(username).locked).toBe(true);

    loginRateLimiter.resetResetCodeAttempts(username);
    expect(loginRateLimiter.isResetCodeLocked(username).locked).toBe(false);
  });
});
