/**
 * @fileoverview Circuit breaker implementation for fault tolerance
 * This module implements the circuit breaker pattern to prevent cascade failures.
 */

import { createLogger } from '../logging/logger.js';

const logger = createLogger('CircuitBreaker');

/**
 * Circuit breaker states
 * @enum
 * @public
 */
export enum CircuitState {
  /** Normal operation - requests pass through */
  CLOSED = 'closed',
  /** Circuit is open - requests fail immediately */
  OPEN = 'open',
  /** Testing if service has recovered */
  HALF_OPEN = 'half-open',
}

/**
 * Circuit breaker configuration
 * @interface
 * @public
 */
export interface CircuitBreakerConfig {
  /** Number of failures before opening circuit */
  failureThreshold: number;
  /** Time window for counting failures (ms) */
  failureWindow: number;
  /** Time to wait before attempting recovery (ms) */
  recoveryTimeout: number;
  /** Number of successful requests needed to close from half-open */
  successThreshold: number;
  /** Maximum number of test requests in half-open state */
  halfOpenMaxAttempts: number;
}

/**
 * Circuit breaker statistics
 * @interface
 * @public
 */
export interface CircuitBreakerStats {
  /** Current circuit state */
  state: CircuitState;
  /** Number of failures in current window */
  failureCount: number;
  /** Number of successful requests */
  successCount: number;
  /** Total requests processed */
  totalRequests: number;
  /** Time when circuit was last opened */
  lastOpenTime?: number;
  /** Time when circuit was last closed */
  lastCloseTime?: number;
  /** Success rate percentage */
  successRate: number;
}

/**
 * Default circuit breaker configuration from environment
 * @private
 */
const DEFAULT_CONFIG: CircuitBreakerConfig = {
  failureThreshold: parseInt(process.env.CIRCUIT_BREAKER_THRESHOLD || '5', 10),
  failureWindow: 60000, // 1 minute
  recoveryTimeout: parseInt(process.env.CIRCUIT_BREAKER_TIMEOUT_MS || '30000', 10),
  successThreshold: 3,
  halfOpenMaxAttempts: 5,
};

/**
 * Circuit breaker implementation
 * @class
 * @public
 */
export class CircuitBreaker {
  private state: CircuitState = CircuitState.CLOSED;
  private failures: number[] = [];
  private successes: number[] = [];
  private lastStateChange: number = Date.now();
  private halfOpenAttempts = 0;
  private totalRequests = 0;
  private readonly config: CircuitBreakerConfig;
  private readonly name: string;

  /**
   * Creates a new circuit breaker instance
   * @param name The name of this circuit breaker (for logging)
   * @param config Optional configuration overrides
   */
  constructor(name: string, config: Partial<CircuitBreakerConfig> = {}) {
    this.name = name;
    this.config = { ...DEFAULT_CONFIG, ...config };
    logger.debug('Circuit breaker created', { name, config: this.config });
  }

  /**
   * Check if a request should be allowed through
   * @returns True if the request can proceed
   * @public
   */
  canRequest(): boolean {
    this.cleanupOldEntries();

    switch (this.state) {
      case CircuitState.CLOSED:
        return true;

      case CircuitState.OPEN: {
        // Check if it's time to transition to half-open
        const timeSinceOpen = Date.now() - this.lastStateChange;
        if (timeSinceOpen >= this.config.recoveryTimeout) {
          this.transitionTo(CircuitState.HALF_OPEN);
          this.halfOpenAttempts = 1; // Count this request as the first attempt
          return true;
        }
        logger.debug('Circuit breaker is open, rejecting request', {
          name: this.name,
          timeSinceOpen,
          recoveryTimeout: this.config.recoveryTimeout,
        });
        return false;
      }

      case CircuitState.HALF_OPEN:
        // Allow limited test requests
        if (this.halfOpenAttempts < this.config.halfOpenMaxAttempts) {
          this.halfOpenAttempts++;
          return true;
        }
        logger.debug('Circuit breaker half-open limit reached', {
          name: this.name,
          attempts: this.halfOpenAttempts,
          max: this.config.halfOpenMaxAttempts,
        });
        return false;

      default:
        return false;
    }
  }

  /**
   * Record a successful request
   * @public
   */
  recordSuccess(): void {
    this.totalRequests++;
    this.successes.push(Date.now());
    this.cleanupOldEntries();

    switch (this.state) {
      case CircuitState.HALF_OPEN: {
        // Check if we have enough successes to close the circuit
        const recentSuccesses = this.getRecentSuccessCount();
        logger.debug('Success in half-open state', {
          name: this.name,
          recentSuccesses,
          threshold: this.config.successThreshold,
        });

        if (recentSuccesses >= this.config.successThreshold) {
          this.transitionTo(CircuitState.CLOSED);
        }
        break;
      }

      case CircuitState.CLOSED:
        // Normal operation, nothing special to do
        break;

      case CircuitState.OPEN:
        // Shouldn't happen, but handle gracefully
        logger.warn('Success recorded while circuit is open', { name: this.name });
        break;

      default:
        // Log unexpected state
        logger.error('Unexpected circuit state in recordSuccess', {
          name: this.name,
          state: this.state,
        });
        break;
    }
  }

  /**
   * Record a failed request
   * @public
   */
  recordFailure(): void {
    this.totalRequests++;
    this.failures.push(Date.now());
    this.cleanupOldEntries();

    switch (this.state) {
      case CircuitState.CLOSED: {
        // Check if we've exceeded the failure threshold
        const recentFailures = this.getRecentFailureCount();
        logger.debug('Failure in closed state', {
          name: this.name,
          recentFailures,
          threshold: this.config.failureThreshold,
        });

        if (recentFailures >= this.config.failureThreshold) {
          this.transitionTo(CircuitState.OPEN);
        }
        break;
      }

      case CircuitState.HALF_OPEN:
        // Any failure in half-open state reopens the circuit
        logger.debug('Failure in half-open state, reopening circuit', { name: this.name });
        this.transitionTo(CircuitState.OPEN);
        break;

      case CircuitState.OPEN:
        // Already open, nothing to do
        break;

      default:
        // Log unexpected state
        logger.error('Unexpected circuit state in recordFailure', {
          name: this.name,
          state: this.state,
        });
        break;
    }
  }

  /**
   * Get current circuit breaker statistics
   * @returns The current statistics
   * @public
   */
  getStats(): CircuitBreakerStats {
    this.cleanupOldEntries();
    const failureCount = this.getRecentFailureCount();
    const successCount = this.getRecentSuccessCount();
    const successRate =
      successCount + failureCount > 0 ? (successCount / (successCount + failureCount)) * 100 : 0;

    const stats: CircuitBreakerStats = {
      state: this.state,
      failureCount,
      successCount,
      totalRequests: this.totalRequests,
      successRate: Math.round(successRate * 100) / 100,
    };

    if (this.state === CircuitState.OPEN) {
      stats.lastOpenTime = this.lastStateChange;
    }
    if (this.state === CircuitState.CLOSED) {
      stats.lastCloseTime = this.lastStateChange;
    }

    return stats;
  }

  /**
   * Reset the circuit breaker to closed state
   * @public
   */
  reset(): void {
    this.state = CircuitState.CLOSED;
    this.failures = [];
    this.successes = [];
    this.halfOpenAttempts = 0;
    this.lastStateChange = Date.now();
    logger.info('Circuit breaker reset', { name: this.name });
  }

  /**
   * Get the current state
   * @returns The current circuit state
   * @public
   */
  getState(): CircuitState {
    return this.state;
  }

  /**
   * Transition to a new state
   * @param newState The new state to transition to
   * @private
   */
  private transitionTo(newState: CircuitState): void {
    const oldState = this.state;
    this.state = newState;
    this.lastStateChange = Date.now();

    if (newState === CircuitState.HALF_OPEN) {
      this.halfOpenAttempts = 0;
    }

    logger.info('Circuit breaker state transition', {
      name: this.name,
      from: oldState,
      to: newState,
    });
  }

  /**
   * Clean up old entries outside the time window
   * @private
   */
  private cleanupOldEntries(): void {
    const cutoff = Date.now() - this.config.failureWindow;
    this.failures = this.failures.filter((time) => time > cutoff);
    this.successes = this.successes.filter((time) => time > cutoff);
  }

  /**
   * Get the count of recent failures within the time window
   * @returns The number of recent failures
   * @private
   */
  private getRecentFailureCount(): number {
    const cutoff = Date.now() - this.config.failureWindow;
    return this.failures.filter((time) => time > cutoff).length;
  }

  /**
   * Get the count of recent successes within the time window
   * @returns The number of recent successes
   * @private
   */
  private getRecentSuccessCount(): number {
    const cutoff = Date.now() - this.config.failureWindow;
    return this.successes.filter((time) => time > cutoff).length;
  }
}

/**
 * Circuit breaker manager for managing multiple circuit breakers
 * @class
 * @public
 */
export class CircuitBreakerManager {
  private static instance: CircuitBreakerManager;
  private breakers: Map<string, CircuitBreaker> = new Map();

  /**
   * Get the singleton instance
   * @returns The circuit breaker manager instance
   * @public
   */
  static getInstance(): CircuitBreakerManager {
    if (!CircuitBreakerManager.instance) {
      CircuitBreakerManager.instance = new CircuitBreakerManager();
    }
    return CircuitBreakerManager.instance;
  }

  /**
   * Get or create a circuit breaker for an endpoint
   * @param endpoint The endpoint name
   * @param config Optional configuration overrides
   * @returns The circuit breaker instance
   * @public
   */
  getBreaker(endpoint: string, config?: Partial<CircuitBreakerConfig>): CircuitBreaker {
    let breaker = this.breakers.get(endpoint);
    if (!breaker) {
      breaker = new CircuitBreaker(endpoint, config);
      this.breakers.set(endpoint, breaker);
    }
    return breaker;
  }

  /**
   * Get statistics for all circuit breakers
   * @returns Map of endpoint to statistics
   * @public
   */
  getAllStats(): Map<string, CircuitBreakerStats> {
    const stats = new Map<string, CircuitBreakerStats>();
    for (const [endpoint, breaker] of this.breakers) {
      stats.set(endpoint, breaker.getStats());
    }
    return stats;
  }

  /**
   * Reset all circuit breakers
   * @public
   */
  resetAll(): void {
    for (const breaker of this.breakers.values()) {
      breaker.reset();
    }
    logger.info('All circuit breakers reset');
  }

  /**
   * Clear all circuit breakers (for testing)
   * @public
   */
  clear(): void {
    this.breakers.clear();
  }
}
