/**
 * Marine-specific circuit breaker implementation
 */

import {
  CircuitBreakerConfig,
  CircuitBreakerMetrics,
  ResilienceResult,
  ResilienceEvent,
  MarineEnvironmentStatus
} from '../types';

import {
  CircuitBreakerState,
  CircuitBreakerStateType,
  MarineFailureType,
  MarineFailureTypeType,
  OperationalContext,
  OperationalContextType
} from '../types/marine-constants';

/**
 * Enhanced circuit breaker with marine-specific adaptations
 */
export class MarineCircuitBreaker {
  private config: CircuitBreakerConfig;
  private metrics: CircuitBreakerMetrics;
  private eventListeners: ((event: ResilienceEvent) => void)[] = [];
  private marineEnvironment?: MarineEnvironmentStatus;

  constructor(name: string, config: Partial<CircuitBreakerConfig> = {}) {
    this.config = {
      failureThreshold: 5,
      successThreshold: 3,
      timeout: 60000, // 1 minute
      operationalContext: OperationalContext.SAILING,
      failureTypes: [MarineFailureType.SENSOR_FAILURE, MarineFailureType.TIMEOUT],
      environmentalAdjustments: {
        roughSeas: false,
        lowPower: false,
        limitedConnectivity: false
      },
      volumeThreshold: 10,
      errorPercentageThreshold: 50,
      slowCallDurationThreshold: 5000, // 5 seconds
      slowCallRateThreshold: 50,
      ...config
    };

    this.metrics = this.initializeMetrics();
  }

  /**
   * Execute operation with circuit breaker protection
   */
  async execute<T>(
    operation: () => Promise<T>,
    operationName: string = 'unknown'
  ): Promise<ResilienceResult<T>> {
    const startTime = Date.now();
    
    // Check if circuit breaker allows execution
    if (!this.canExecute()) {
      this.metrics.rejectedRequests++;
      this.emitEvent('circuit_breaker_opened', operationName, {
        reason: 'Circuit breaker is open',
        state: this.metrics.state
      }, 'warning');

      return {
        success: false,
        error: new Error(`Circuit breaker is ${this.metrics.state} for ${operationName}`),
        metrics: {
          executionTime: 0,
          retryCount: 0,
          circuitBreakerState: this.metrics.state
        },
        marineContext: this.getMarineContext()
      };
    }

    this.metrics.totalRequests++;

    try {
      // Execute the operation with timeout
      const timeoutMs = this.calculateTimeout();
      const result = await this.executeWithTimeout(operation, timeoutMs);
      
      const executionTime = Date.now() - startTime;
      
      // Check if call was slow
      const isSlow = executionTime > this.config.slowCallDurationThreshold;
      if (isSlow) {
        this.metrics.slowCalls++;
      }

      // Record success
      this.onSuccess(executionTime);

      return {
        success: true,
        data: result,
        metrics: {
          executionTime,
          retryCount: 0,
          circuitBreakerState: this.metrics.state,
          timeoutApplied: timeoutMs
        },
        marineContext: this.getMarineContext()
      };

    } catch (error) {
      const executionTime = Date.now() - startTime;
      
      // Classify the failure type
      const failureType = this.classifyFailure(error as Error);
      
      // Record failure
      this.onFailure(failureType, executionTime);

      return {
        success: false,
        error: error as Error,
        metrics: {
          executionTime,
          retryCount: 0,
          circuitBreakerState: this.metrics.state
        },
        marineContext: this.getMarineContext()
      };
    }
  }

  /**
   * Update marine environment status for adaptive behavior
   */
  updateMarineEnvironment(environment: MarineEnvironmentStatus): void {
    this.marineEnvironment = environment;
    
    // Adjust configuration based on marine conditions
    this.adjustForMarineConditions();
  }

  /**
   * Get current circuit breaker metrics
   */
  getMetrics(): CircuitBreakerMetrics {
    return { ...this.metrics };
  }

  /**
   * Get current circuit breaker state
   */
  getState(): CircuitBreakerStateType {
    return this.metrics.state;
  }

  /**
   * Force circuit breaker to specific state (for testing/emergency)
   */
  forceState(state: CircuitBreakerStateType, reason: string = 'Manual override'): void {
    const previousState = this.metrics.state;
    this.metrics.state = state;
    this.metrics.stateChangedAt = new Date();
    
    if (state === CircuitBreakerState.OPEN) {
      this.metrics.nextAttemptAt = new Date(Date.now() + this.config.timeout);
    } else {
      delete this.metrics.nextAttemptAt;
    }

    this.emitEvent('circuit_breaker_opened', 'manual', {
      previousState,
      newState: state,
      reason
    }, 'info');
  }

  /**
   * Add event listener for circuit breaker events
   */
  onEvent(listener: (event: ResilienceEvent) => void): void {
    this.eventListeners.push(listener);
  }

  /**
   * Reset circuit breaker metrics (for testing/maintenance)
   */
  reset(): void {
    this.metrics = this.initializeMetrics();
  }

  /**
   * Check if circuit breaker allows execution
   */
  private canExecute(): boolean {
    const now = new Date();

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

      case CircuitBreakerState.OPEN:
        // Check if timeout period has passed
        if (this.metrics.nextAttemptAt && now >= this.metrics.nextAttemptAt) {
          this.transitionToHalfOpen();
          return true;
        }
        return false;

      case CircuitBreakerState.HALF_OPEN:
        return true;

      default:
        return false;
    }
  }

  /**
   * Execute operation with timeout
   */
  private async executeWithTimeout<T>(
    operation: () => Promise<T>,
    timeoutMs: number
  ): Promise<T> {
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        reject(new Error(`Operation timed out after ${timeoutMs}ms`));
      }, timeoutMs);

      operation()
        .then(result => {
          clearTimeout(timer);
          resolve(result);
        })
        .catch(error => {
          clearTimeout(timer);
          reject(error);
        });
    });
  }

  /**
   * Calculate timeout based on marine conditions
   */
  private calculateTimeout(): number {
    let timeout = this.config.slowCallDurationThreshold;

    // Adjust for marine environment
    if (this.marineEnvironment) {
      timeout *= this.marineEnvironment.recommendedTimeoutMultiplier;
    }

    // Adjust for operational context
    switch (this.config.operationalContext) {
      case OperationalContext.EMERGENCY:
        timeout *= 0.5; // Faster response needed in emergency
        break;
      case OperationalContext.MAINTENANCE:
        timeout *= 2.0; // More tolerance during maintenance
        break;
      case OperationalContext.ANCHORED:
        timeout *= 1.5; // More relaxed when anchored
        break;
    }

    // Adjust for environmental conditions
    if (this.config.environmentalAdjustments.roughSeas) {
      timeout *= 1.5;
    }
    if (this.config.environmentalAdjustments.limitedConnectivity) {
      timeout *= 2.0;
    }

    return Math.max(1000, Math.min(timeout, 30000)); // Between 1s and 30s
  }

  /**
   * Classify failure type for marine-specific handling
   */
  private classifyFailure(error: Error): MarineFailureTypeType {
    const message = error.message.toLowerCase();

    if (message.includes('timeout')) {
      return MarineFailureType.TIMEOUT;
    }
    if (message.includes('network') || message.includes('connection')) {
      return MarineFailureType.CONNECTIVITY_LOSS;
    }
    if (message.includes('sensor') || message.includes('reading')) {
      return MarineFailureType.SENSOR_FAILURE;
    }
    if (message.includes('power') || message.includes('voltage')) {
      return MarineFailureType.POWER_FLUCTUATION;
    }
    if (message.includes('validation') || message.includes('quality')) {
      return MarineFailureType.DATA_QUALITY;
    }
    if (message.includes('resource') || message.includes('memory')) {
      return MarineFailureType.RESOURCE_EXHAUSTION;
    }

    return MarineFailureType.ENVIRONMENTAL; // Default for unknown failures
  }

  /**
   * Handle successful operation
   */
  private onSuccess(executionTime: number): void {
    this.metrics.successfulRequests++;
    this.metrics.lastSuccessTime = new Date();
    this.updateAverageResponseTime(executionTime);

    if (this.metrics.state === CircuitBreakerState.HALF_OPEN) {
      // Count successes in half-open state
      const recentSuccesses = this.calculateRecentSuccesses();
      if (recentSuccesses >= this.config.successThreshold) {
        this.transitionToClosed();
      }
    }
  }

  /**
   * Handle failed operation
   */
  private onFailure(failureType: MarineFailureTypeType, executionTime: number): void {
    this.metrics.failedRequests++;
    this.metrics.lastFailureTime = new Date();
    this.updateAverageResponseTime(executionTime);

    // Update marine-specific failure counters
    this.updateMarineFailureCounters(failureType);

    // Check if circuit should open
    if (this.shouldOpenCircuit()) {
      this.transitionToOpen(failureType);
    }
  }

  /**
   * Update marine-specific failure counters
   */
  private updateMarineFailureCounters(failureType: MarineFailureTypeType): void {
    switch (failureType) {
      case MarineFailureType.ENVIRONMENTAL:
        this.metrics.environmentalFailures++;
        break;
      case MarineFailureType.POWER_FLUCTUATION:
        this.metrics.powerRelatedFailures++;
        break;
      case MarineFailureType.CONNECTIVITY_LOSS:
        this.metrics.connectivityFailures++;
        break;
      case MarineFailureType.SENSOR_FAILURE:
        this.metrics.sensorFailures++;
        break;
    }
  }

  /**
   * Check if circuit breaker should open
   */
  private shouldOpenCircuit(): boolean {
    // Don't open if we haven't reached minimum volume
    if (this.metrics.totalRequests < this.config.volumeThreshold) {
      return false;
    }

    // Check failure count threshold
    if (this.metrics.failedRequests >= this.config.failureThreshold) {
      return true;
    }

    // Check error percentage threshold
    const errorPercentage = (this.metrics.failedRequests / this.metrics.totalRequests) * 100;
    if (errorPercentage >= this.config.errorPercentageThreshold) {
      return true;
    }

    // Check slow call rate threshold
    const slowCallPercentage = (this.metrics.slowCalls / this.metrics.totalRequests) * 100;
    if (slowCallPercentage >= this.config.slowCallRateThreshold) {
      return true;
    }

    return false;
  }

  /**
   * Transition to OPEN state
   */
  private transitionToOpen(failureType: MarineFailureTypeType): void {
    this.metrics.state = CircuitBreakerState.OPEN;
    this.metrics.stateChangedAt = new Date();
    this.metrics.nextAttemptAt = new Date(Date.now() + this.config.timeout);

    this.emitEvent('circuit_breaker_opened', 'automatic', {
      failureType,
      failureCount: this.metrics.failedRequests,
      totalRequests: this.metrics.totalRequests,
      errorPercentage: (this.metrics.failedRequests / this.metrics.totalRequests) * 100
    }, 'warning');
  }

  /**
   * Transition to HALF_OPEN state
   */
  private transitionToHalfOpen(): void {
    this.metrics.state = CircuitBreakerState.HALF_OPEN;
    this.metrics.stateChangedAt = new Date();
    delete this.metrics.nextAttemptAt;

    this.emitEvent('circuit_breaker_opened', 'automatic', {
      previousState: CircuitBreakerState.OPEN,
      newState: CircuitBreakerState.HALF_OPEN,
      reason: 'Timeout period elapsed, testing service'
    }, 'info');
  }

  /**
   * Transition to CLOSED state
   */
  private transitionToClosed(): void {
    this.metrics.state = CircuitBreakerState.CLOSED;
    this.metrics.stateChangedAt = new Date();
    delete this.metrics.nextAttemptAt;

    this.emitEvent('circuit_breaker_closed', 'automatic', {
      successCount: this.calculateRecentSuccesses(),
      reason: 'Service recovered successfully'
    }, 'info');
  }

  /**
   * Calculate recent successes (for half-open state)
   */
  private calculateRecentSuccesses(): number {
    // Simplified: return recent success count
    // In a full implementation, this would track recent operations
    return this.metrics.successfulRequests;
  }

  /**
   * Update average response time
   */
  private updateAverageResponseTime(executionTime: number): void {
    const totalTime = this.metrics.averageResponseTime * (this.metrics.totalRequests - 1);
    this.metrics.averageResponseTime = (totalTime + executionTime) / this.metrics.totalRequests;
  }

  /**
   * Adjust configuration for marine conditions
   */
  private adjustForMarineConditions(): void {
    if (!this.marineEnvironment) return;

    // Adjust thresholds based on expected failure rate
    const baseFailureThreshold = this.config.failureThreshold;
    const adjustedThreshold = Math.ceil(baseFailureThreshold * this.marineEnvironment.expectedFailureRate);
    
    // Don't make it too sensitive or too tolerant
    this.config.failureThreshold = Math.max(3, Math.min(adjustedThreshold, 20));

    // Adjust timeout based on conditions
    if (this.marineEnvironment.criticalOperationsOnly) {
      this.config.environmentalAdjustments.roughSeas = true;
      this.config.environmentalAdjustments.limitedConnectivity = true;
    }
  }

  /**
   * Get current marine context
   */
  private getMarineContext() {
    const connectivityQuality = this.marineEnvironment?.connectivityQuality ?? 0;
    const connectivityStatus: 'online' | 'intermittent' | 'offline' = 
      connectivityQuality > 0.7 ? 'online' : 
      connectivityQuality > 0.3 ? 'intermittent' : 'offline';

    return {
      operationalContext: this.config.operationalContext,
      environmentalConditions: this.marineEnvironment || {},
      powerStatus: this.marineEnvironment?.powerStatus || 'normal',
      connectivityStatus
    };
  }

  /**
   * Emit resilience event
   */
  private emitEvent(
    eventType: ResilienceEvent['eventType'],
    component: string,
    details: Record<string, any>,
    severity: ResilienceEvent['severity']
  ): void {
    const event: ResilienceEvent = {
      timestamp: new Date(),
      eventType,
      component,
      details,
      severity,
      marineContext: {
        operationalContext: this.config.operationalContext,
        failureType: details['failureType'],
        environmentalImpact: this.config.environmentalAdjustments.roughSeas,
        safetyImpact: this.config.operationalContext === OperationalContext.EMERGENCY
      }
    };

    this.eventListeners.forEach(listener => {
      try {
        listener(event);
      } catch (error) {
        console.error('Error in circuit breaker event listener:', error);
      }
    });
  }

  /**
   * Initialize metrics
   */
  private initializeMetrics(): CircuitBreakerMetrics {
    return {
      state: CircuitBreakerState.CLOSED,
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      rejectedRequests: 0,
      averageResponseTime: 0,
      slowCalls: 0,
      environmentalFailures: 0,
      powerRelatedFailures: 0,
      connectivityFailures: 0,
      sensorFailures: 0,
      stateChangedAt: new Date()
    };
  }
}
