/**
 * Generic circuit breaker state machine.
 * Provides the core state management logic that can be reused across different services.
 *
 * State Transitions:
 * - CLOSED -> OPEN: After failureThreshold consecutive failures
 * - OPEN -> HALF_OPEN: After resetTimeoutMs has elapsed
 * - HALF_OPEN -> CLOSED: After successThreshold consecutive successes
 * - HALF_OPEN -> OPEN: On any failure
 */

import { ServiceUnavailableError } from './errors'
import {
  CIRCUIT_BREAKER_FAILURE_THRESHOLD,
  CIRCUIT_BREAKER_COOLDOWN_MS,
  CIRCUIT_BREAKER_SUCCESS_THRESHOLD,
  CIRCUIT_BREAKER_HALF_OPEN_MAX_REQUESTS
} from '../constants/resilience'

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

export interface CircuitBreakerConfig {
  /** Number of failures before opening circuit (default: 5) */
  failureThreshold?: number
  /** Time in ms before attempting recovery (default: 60000) */
  resetTimeoutMs?: number
  /** Successes needed to close circuit from half-open (default: 2) */
  successThreshold?: number
  /** Max concurrent requests in half-open state (default: 3) */
  halfOpenMaxRequests?: number
  /** Called when circuit state changes. Use for alerting on OPEN transitions. */
  onStateChange?: (service: string, from: CircuitState, to: CircuitState, context: { failureCount: number }) => void
}

export interface CircuitBreaker {
  /** Execute a function through the circuit breaker */
  execute<T>(fn: () => Promise<T>): Promise<T>
  /** Get current circuit state */
  getState(): CircuitState
  /** Get current failure count */
  getFailureCount(): number
  /** Reset circuit to closed state */
  reset(): void
}

const DEFAULT_CONFIG: Required<Pick<CircuitBreakerConfig, 'failureThreshold' | 'resetTimeoutMs' | 'successThreshold' | 'halfOpenMaxRequests'>> = {
  failureThreshold: CIRCUIT_BREAKER_FAILURE_THRESHOLD,
  resetTimeoutMs: CIRCUIT_BREAKER_COOLDOWN_MS,
  successThreshold: CIRCUIT_BREAKER_SUCCESS_THRESHOLD,
  halfOpenMaxRequests: CIRCUIT_BREAKER_HALF_OPEN_MAX_REQUESTS
}

/**
 * Creates a circuit breaker instance for a specific service.
 *
 * @param service - Service name for error context (e.g., 'llm:gpt-4', 'workos')
 * @param config - Optional configuration overrides
 * @returns CircuitBreaker instance
 *
 * @example
 * ```typescript
 * const breaker = createCircuitBreaker('workos')
 *
 * try {
 *   const result = await breaker.execute(() => workos.userManagement.getUser(id))
 * } catch (error) {
 *   if (error instanceof ServiceUnavailableError) {
 *     // Circuit is open, service unavailable
 *   }
 * }
 * ```
 */
export function createCircuitBreaker(
  service: string,
  config?: CircuitBreakerConfig
): CircuitBreaker {
  const cfg = { ...DEFAULT_CONFIG, ...config }

  let state = CircuitState.CLOSED
  let failureCount = 0
  let successCount = 0
  let lastFailureTime = 0
  let halfOpenRequests = 0

  return {
    async execute<T>(fn: () => Promise<T>): Promise<T> {
      // Check if circuit should transition from OPEN to HALF_OPEN
      if (state === CircuitState.OPEN) {
        if (Date.now() - lastFailureTime >= cfg.resetTimeoutMs) {
          const prev = state
          state = CircuitState.HALF_OPEN
          halfOpenRequests = 0
          successCount = 0
          cfg.onStateChange?.(service, prev, state, { failureCount })
        } else {
          throw new ServiceUnavailableError(service, 'circuit_open', {
            failureCount,
            resetIn: cfg.resetTimeoutMs - (Date.now() - lastFailureTime)
          })
        }
      }

      // In HALF_OPEN, limit concurrent requests to prevent thundering herd
      if (state === CircuitState.HALF_OPEN) {
        if (halfOpenRequests >= cfg.halfOpenMaxRequests) {
          throw new ServiceUnavailableError(service, 'circuit_open', {
            reason: 'half_open_limit_reached'
          })
        }
        halfOpenRequests++
      }

      try {
        const result = await fn()

        // Success handling
        if (state === CircuitState.HALF_OPEN) {
          successCount++
          if (successCount >= cfg.successThreshold) {
            const prev = state
            state = CircuitState.CLOSED
            failureCount = 0
            cfg.onStateChange?.(service, prev, state, { failureCount: 0 })
          }
        } else {
          // Reset failure count on success in CLOSED state
          failureCount = 0
        }

        return result
      } catch (error) {
        failureCount++
        lastFailureTime = Date.now()

        if (state === CircuitState.HALF_OPEN) {
          // Any failure in HALF_OPEN immediately opens the circuit
          const prev = state
          state = CircuitState.OPEN
          cfg.onStateChange?.(service, prev, state, { failureCount })
        } else if (failureCount >= cfg.failureThreshold) {
          // Threshold reached in CLOSED state
          const prev = state
          state = CircuitState.OPEN
          cfg.onStateChange?.(service, prev, state, { failureCount })
        }

        throw error
      }
    },

    getState: () => state,
    getFailureCount: () => failureCount,
    reset: () => {
      state = CircuitState.CLOSED
      failureCount = 0
      successCount = 0
      halfOpenRequests = 0
    }
  }
}
