/**
 * Circuit breaker implementation for resilient operations
 * Prevents cascading failures by opening the circuit after threshold failures
 */

export enum CircuitBreakerState {
  CLOSED = 'closed',
  OPEN = 'open',
  HALF_OPEN = 'half_open',
}

export interface CircuitBreakerOptions {
  failureThreshold?: number;
  resetTimeout?: number;
  halfOpenMaxCalls?: number;
  onStateChange?: (state: CircuitBreakerState) => void;
}

/**
 * Circuit breaker for resilient operations
 */
export class CircuitBreaker {
  private state: CircuitBreakerState = CircuitBreakerState.CLOSED;
  private failures = 0;
  private lastFailureTime: number | null = null;
  private halfOpenCalls = 0;

  constructor(private options: CircuitBreakerOptions = {}) {}

  /**
   * Execute a function with circuit breaker protection
   */
  async execute<T>(fn: () => Promise<T>): Promise<T> {
    const {
      failureThreshold = 5,
      resetTimeout = 60000,
    } = this.options;

    // Check if circuit should be opened
    if (this.state === CircuitBreakerState.CLOSED) {
      if (this.failures >= failureThreshold) {
        this.setState(CircuitBreakerState.OPEN);
        this.lastFailureTime = Date.now();
      }
    }

    // Check if circuit should be half-opened
    if (this.state === CircuitBreakerState.OPEN) {
      if (this.lastFailureTime && Date.now() - this.lastFailureTime >= resetTimeout) {
        this.setState(CircuitBreakerState.HALF_OPEN);
        this.halfOpenCalls = 0;
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    // Execute function
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  /**
   * Handle successful execution
   */
  private onSuccess(): void {
    if (this.state === CircuitBreakerState.HALF_OPEN) {
      this.setState(CircuitBreakerState.CLOSED);
      this.failures = 0;
      this.halfOpenCalls = 0;
    } else if (this.state === CircuitBreakerState.CLOSED) {
      this.failures = 0;
    }
  }

  /**
   * Handle failed execution
   */
  private onFailure(): void {
    if (this.state === CircuitBreakerState.HALF_OPEN) {
      this.halfOpenCalls++;
      if (this.halfOpenCalls >= (this.options.halfOpenMaxCalls || 3)) {
        this.setState(CircuitBreakerState.OPEN);
        this.lastFailureTime = Date.now();
      }
    } else if (this.state === CircuitBreakerState.CLOSED) {
      this.failures++;
    }
  }

  /**
   * Set circuit breaker state
   */
  private setState(state: CircuitBreakerState): void {
    if (this.state !== state) {
      this.state = state;
      if (this.options.onStateChange) {
        this.options.onStateChange(state);
      }
    }
  }

  /**
   * Get current state
   */
  getState(): CircuitBreakerState {
    return this.state;
  }

  /**
   * Reset circuit breaker
   */
  reset(): void {
    this.state = CircuitBreakerState.CLOSED;
    this.failures = 0;
    this.lastFailureTime = null;
    this.halfOpenCalls = 0;
  }
}

