import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

// Mock the config/env module
vi.mock('./config/env', () => ({
  assertKvConfiguredInProduction: vi.fn(),
  isKvConfigured: vi.fn(() => false),
  isProductionEnvironment: vi.fn(() => false),
}));

describe('rate-limit', () => {
  const originalEnv = process.env;
  const mockNow = 1234567890000;

  beforeEach(() => {
    vi.resetModules();
    vi.clearAllMocks();
    process.env = { ...originalEnv };
    vi.spyOn(Date, 'now').mockReturnValue(mockNow);
  });

  afterEach(() => {
    process.env = originalEnv;
    vi.restoreAllMocks();
  });

  describe('checkRateLimit - Development Mode', () => {
    it('should allow first request from a new identifier', async () => {
      const { checkRateLimit } = await import('./rate-limit');
      const result = await checkRateLimit('test-ip-1');

      expect(result.allowed).toBe(true);
      expect(result.remaining).toBe(4); // 5 total - 1 used
      expect(result.resetAt).toBe(mockNow + 60 * 60 * 1000); // 1 hour
    });

    it('should increment count for subsequent requests within window', async () => {
      const { checkRateLimit } = await import('./rate-limit');
      await checkRateLimit('test-ip-2');
      const result = await checkRateLimit('test-ip-2');

      expect(result.allowed).toBe(true);
      expect(result.remaining).toBe(3); // 5 total - 2 used
    });

    it('should block after 5 requests', async () => {
      const { checkRateLimit } = await import('./rate-limit');
      // Make 5 requests
      for (let i = 0; i < 5; i++) {
        await checkRateLimit('test-ip-3');
      }

      const result = await checkRateLimit('test-ip-3');

      expect(result.allowed).toBe(false);
      expect(result.remaining).toBe(0);
    });

    it('should reset after window expires', async () => {
      const { checkRateLimit } = await import('./rate-limit');
      // First request
      await checkRateLimit('test-ip-4');
      
      // Simulate time passing beyond window
      const futureTime = mockNow + 60 * 60 * 1000 + 1;
      vi.spyOn(Date, 'now').mockReturnValue(futureTime);

      const result = await checkRateLimit('test-ip-4');

      expect(result.allowed).toBe(true);
      expect(result.remaining).toBe(4); // Reset to 5 - 1
    });

    it('should track different identifiers separately', async () => {
      const { checkRateLimit } = await import('./rate-limit');
      await checkRateLimit('ip-1');
      await checkRateLimit('ip-1');
      
      const result = await checkRateLimit('ip-2');

      expect(result.allowed).toBe(true);
      expect(result.remaining).toBe(4); // ip-2 is at 1, ip-1 is at 2
    });
  });

  describe('getClientIp', () => {
    it('should extract IP from x-forwarded-for header', async () => {
      const { getClientIp } = await import('./rate-limit');
      const mockRequest = {
        headers: new Headers({
          'x-forwarded-for': '192.168.1.1, 10.0.0.1',
        }),
      } as Request;

      const ip = getClientIp(mockRequest);
      expect(ip).toBe('192.168.1.1');
    });

    it('should extract IP from x-real-ip header', async () => {
      const { getClientIp } = await import('./rate-limit');
      const mockRequest = {
        headers: new Headers({
          'x-real-ip': '10.0.0.2',
        }),
      } as Request;

      const ip = getClientIp(mockRequest);
      expect(ip).toBe('10.0.0.2');
    });

    it('should extract IP from cf-connecting-ip header', async () => {
      const { getClientIp } = await import('./rate-limit');
      const mockRequest = {
        headers: new Headers({
          'cf-connecting-ip': '1.2.3.4',
        }),
      } as Request;

      const ip = getClientIp(mockRequest);
      expect(ip).toBe('1.2.3.4');
    });

    it('should prioritize x-forwarded-for over other headers', async () => {
      const { getClientIp } = await import('./rate-limit');
      const mockRequest = {
        headers: new Headers({
          'x-forwarded-for': '192.168.1.1',
          'x-real-ip': '10.0.0.2',
          'cf-connecting-ip': '1.2.3.4',
        }),
      } as Request;

      const ip = getClientIp(mockRequest);
      expect(ip).toBe('192.168.1.1');
    });

    it('should return "unknown" when no IP headers present', async () => {
      const { getClientIp } = await import('./rate-limit');
      const mockRequest = {
        headers: new Headers({}),
      } as Request;

      const ip = getClientIp(mockRequest);
      expect(ip).toBe('unknown');
    });

    it('should handle x-forwarded-for with single IP', async () => {
      const { getClientIp } = await import('./rate-limit');
      const mockRequest = {
        headers: new Headers({
          'x-forwarded-for': '203.0.113.1',
        }),
      } as Request;

      const ip = getClientIp(mockRequest);
      expect(ip).toBe('203.0.113.1');
    });

    it('should trim whitespace from IP', async () => {
      const { getClientIp } = await import('./rate-limit');
      const mockRequest = {
        headers: new Headers({
          'x-forwarded-for': '  192.168.1.1  ',
        }),
      } as Request;

      const ip = getClientIp(mockRequest);
      expect(ip).toBe('192.168.1.1');
    });
  });
});