// Circuit Breaker Implementation
// Prevents cascading failures in external service calls

import { logger } from '@/infrastructure/monitoring/logger';

export interface CircuitBreakerOptions {
  failureThreshold: number;
  resetTimeoutMs: number;
  halfOpenMaxCalls: number;
  name?: string;
}

export class CircuitBreaker {
  private failures = 0;
  private lastFailureTime?: Date;
  private state: 'closed' | 'open' | 'half-open' = 'closed';
  private halfOpenCalls = 0;
  private name: string;

  constructor(private options: CircuitBreakerOptions) {
    this.name = options.name || 'CircuitBreaker';
  }

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    // Check circuit state
    if (this.state === 'open') {
      if (this.lastFailureTime) {
        const timeSinceLastFailure = Date.now() - this.lastFailureTime.getTime();
        if (timeSinceLastFailure >= this.options.resetTimeoutMs) {
          this.state = 'half-open';
          this.failures = 0;
          this.halfOpenCalls = 0;
          logger.info('Circuit breaker transitioning to half-open', {
            name: this.name,
          });
        } else {
          throw new Error(`Circuit breaker ${this.name} is open`);
        }
      }
    }

    // Limit half-open calls
    if (this.state === 'half-open' && this.halfOpenCalls >= this.options.halfOpenMaxCalls) {
      throw new Error(`Circuit breaker ${this.name} half-open call limit reached`);
    }

    try {
      if (this.state === 'half-open') {
        this.halfOpenCalls++;
      }

      const result = await fn();

      // Success - reset failures if half-open or closed
      if (this.state === 'half-open') {
        this.state = 'closed';
        this.failures = 0;
        this.halfOpenCalls = 0;
        logger.info('Circuit breaker closed after successful half-open call', {
          name: this.name,
        });
      } else {
        this.failures = 0;
      }

      return result;
    } catch (error) {
      this.failures++;
      this.lastFailureTime = new Date();

      logger.warn('Circuit breaker failure', {
        name: this.name,
        failures: this.failures,
        threshold: this.options.failureThreshold,
        error: error instanceof Error ? error.message : 'Unknown error',
      });

      // Open circuit if threshold reached
      if (this.failures >= this.options.failureThreshold) {
        this.state = 'open';
        logger.error('Circuit breaker opened', {
          name: this.name,
          failures: this.failures,
        });
      }

      throw error;
    }
  }

  getState(): 'closed' | 'open' | 'half-open' {
    return this.state;
  }

  reset(): void {
    this.failures = 0;
    this.lastFailureTime = undefined;
    this.state = 'closed';
    this.halfOpenCalls = 0;
    logger.info('Circuit breaker reset', { name: this.name });
  }

  getStats() {
    return {
      name: this.name,
      state: this.state,
      failures: this.failures,
      lastFailureTime: this.lastFailureTime,
      halfOpenCalls: this.halfOpenCalls,
    };
  }
}

// Circuit breaker instances for external services
export const proxmoxCircuitBreaker = new CircuitBreaker({
  name: 'ProxmoxVE',
  failureThreshold: 5,
  resetTimeoutMs: 60000, // 1 minute
  halfOpenMaxCalls: 3,
});

export const docusignCircuitBreaker = new CircuitBreaker({
  name: 'DocuSign',
  failureThreshold: 5,
  resetTimeoutMs: 60000,
  halfOpenMaxCalls: 3,
});

export const stripeCircuitBreaker = new CircuitBreaker({
  name: 'Stripe',
  failureThreshold: 5,
  resetTimeoutMs: 60000,
  halfOpenMaxCalls: 3,
});

export const braintreeCircuitBreaker = new CircuitBreaker({
  name: 'Braintree',
  failureThreshold: 5,
  resetTimeoutMs: 60000,
  halfOpenMaxCalls: 3,
});
