// src/middleware/circuitBreaker.ts

import type { ITransportAdapter } from '../engine/remoting/ITransportAdapter';
import type { ITransportMiddleware } from '../engine/remoting/TransportMiddleware';
import { wrapTransport } from '../engine/remoting/TransportMiddleware';

/**
 * Circuit breaker states.
 */
export type CircuitState = 'closed' | 'open' | 'half-open';

/**
 * Circuit breaker options.
 */
export interface ICircuitBreakerOptions {
  /**
   * Number of failures before opening circuit.
   * Default: 5
   */
  failureThreshold?: number;

  /**
   * Time to wait before trying half-open in ms.
   * Default: 30000 (30 seconds)
   */
  resetTimeoutMs?: number;

  /**
   * Number of successful calls needed to close circuit from half-open.
   * Default: 2
   */
  successThreshold?: number;

  /**
   * Topic patterns to monitor (regex).
   * If not specified, monitors all topics.
   */
  topicPatterns?: RegExp[];

  /**
   * Called when circuit state changes.
   */
  onStateChange?: (from: CircuitState, to: CircuitState, stats: ICircuitStats) => void;
}

/**
 * Circuit statistics.
 */
export interface ICircuitStats {
  state: CircuitState;
  failures: number;
  successes: number;
  lastFailure?: number;
  lastSuccess?: number;
  openedAt?: number;
  closedAt?: number;
}

/**
 * Circuit breaker error thrown when circuit is open.
 */
export class CircuitOpenError extends Error {
  readonly circuitState: CircuitState;
  readonly stats: ICircuitStats;

  constructor(message: string, stats: ICircuitStats) {
    super(message);
    this.name = 'CircuitOpenError';
    this.circuitState = stats.state;
    this.stats = stats;
  }
}

/**
 * CircuitBreaker - Protects against cascading failures.
 *
 * States:
 * - CLOSED: Normal operation, failures counted
 * - OPEN: Requests fail fast, waiting for reset timeout
 * - HALF-OPEN: Testing with limited requests
 */
export class CircuitBreaker {
  private _state: CircuitState = 'closed';
  private _failures = 0;
  private _successes = 0;
  private _lastFailure?: number;
  private _lastSuccess?: number;
  private _openedAt?: number;
  private _closedAt?: number;

  private readonly _options: Required<Omit<ICircuitBreakerOptions, 'topicPatterns' | 'onStateChange'>> & {
    topicPatterns?: RegExp[];
    onStateChange?: ICircuitBreakerOptions['onStateChange'];
  };

  constructor(options: ICircuitBreakerOptions = {}) {
    this._options = {
      failureThreshold: options.failureThreshold ?? 5,
      resetTimeoutMs: options.resetTimeoutMs ?? 30000,
      successThreshold: options.successThreshold ?? 2,
      topicPatterns: options.topicPatterns,
      onStateChange: options.onStateChange,
    };
    this._closedAt = Date.now();
  }

  /**
   * Get current circuit state.
   */
  get state(): CircuitState {
    // Check if we should transition to half-open
    if (this._state === 'open' && this._openedAt) {
      const elapsed = Date.now() - this._openedAt;
      if (elapsed >= this._options.resetTimeoutMs) {
        this._transitionTo('half-open');
      }
    }
    return this._state;
  }

  /**
   * Get circuit statistics.
   */
  get stats(): ICircuitStats {
    return {
      state: this.state,
      failures: this._failures,
      successes: this._successes,
      lastFailure: this._lastFailure,
      lastSuccess: this._lastSuccess,
      openedAt: this._openedAt,
      closedAt: this._closedAt,
    };
  }

  /**
   * Check if a topic should be monitored.
   */
  shouldMonitor(topic: string): boolean {
    if (!this._options.topicPatterns || this._options.topicPatterns.length === 0) {
      return true;
    }
    return this._options.topicPatterns.some(pattern => pattern.test(topic));
  }

  /**
   * Check if request can proceed.
   * Throws CircuitOpenError if circuit is open.
   */
  canProceed(topic: string): boolean {
    if (!this.shouldMonitor(topic)) {
      return true;
    }

    const currentState = this.state; // This may trigger half-open transition

    if (currentState === 'closed') {
      return true;
    }

    if (currentState === 'half-open') {
      return true; // Allow test request
    }

    // Open - fail fast
    throw new CircuitOpenError(
      `Circuit breaker is OPEN. Reset timeout: ${this._options.resetTimeoutMs}ms`,
      this.stats
    );
  }

  /**
   * Record a successful operation.
   */
  recordSuccess(topic: string): void {
    if (!this.shouldMonitor(topic)) return;

    // Use getter to ensure proper state transitions (e.g., open -> half-open)
    const currentState = this.state;

    this._successes++;
    this._lastSuccess = Date.now();

    if (currentState === 'half-open') {
      if (this._successes >= this._options.successThreshold) {
        this._transitionTo('closed');
      }
    }
  }

  /**
   * Record a failed operation.
   */
  recordFailure(topic: string): void {
    if (!this.shouldMonitor(topic)) return;

    // Use getter to ensure proper state transitions (e.g., open -> half-open)
    const currentState = this.state;

    this._failures++;
    this._lastFailure = Date.now();

    if (currentState === 'half-open') {
      // Any failure in half-open reopens immediately
      this._transitionTo('open');
    } else if (currentState === 'closed') {
      if (this._failures >= this._options.failureThreshold) {
        this._transitionTo('open');
      }
    }
  }

  /**
   * Manually reset the circuit to closed.
   */
  reset(): void {
    this._transitionTo('closed');
    this._failures = 0;
    this._successes = 0;
  }

  /**
   * Manually trip the circuit to open.
   */
  trip(): void {
    this._transitionTo('open');
  }

  /**
   * Transition to a new state.
   */
  private _transitionTo(newState: CircuitState): void {
    if (this._state === newState) return;

    const oldState = this._state;
    this._state = newState;

    if (newState === 'open') {
      this._openedAt = Date.now();
      this._successes = 0;
    } else if (newState === 'closed') {
      this._closedAt = Date.now();
      this._failures = 0;
      this._successes = 0;
    } else if (newState === 'half-open') {
      this._successes = 0;
    }

    this._options.onStateChange?.(oldState, newState, this.stats);
  }
}

/**
 * Create a circuit breaker middleware for transport.
 */
export function withCircuitBreaker(
  transport: ITransportAdapter,
  options: ICircuitBreakerOptions = {}
): ITransportAdapter {
  const breaker = new CircuitBreaker(options);

  const middleware: ITransportMiddleware = {
    beforePublish(topic: string, payload: unknown) {
      try {
        breaker.canProceed(topic);
        return { topic, payload };
      } catch (err) {
        if (err instanceof CircuitOpenError) {
          // eslint-disable-next-line no-console -- kernel infrastructure, no actor context
          console.warn(`[CircuitBreaker] Blocked publish to '${topic}': circuit is OPEN`);
          return { topic, payload, cancel: true };
        }
        throw err;
      }
    },
    afterPublish(topic: string, _payload: unknown) {
      // Note: Transport middleware afterPublish doesn't receive error
      // Success is recorded here. Failures need external tracking.
      breaker.recordSuccess(topic);
    },
  };

  return wrapTransport(transport, [middleware]);
}

/**
 * Create a circuit breaker that can be shared across transports.
 */
export function createCircuitBreaker(options: ICircuitBreakerOptions = {}): CircuitBreaker {
  return new CircuitBreaker(options);
}
