import assert from 'node:assert/strict';
import { test } from 'node:test';

import {
  AuthenticationRateLimiter,
  authenticateWithRateLimit,
  defaultAuthenticationRateLimits,
  normalizeAuthenticationAccount,
  normalizeAuthenticationIp
} from '../src/auth-rate-limit.js';

test('authentication rate limiter uses production thresholds and normalizes identities', () => {
  assert.deepEqual(defaultAuthenticationRateLimits, {
    windowMs: 600_000,
    blockMs: 600_000,
    combinationLimit: 10,
    accountLimit: 30,
    ipLimit: 100,
    maxEntries: 50_000,
    cleanupIntervalMs: 60_000
  });
  assert.equal(normalizeAuthenticationIp('::ffff:192.0.2.10'), '192.0.2.10');
  assert.equal(normalizeAuthenticationIp('[2001:DB8::1]'), '2001:db8::1');
  assert.equal(normalizeAuthenticationAccount('  Admin@Example.COM  '), 'admin@example.com');
});

test('authentication rate limiter blocks an IP and account combination for the configured duration', () => {
  let now = 1_000;
  const limiter = new AuthenticationRateLimiter({
    now: () => now,
    windowMs: 10_000,
    blockMs: 20_000,
    combinationLimit: 3,
    accountLimit: 10,
    ipLimit: 20
  });
  const identity = { ip: '192.0.2.1', account: 'user@example.com' };

  limiter.recordFailure(identity);
  limiter.recordFailure(identity);
  assert.equal(limiter.isBlocked(identity), false);
  assert.equal(limiter.recordFailure(identity), true);
  assert.equal(limiter.isBlocked(identity), true);

  now += 19_999;
  assert.equal(limiter.isBlocked(identity), true);
  now += 1;
  assert.equal(limiter.isBlocked(identity), false);
});

test('authentication rate limiter aggregates failures by account across IPs', () => {
  const limiter = new AuthenticationRateLimiter({
    combinationLimit: 10,
    accountLimit: 3,
    ipLimit: 20
  });
  const account = 'user@example.com';

  limiter.recordFailure({ ip: '192.0.2.1', account });
  limiter.recordFailure({ ip: '192.0.2.2', account });
  limiter.recordFailure({ ip: '192.0.2.3', account });

  assert.equal(limiter.isBlocked({ ip: '192.0.2.99', account }), true);
  assert.equal(limiter.isBlocked({ ip: '192.0.2.99', account: 'other@example.com' }), false);
});

test('authentication rate limiter aggregates failures by IP across accounts', () => {
  const limiter = new AuthenticationRateLimiter({
    combinationLimit: 10,
    accountLimit: 10,
    ipLimit: 3
  });
  const ip = '192.0.2.1';

  limiter.recordFailure({ ip, account: 'one@example.com' });
  limiter.recordFailure({ ip, account: 'two@example.com' });
  limiter.recordFailure({ ip, account: 'three@example.com' });

  assert.equal(limiter.isBlocked({ ip, account: 'four@example.com' }), true);
  assert.equal(limiter.isBlocked({ ip: '192.0.2.2', account: 'four@example.com' }), false);
});

test('successful authentication clears account records without clearing unrelated IP failures', () => {
  const limiter = new AuthenticationRateLimiter({
    combinationLimit: 5,
    accountLimit: 5,
    ipLimit: 3
  });
  const ip = '192.0.2.1';
  const account = 'user@example.com';

  limiter.recordFailure({ ip, account });
  limiter.recordFailure({ ip, account });
  const result = authenticateWithRateLimit({
    limiter,
    ip,
    account,
    authenticate: () => ({ id: 1 })
  });
  assert.deepEqual(result, { id: 1 });

  limiter.recordFailure({ ip, account: 'second@example.com' });
  assert.equal(limiter.isBlocked({ ip, account: 'third@example.com' }), true);
  assert.equal(limiter.isBlocked({ ip: '192.0.2.2', account }), false);
});

test('rate-limited authentication skips credential verification while keeping storage bounded', () => {
  let now = 1_000;
  const limiter = new AuthenticationRateLimiter({
    now: () => now,
    windowMs: 100,
    blockMs: 100,
    combinationLimit: 1,
    accountLimit: 100,
    ipLimit: 100,
    maxEntries: 5,
    cleanupIntervalMs: 1
  });
  let verifications = 0;

  assert.equal(authenticateWithRateLimit({
    limiter,
    ip: '192.0.2.1',
    account: 'blocked@example.com',
    authenticate: () => {
      verifications += 1;
      return null;
    }
  }), null);
  assert.equal(authenticateWithRateLimit({
    limiter,
    ip: '192.0.2.1',
    account: 'blocked@example.com',
    authenticate: () => {
      verifications += 1;
      return { id: 1 };
    }
  }), null);
  assert.equal(verifications, 1);

  for (let index = 0; index < 20; index += 1) {
    limiter.recordFailure({ ip: `198.51.100.${index}`, account: `user-${index}@example.com` });
    assert.ok(limiter.entryCount <= 5);
  }

  now += 201;
  limiter.isBlocked({ ip: '203.0.113.1', account: 'fresh@example.com' });
  assert.equal(limiter.entryCount, 0);
});
