import { describe, it, expect } from 'vitest';
import { checkLock, recordFail, recordSuccess, getClientIp } from './loginLimiter.js';

// Helper: mock a Next.js request with headers
function mockRequest(headers = {}) {
  return { headers: new Map(Object.entries(headers)) };
}

describe('loginLimiter', () => {
  describe('getClientIp', () => {
    it('returns x-forwarded-for when present', () => {
      const req = mockRequest({ 'x-forwarded-for': '1.2.3.4, 5.6.7.8' });
      expect(getClientIp(req)).toBe('1.2.3.4');
    });

    it('falls back to x-real-ip', () => {
      const req = mockRequest({ 'x-real-ip': '9.9.9.9' });
      expect(getClientIp(req)).toBe('9.9.9.9');
    });

    it('returns "unknown" when no headers', () => {
      const req = mockRequest({});
      expect(getClientIp(req)).toBe('unknown');
    });
  });

  describe('checkLock', () => {
    it('returns not locked when no attempts recorded', () => {
      expect(checkLock('10.0.0.1')).toEqual({ locked: false });
    });

    it('remains not locked after few failures', () => {
      recordFail('10.0.0.2');
      recordFail('10.0.0.2');
      expect(checkLock('10.0.0.2')).toEqual({ locked: false });
      recordSuccess('10.0.0.2');
    });

    it('locks after MAX_FAILS (5) failures', () => {
      const ip = '10.0.0.3';
      for (let i = 0; i < 5; i++) recordFail(ip);
      const lock = checkLock(ip);
      expect(lock.locked).toBe(true);
      expect(lock.retryAfter).toBeGreaterThan(0);
      expect(lock.retryAfter).toBeLessThanOrEqual(30);
      recordSuccess(ip);
    });

    it('returns retryAfter in seconds (not milliseconds)', () => {
      const ip = '10.0.0.4';
      for (let i = 0; i < 5; i++) recordFail(ip);
      const lock = checkLock(ip);
      expect(lock.retryAfter).toBeTypeOf('number');
      expect(lock.retryAfter).toBeGreaterThan(0);
      expect(lock.retryAfter).toBeLessThanOrEqual(30);
      recordSuccess(ip);
    });
  });

  describe('recordFail', () => {
    it('returns remainingBeforeLock count', () => {
      const ip = '10.0.0.5';
      const result = recordFail(ip);
      expect(result.remainingBeforeLock).toBe(4);
      recordSuccess(ip);
    });

    it('counts down remaining before lock, resets to 5 on lock', () => {
      const ip = '10.0.0.6';
      expect(recordFail(ip).remainingBeforeLock).toBe(4);
      expect(recordFail(ip).remainingBeforeLock).toBe(3);
      expect(recordFail(ip).remainingBeforeLock).toBe(2);
      expect(recordFail(ip).remainingBeforeLock).toBe(1);
      // 5th fail triggers lock → fails resets to 0 → remaining becomes 5
      const fifth = recordFail(ip);
      expect(fifth.remainingBeforeLock).toBe(5);
      expect(checkLock(ip).locked).toBe(true);
      recordSuccess(ip);
    });

    it('resets fails count after lock triggers', () => {
      const ip = '10.0.0.7';
      for (let i = 0; i < 5; i++) recordFail(ip);
      // After lock, fails should reset
      expect(checkLock(ip).locked).toBe(true);
      // After lock expires (simulate), next fail starts fresh
      recordSuccess(ip);
    });
  });

  describe('recordSuccess', () => {
    it('clears attempts for IP', () => {
      const ip = '10.0.0.8';
      recordFail(ip);
      recordFail(ip);
      recordSuccess(ip);
      expect(checkLock(ip)).toEqual({ locked: false });
    });
  });

  describe('progressive lockout', () => {
    it('increases lock level on repeated lockouts', () => {
      const ip = '10.0.0.9';
      // 1st lockout: 5 fails → 30s
      for (let i = 0; i < 5; i++) recordFail(ip);
      const lock1 = checkLock(ip);
      expect(lock1.locked).toBe(true);
      recordSuccess(ip);

      // 2nd lockout: another 5 fails → 2min
      for (let i = 0; i < 5; i++) recordFail(ip);
      const lock2 = checkLock(ip);
      expect(lock2.locked).toBe(true);
      recordSuccess(ip);

      // lock2 should have longer duration than lock1 (progressive)
      // We can't easily assert exact values without time mocking, but the level increments
    });
  });

  describe('independent IPs', () => {
    it('tracks different IPs independently', () => {
      recordFail('10.0.0.10');
      recordFail('10.0.0.10');
      expect(checkLock('10.0.0.11')).toEqual({ locked: false });
      expect(checkLock('10.0.0.10').locked).toBe(false);
      recordSuccess('10.0.0.10');
    });
  });
});
