/**
 * Per-(userId,ip) + per-userId 2FA verification counter tests.
 *
 * audit3-followup (2026-05-29): MySQL-backed via BruteForceCounter
 * with two buckets ('twofa-ip' and 'twofa-user'). Prisma mocked
 * in-memory via _prisma-mock.ts. Original assertions unchanged.
 */
import { bruteForceCounterMock, _resetMockStore } from './_prisma-mock';

jest.mock('@/lib/prisma', () => ({
    prisma: { bruteForceCounter: bruteForceCounterMock },
}));

import { isLocked, recordFailure, recordSuccess } from '../twofa-attempts';

const CFG = { maxFailures: 5, windowMs: 60_000, lockoutMs: 60_000 };

beforeEach(() => _resetMockStore());

describe('twofa-attempts', () => {
    it('starts unlocked', async () => {
        await expect(isLocked(42, '1.2.3.4', CFG).then((r) => r.locked)).resolves.toBe(false);
    });

    it('locks after MAX_FAILURES on same IP+userId', async () => {
        for (let i = 0; i < 5; i++) await recordFailure(42, '1.2.3.4', CFG);
        const status = await isLocked(42, '1.2.3.4', CFG);
        expect(status.locked).toBe(true);
        expect(status.retryAfterSeconds).toBeGreaterThan(0);
    });

    it('locks userId even when IP rotates per request (distributed brute force)', async () => {
        for (let i = 0; i < 5; i++) await recordFailure(42, `1.2.3.${i}`, CFG);
        // No single IP has 5 failures, but userId-keyed store does.
        const status = await isLocked(42, '9.9.9.9', CFG);
        expect(status.locked).toBe(true);
    });

    it('successful verify clears the userId counter', async () => {
        for (let i = 0; i < 4; i++) await recordFailure(42, '1.2.3.4', CFG);
        await recordSuccess(42, '1.2.3.4');
        await expect(isLocked(42, '1.2.3.4', CFG).then((r) => r.locked)).resolves.toBe(false);
        // And clears other-IP entries for that user.
        for (let i = 0; i < 4; i++) await recordFailure(42, `5.6.7.${i}`, CFG);
        await recordSuccess(42);
        await expect(isLocked(42, '9.9.9.9', CFG).then((r) => r.locked)).resolves.toBe(false);
    });

    it('different users are independent', async () => {
        for (let i = 0; i < 5; i++) await recordFailure(42, '1.2.3.4', CFG);
        await expect(isLocked(42, '1.2.3.4', CFG).then((r) => r.locked)).resolves.toBe(true);
        await expect(isLocked(99, '1.2.3.4', CFG).then((r) => r.locked)).resolves.toBe(false);
    });
});
