import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { RetryHandler, CircuitBreaker, CircuitBreakerRegistry, DEFAULT_RETRY_CONFIG } from './RetryHandler';

describe('RetryHandler', () => {
  let handler: RetryHandler;
  const fastConfig = {
    ...DEFAULT_RETRY_CONFIG,
    baseDelay: 1,
    maxDelay: 1,
    backoffMultiplier: 1,
  };

  beforeEach(() => {
    handler = new RetryHandler(fastConfig);
    vi.spyOn(Math, 'random').mockReturnValue(0);
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('execute', () => {
    it('should execute function successfully without retry', async () => {
      const fn = vi.fn().mockResolvedValue('success');
      const result = await handler.execute(fn);
      
      expect(result).toBe('success');
      expect(fn).toHaveBeenCalledTimes(1);
    });

    it('should retry on failure and eventually succeed', async () => {
      const fn = vi.fn()
        .mockRejectedValueOnce(new Error('First attempt'))
        .mockResolvedValueOnce('success');
      
      const result = await handler.execute(fn);
      
      expect(result).toBe('success');
      expect(fn).toHaveBeenCalledTimes(2);
    });

    it('should exhaust retries and throw final error', async () => {
      const fn = vi.fn().mockRejectedValue(new Error('Always fails'));
      
      await expect(handler.execute(fn)).rejects.toThrow('Always fails');
      expect(fn).toHaveBeenCalledTimes(fastConfig.maxRetries + 1);
    });

    it('should respect custom retry config', async () => {
      const customConfig = { ...fastConfig, maxRetries: 2 };
      const customHandler = new RetryHandler(customConfig);
      const fn = vi.fn().mockRejectedValue(new Error('Fail'));
      
      await expect(customHandler.execute(fn)).rejects.toThrow('Fail');
      expect(fn).toHaveBeenCalledTimes(3); // 2 retries + 1 initial
    });
  });
});

describe('CircuitBreaker', () => {
  let breaker: CircuitBreaker;

  beforeEach(() => {
    breaker = new CircuitBreaker({ failureThreshold: 3, successThreshold: 1, timeout: 1000 });
  });

  describe('canExecute', () => {
    it('should allow execution when closed', () => {
      expect(breaker.canExecute()).toBe(true);
    });

    it('should prevent execution when open', () => {
      breaker.recordFailure();
      breaker.recordFailure();
      breaker.recordFailure();
      
      expect(breaker.canExecute()).toBe(false);
    });
  });

  describe('recordSuccess', () => {
    it('should reset failure count on success', () => {
      breaker.recordFailure();
      breaker.recordFailure();
      breaker.recordSuccess();
      
      // Should be closed again
      expect(breaker.canExecute()).toBe(true);
    });
  });

  describe('recordFailure', () => {
    it('should increment failure count', () => {
      breaker.recordFailure();
      expect(breaker.canExecute()).toBe(true);
      
      breaker.recordFailure();
      expect(breaker.canExecute()).toBe(true);
      
      breaker.recordFailure();
      expect(breaker.canExecute()).toBe(false);
    });
  });
});

describe('CircuitBreakerRegistry', () => {
  it('should return singleton instance', () => {
    const registry1 = CircuitBreakerRegistry.getInstance();
    const registry2 = CircuitBreakerRegistry.getInstance();
    expect(registry1).toBe(registry2);
  });

  it('should return same breaker for same path', () => {
    const registry = CircuitBreakerRegistry.getInstance();
    const breaker1 = registry.getBreaker('/api/test');
    const breaker2 = registry.getBreaker('/api/test');
    expect(breaker1).toBe(breaker2);
  });

  it('should return different breakers for different paths', () => {
    const registry = CircuitBreakerRegistry.getInstance();
    const breaker1 = registry.getBreaker('/api/test1');
    const breaker2 = registry.getBreaker('/api/test2');
    expect(breaker1).not.toBe(breaker2);
  });
});
