/**
 * Rate Limiting Tests
 * 
 * Tests for the rate limiting functionality in the UnifiedStorageManager.
 */

import { createUnifiedStorageManager } from '../../src/storage/UnifiedStorageManagerImpl';
import { StorageConfig, StorageTier } from '../../src/types';
import { MockProvider, MockProviderState } from '../mocks/mock-provider';

// Mock providers
const mockRedisProvider = new MockProvider();
const mockLocalFileProvider = {
  initialize: jest.fn().mockResolvedValue(undefined),
  close: jest.fn().mockResolvedValue(undefined),
  store: jest.fn().mockImplementation((data, metadata) => {
    return Promise.resolve(`local-${Date.now()}`);
  }),
  retrieve: jest.fn().mockImplementation((query) => {
    return Promise.resolve([
      {
        data: { value: 42 },
        metadata: {
          id: query.id || 'test-id',
          category: 'test-category',
          timestamp: new Date().toISOString(),
          tags: {},
          tier: StorageTier.HOT
        }
      }
    ]);
  }),
  update: jest.fn().mockResolvedValue(undefined),
  delete: jest.fn().mockResolvedValue(undefined),
  cleanup: jest.fn().mockResolvedValue({
    itemsRemoved: 10,
    bytesFreed: 1024,
    duration: 100
  }),
  getStatus: jest.fn().mockResolvedValue({
    connected: true,
    healthy: true,
    metrics: {
      operationsPerSecond: 100,
      averageLatency: 5,
      errorRate: 0,
      storageUsed: 1024 * 1024,
      storageAvailable: 1024 * 1024 * 100
    }
  }),
  getLocalStorageStatus: jest.fn().mockResolvedValue({
    available: true,
    healthy: true,
    totalSizeBytes: 1024 * 1024 * 100,
    usedSizeBytes: 1024 * 1024,
    freeSizeBytes: 1024 * 1024 * 99,
    itemCount: 100
  }),
  clearAll: jest.fn().mockResolvedValue({
    success: true,
    itemsRemoved: 100,
    bytesFreed: 1024 * 1024,
    durationMs: 100
  })
};

// Mock the local file provider
jest.mock('../../src/storage/providers/local-file', () => {
  return {
    createLocalFileProvider: jest.fn(() => mockLocalFileProvider)
  };
});

// Mock the StorageManagerImpl class
jest.mock('../../src/storage/manager', () => {
  return {
    StorageManagerImpl: jest.fn().mockImplementation(() => {
      return {
        initialize: jest.fn().mockResolvedValue(undefined),
        close: jest.fn().mockResolvedValue(undefined),
        store: jest.fn().mockImplementation((data, category) => {
          return Promise.resolve(`mock-${Date.now()}`);
        }),
        retrieve: jest.fn().mockResolvedValue([]),
        update: jest.fn().mockResolvedValue(undefined),
        delete: jest.fn().mockResolvedValue(undefined),
        storeBatch: jest.fn().mockResolvedValue([]),
        retrieveBatch: jest.fn().mockResolvedValue([]),
        cleanup: jest.fn().mockResolvedValue({
          itemsRemoved: 0,
          bytesFreed: 0,
          duration: 0
        }),
        migrate: jest.fn().mockResolvedValue(undefined),
        getStatus: jest.fn().mockResolvedValue({
          connected: true,
          healthy: true,
          queryPerformance: {
            hot: { averageQueryTime: 0, queriesPerSecond: 0 },
            warm: { averageQueryTime: 0, queriesPerSecond: 0 },
            cold: { averageQueryTime: 0, queriesPerSecond: 0 },
            overall: 'healthy',
            providers: {
              hot: 'healthy',
              warm: 'healthy',
              cold: 'healthy'
            }
          }
        })
      };
    })
  };
});

describe('Rate Limiting Tests', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    jest.useFakeTimers();
  });
  
  afterEach(() => {
    jest.useRealTimers();
  });
  
  const createStorageConfig = (): StorageConfig => ({
    providers: {
      hot: {
        type: 'redis',
        config: {
          host: 'localhost',
          port: 6379
        }
      }
    }
  });
  
  it('should apply rate limiting to operations', async () => {
    // Arrange
    const storageManager = createUnifiedStorageManager(createStorageConfig(), {
      resilience: {
        rateLimit: {
          defaultTokensPerSecond: 2, // 2 operations per second
          defaultBucketSize: 2, // Allow bursts of up to 2 operations
          waitWhenLimited: false, // Don't wait when rate limited
          maxWaitTimeMs: 1000
        }
      }
    });
    
    await storageManager.initialize();
    
    // Act & Assert
    // First two operations should succeed (using the initial bucket tokens)
    await expect(storageManager.store({ value: 1 }, 'test-category')).resolves.toBeDefined();
    await expect(storageManager.store({ value: 2 }, 'test-category')).resolves.toBeDefined();
    
    // Third operation should fail due to rate limiting
    await expect(storageManager.store({ value: 3 }, 'test-category')).rejects.toThrow('Rate limit exceeded');
    
    // Check metrics
    const metrics = storageManager.getResilienceMetrics();
    expect(metrics.rateLimit.limitedCount['store']).toBe(1);
    expect(metrics.rateLimit.currentTokens['store']).toBeLessThan(1);
    
    // Advance time to replenish tokens
    jest.advanceTimersByTime(1000); // Advance 1 second
    
    // Now the operation should succeed
    await expect(storageManager.store({ value: 4 }, 'test-category')).resolves.toBeDefined();
  });
  
  it('should wait when rate limited if waitWhenLimited is true', async () => {
    // Arrange
    const storageManager = createUnifiedStorageManager(createStorageConfig(), {
      resilience: {
        rateLimit: {
          defaultTokensPerSecond: 2, // 2 operations per second
          defaultBucketSize: 2, // Allow bursts of up to 2 operations
          waitWhenLimited: true, // Wait when rate limited
          maxWaitTimeMs: 2000
        }
      }
    });
    
    await storageManager.initialize();
    
    // Act & Assert
    // First two operations should succeed immediately
    const start = Date.now();
    await storageManager.store({ value: 1 }, 'test-category');
    await storageManager.store({ value: 2 }, 'test-category');
    
    // Third operation should wait for a token
    const storePromise = storageManager.store({ value: 3 }, 'test-category');
    
    // Advance time to replenish tokens
    jest.advanceTimersByTime(500); // Advance 0.5 seconds (should replenish 1 token)
    
    // Wait for the operation to complete
    await storePromise;
    
    // Check metrics
    const metrics = storageManager.getResilienceMetrics();
    expect(metrics.rateLimit.waitTimeMs['store']).toBeGreaterThan(0);
  });
  
  it('should bypass rate limiting for specified operations', async () => {
    // Arrange
    const storageManager = createUnifiedStorageManager(createStorageConfig(), {
      resilience: {
        rateLimit: {
          defaultTokensPerSecond: 2, // 2 operations per second
          defaultBucketSize: 2, // Allow bursts of up to 2 operations
          waitWhenLimited: false, // Don't wait when rate limited
          maxWaitTimeMs: 1000,
          bypassOperations: ['retrieve'] // Bypass rate limiting for retrieve operations
        }
      }
    });
    
    await storageManager.initialize();
    
    // Act & Assert
    // First two store operations should succeed
    await expect(storageManager.store({ value: 1 }, 'test-category')).resolves.toBeDefined();
    await expect(storageManager.store({ value: 2 }, 'test-category')).resolves.toBeDefined();
    
    // Third store operation should fail due to rate limiting
    await expect(storageManager.store({ value: 3 }, 'test-category')).rejects.toThrow('Rate limit exceeded');
    
    // But retrieve operations should bypass rate limiting
    await expect(storageManager.retrieve({ category: 'test-category' })).resolves.toBeDefined();
    await expect(storageManager.retrieve({ category: 'test-category' })).resolves.toBeDefined();
    await expect(storageManager.retrieve({ category: 'test-category' })).resolves.toBeDefined();
    
    // Check metrics
    const metrics = storageManager.getResilienceMetrics();
    expect(metrics.rateLimit.bypassCount['retrieve']).toBe(3);
  });
  
  it('should apply operation-specific rate limits', async () => {
    // Arrange
    const storageManager = createUnifiedStorageManager(createStorageConfig(), {
      resilience: {
        rateLimit: {
          defaultTokensPerSecond: 2, // 2 operations per second
          defaultBucketSize: 2, // Allow bursts of up to 2 operations
          waitWhenLimited: false, // Don't wait when rate limited
          maxWaitTimeMs: 1000,
          operationLimits: {
            store: {
              tokensPerSecond: 1, // 1 operation per second for store
              bucketSize: 1 // Allow bursts of up to 1 operation for store
            }
          }
        }
      }
    });
    
    await storageManager.initialize();
    
    // Act & Assert
    // First store operation should succeed
    await expect(storageManager.store({ value: 1 }, 'test-category')).resolves.toBeDefined();
    
    // Second store operation should fail due to operation-specific rate limiting
    await expect(storageManager.store({ value: 2 }, 'test-category')).rejects.toThrow('Rate limit exceeded');
    
    // But retrieve operations should use the default rate limit
    await expect(storageManager.retrieve({ category: 'test-category' })).resolves.toBeDefined();
    await expect(storageManager.retrieve({ category: 'test-category' })).resolves.toBeDefined();
    
    // Third retrieve operation should fail due to default rate limiting
    await expect(storageManager.retrieve({ category: 'test-category' })).rejects.toThrow('Rate limit exceeded');
  });
});
