/**
 * AUDIT-2 #1: cleanup must respect each record's own windowMs.
 *
 * Previously the module-level setInterval deleted any record older
 * than a hardcoded 60s, which silently shortened every windowMs >
 * 60s. The login limiter (5 attempts per 1h, middleware.ts:226)
 * effectively ran as 5-per-60s — a working brute-force bypass.
 *
 * These tests use the module's exported _cleanupExpiredRecords +
 * _resetStoreForTests helpers and Jest fake timers to drive
 * Date.now without waiting real seconds.
 */
import { NextRequest } from 'next/server';
import { rateLimiter, _cleanupExpiredRecords, _resetStoreForTests } from '../rate-limiter';

function mockReq(pathname = '/api/auth/callback/credentials'): NextRequest {
    return {
        headers: {
            get: () => null,
        },
        nextUrl: { pathname },
    } as unknown as NextRequest;
}

beforeEach(() => {
    _resetStoreForTests();
    jest.useFakeTimers({ doNotFake: ['setInterval', 'clearInterval'] });
    jest.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
});

afterEach(() => {
    jest.useRealTimers();
});

describe('rate-limiter cleanup honours per-record windowMs', () => {
    it('does NOT delete a 1-hour record after 60s have elapsed', () => {
        const req = mockReq('/api/auth/callback/credentials');
        // Create a record with the login-window config (5 per 1 hour).
        expect(rateLimiter(req, { limit: 5, windowMs: 60 * 60 * 1000 })).toBeNull();

        // Advance 120 seconds (past the old hardcoded 60s cleanup threshold).
        jest.advanceTimersByTime(120_000);
        _cleanupExpiredRecords();

        // The bucket must still exist — a fresh probe should count as
        // attempt 2, not attempt 1. We verify by exhausting the limit:
        // 4 more calls should each succeed and the 5th should 429.
        for (let i = 0; i < 4; i++) {
            expect(rateLimiter(req, { limit: 5, windowMs: 60 * 60 * 1000 })).toBeNull();
        }
        const rejected = rateLimiter(req, { limit: 5, windowMs: 60 * 60 * 1000 });
        expect(rejected).not.toBeNull();
        expect(rejected?.status).toBe(429);
    });

    it('DOES delete a record once its own windowMs has elapsed', () => {
        const req = mockReq('/api/x');
        // 10-second window, 1-attempt limit.
        expect(rateLimiter(req, { limit: 1, windowMs: 10_000 })).toBeNull();

        // Immediately exhausted.
        expect(rateLimiter(req, { limit: 1, windowMs: 10_000 })?.status).toBe(429);

        // Advance past the window + run cleanup.
        jest.advanceTimersByTime(11_000);
        _cleanupExpiredRecords();

        // Fresh bucket — next call should succeed (count starts at 1 again).
        expect(rateLimiter(req, { limit: 1, windowMs: 10_000 })).toBeNull();
    });

    it('does not cross-contaminate records with different windowMs', () => {
        const req1 = mockReq('/short-window');
        const req2 = mockReq('/long-window');

        expect(rateLimiter(req1, { limit: 1, windowMs: 10_000 })).toBeNull();
        expect(rateLimiter(req2, { limit: 1, windowMs: 3_600_000 })).toBeNull();

        // Advance 60s + cleanup: short-window expired, long-window persists.
        jest.advanceTimersByTime(60_000);
        _cleanupExpiredRecords();

        // Short-window: fresh bucket — succeeds again.
        expect(rateLimiter(req1, { limit: 1, windowMs: 10_000 })).toBeNull();
        // Long-window: still in window — count=2, exceeds limit=1, 429.
        expect(rateLimiter(req2, { limit: 1, windowMs: 3_600_000 })?.status).toBe(429);
    });
});
