import test from 'node:test'
import assert from 'node:assert/strict'
import type { NextFunction, Request, Response } from 'express'

import { createRateLimitMiddleware, resetRateLimitBucketsForTest } from './rate-limit.js'

test('createRateLimitMiddleware allows requests within the window', () => {
  resetRateLimitBucketsForTest()

  const limiter = createRateLimitMiddleware({
    scope: 'test:allow',
    windowMs: 60_000,
    max: 2,
  })
  const req = createMockRequest()
  const res = createMockResponse()
  let nextCount = 0
  const next: NextFunction = () => {
    nextCount += 1
  }

  limiter(req, res, next)
  limiter(req, res, next)

  assert.equal(nextCount, 2)
  assert.equal(res.statusCode, 200)
})

test('createRateLimitMiddleware blocks requests over the limit', () => {
  resetRateLimitBucketsForTest()

  const limiter = createRateLimitMiddleware({
    scope: 'test:block',
    windowMs: 60_000,
    max: 1,
  })
  const req = createMockRequest()
  const res = createMockResponse()
  let nextCount = 0
  const next: NextFunction = () => {
    nextCount += 1
  }

  limiter(req, res, next)
  limiter(req, res, next)

  assert.equal(nextCount, 1)
  assert.equal(res.statusCode, 429)
  assert.equal(res.headers['Retry-After'], '60')
  assert.equal(res.body.errorCode, 'rate_limited')
})

test('createRateLimitMiddleware supports custom limit responses', () => {
  resetRateLimitBucketsForTest()

  const limiter = createRateLimitMiddleware({
    scope: 'test:custom',
    windowMs: 60_000,
    max: 1,
    onLimit: (_req, res) => {
      res.status(200).json({ code: 429, message: 'limited' })
    },
  })
  const req = createMockRequest()
  const res = createMockResponse()

  limiter(req, res, () => {})
  limiter(req, res, () => {})

  assert.equal(res.statusCode, 200)
  assert.deepEqual(res.body, { code: 429, message: 'limited' })
})

test('createRateLimitMiddleware can cap an IP across different accounts', () => {
  resetRateLimitBucketsForTest()

  const limiter = createRateLimitMiddleware({
    scope: 'test:ip-wide',
    windowMs: 60_000,
    max: 2,
  })
  const first = createMockRequest()
  const second = createMockRequest()
  const third = createMockRequest()
  const response = createMockResponse()
  let nextCount = 0
  const next: NextFunction = () => {
    nextCount += 1
  }

  limiter(first, response, next)
  limiter(second, response, next)
  limiter(third, response, next)

  assert.equal(nextCount, 2)
  assert.equal(response.statusCode, 429)
})

function createMockRequest(): Request {
  return {
    ip: '127.0.0.1',
    originalUrl: '/test',
    headers: {},
    socket: {
      remoteAddress: '127.0.0.1',
    },
  } as Request
}

function createMockResponse(): Response & {
  statusCode: number
  headers: Record<string, string>
  body: any
} {
  const res = {
    statusCode: 200,
    headers: {} as Record<string, string>,
    body: null as any,
    setHeader(name: string, value: string) {
      this.headers[name] = value
      return this
    },
    status(statusCode: number) {
      this.statusCode = statusCode
      return this
    },
    json(body: any) {
      this.body = body
      return this
    },
  }

  return res as Response & {
    statusCode: number
    headers: Record<string, string>
    body: any
  }
}
