import { describe, it, expect } from 'vitest'
import {
  policyForPlan,
  PRE_AUTH_POLICY,
  checkPreAuthRate,
  checkConnectRate,
  checkConcurrentSessions,
  checkEnrollRate,
} from '../src/ratelimit/quota.js'
import { fakeCountingBucket } from './_helpers.js'

const NOW = 1_700_000_000

describe('rate-limits / quotas (T11)', () => {
  it('pre-auth throttle (Finding-5): one IP hammering many subdomains is throttled, no account', async () => {
    const bucket = fakeCountingBucket()
    const ip = 'ip-hash-1'
    let allowed = 0
    for (let i = 0; i < PRE_AUTH_POLICY.preAuthPerMinPerIp + 5; i++) {
      if (await checkPreAuthRate(ip, bucket, NOW)) allowed++
    }
    expect(allowed).toBe(PRE_AUTH_POLICY.preAuthPerMinPerIp) // burst then throttled
  })

  it('pre-auth throttle refills after its window', async () => {
    const bucket = fakeCountingBucket()
    const ip = 'ip-hash-2'
    for (let i = 0; i < PRE_AUTH_POLICY.preAuthPerMinPerIp; i++) await checkPreAuthRate(ip, bucket, NOW)
    expect(await checkPreAuthRate(ip, bucket, NOW)).toBe(false)
    expect(await checkPreAuthRate(ip, bucket, NOW + 120)).toBe(true) // refilled
  })

  it('per-account connect rate: over connectPerMin → false, refills after window', async () => {
    const bucket = fakeCountingBucket()
    const policy = policyForPlan('free')
    let allowed = 0
    for (let i = 0; i < policy.connectPerMin + 3; i++) {
      if (await checkConnectRate('acct-A', policy, bucket, NOW)) allowed++
    }
    expect(allowed).toBe(policy.connectPerMin)
    expect(await checkConnectRate('acct-A', policy, bucket, NOW + 120)).toBe(true)
  })

  it('per-account limits are per accountId — A hitting its limit does not affect B', async () => {
    const bucket = fakeCountingBucket()
    const policy = policyForPlan('free')
    for (let i = 0; i < policy.connectPerMin + 5; i++) await checkConnectRate('acct-A', policy, bucket, NOW)
    expect(await checkConnectRate('acct-A', policy, bucket, NOW)).toBe(false)
    expect(await checkConnectRate('acct-B', policy, bucket, NOW)).toBe(true) // B unaffected
  })

  it('cross-key isolation: pre-auth IP throttle and account quota use disjoint namespaces', async () => {
    const bucket = fakeCountingBucket()
    const policy = policyForPlan('free')
    // Exhaust the pre-auth bucket for an IP hash that equals an accountId string.
    for (let i = 0; i < PRE_AUTH_POLICY.preAuthPerMinPerIp; i++) await checkPreAuthRate('collide', bucket, NOW)
    expect(await checkPreAuthRate('collide', bucket, NOW)).toBe(false)
    // The account quota for the same string is a different key → still allowed.
    expect(await checkConnectRate('collide', policy, bucket, NOW)).toBe(true)
  })

  it('concurrent sessions over the cap → denied', () => {
    const policy = policyForPlan('free')
    expect(checkConcurrentSessions('acct-A', policy.maxConcurrentSessions - 1, policy)).toBe(true)
    expect(checkConcurrentSessions('acct-A', policy.maxConcurrentSessions, policy)).toBe(false)
  })

  it('enroll spam over enrollPerHour → denied', async () => {
    const bucket = fakeCountingBucket()
    const policy = policyForPlan('free')
    let allowed = 0
    for (let i = 0; i < policy.enrollPerHour + 2; i++) {
      if (await checkEnrollRate('acct-A', policy, bucket, NOW)) allowed++
    }
    expect(allowed).toBe(policy.enrollPerHour)
  })
})
