import type { CircuitBreakerState, ProviderRuntimeState, ProviderHealth } from '@zyphon/provider-contracts';

export interface CircuitBreakerConfig {
  failureThreshold?: number;     // Number of consecutive failures to trip (default: 3)
  recoveryTimeSeconds?: number;  // Seconds to remain open before half-open (default: 20)
}

export class CircuitBreakerManager {
  private states = new Map<string, ProviderRuntimeState>();
  private failureThreshold: number;
  private recoveryTimeMs: number;

  constructor(config: CircuitBreakerConfig = {}) {
    this.failureThreshold = config.failureThreshold ?? 3;
    this.recoveryTimeMs = (config.recoveryTimeSeconds ?? 20) * 1000;
  }

  public getOrCreate(providerId: string): ProviderRuntimeState {
    let state = this.states.get(providerId);
    if (!state) {
      state = {
        circuit_state: 'CLOSED',
        consecutive_failures: 0,
        total_ok: 0,
        total_failed: 0,
        last_latency_ms: 0,
        recent_latencies: [],
        recent_results: [],
        health: {
          status: 'healthy',
          latency_ms: 0,
          checked_at: Date.now(),
        },
      };
      this.states.set(providerId, state);
    }
    return state;
  }

  public getState(providerId: string): CircuitBreakerState {
    const state = this.getOrCreate(providerId);
    const now = Date.now();

    if (state.circuit_state === 'OPEN') {
      if (state.open_until && now >= state.open_until) {
        state.circuit_state = 'HALF_OPEN';
      }
    }

    return state.circuit_state;
  }

  public recordSuccess(providerId: string, latencyMs: number): void {
    const state = this.getOrCreate(providerId);
    state.total_ok++;
    state.consecutive_failures = 0;
    state.last_latency_ms = latencyMs;
    state.circuit_state = 'CLOSED';
    state.open_until = undefined;

    state.recent_latencies.push(latencyMs);
    if (state.recent_latencies.length > 20) state.recent_latencies.shift();

    state.recent_results.push(true);
    if (state.recent_results.length > 20) state.recent_results.shift();

    state.health = {
      status: 'healthy',
      latency_ms: latencyMs,
      checked_at: Date.now(),
    };
  }

  public recordFailure(providerId: string, latencyMs = 0, errorMsg?: string): CircuitBreakerState {
    const state = this.getOrCreate(providerId);
    state.total_failed++;
    state.consecutive_failures++;
    state.last_latency_ms = latencyMs;

    state.recent_latencies.push(latencyMs);
    if (state.recent_latencies.length > 20) state.recent_latencies.shift();

    state.recent_results.push(false);
    if (state.recent_results.length > 20) state.recent_results.shift();

    if (state.consecutive_failures >= this.failureThreshold || state.circuit_state === 'HALF_OPEN') {
      state.circuit_state = 'OPEN';
      state.open_until = Date.now() + this.recoveryTimeMs;
    }

    state.health = {
      status: state.circuit_state === 'OPEN' ? 'down' : 'degraded',
      latency_ms: latencyMs,
      checked_at: Date.now(),
      message: errorMsg,
    };

    return state.circuit_state;
  }

  public getAllStates(): Record<string, ProviderRuntimeState> {
    const now = Date.now();
    const result: Record<string, ProviderRuntimeState> = {};
    for (const [id, state] of this.states.entries()) {
      if (state.circuit_state === 'OPEN' && state.open_until && now >= state.open_until) {
        state.circuit_state = 'HALF_OPEN';
      }
      result[id] = { ...state };
    }
    return result;
  }
}

