/**
 * Retry mechanism with marine-specific adaptations
 */

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

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

/**
 * Retry configuration
 */
export interface RetryConfig {
  /**
   * Maximum number of retry attempts
   */
  maxRetries: number;
  
  /**
   * Base delay between retries in ms
   */
  baseDelayMs: number;
  
  /**
   * Multiplier for exponential backoff
   */
  backoffMultiplier: number;
  
  /**
   * Maximum delay between retries in ms
   */
  maxDelayMs: number;
  
  /**
   * Whether to add jitter to retry delays
   */
  useJitter: boolean;
  
  /**
   * Failure types that should trigger retry
   */
  retryableFailures: MarineFailureTypeType[];
  
  /**
   * Marine-specific settings
   */
  marineSettings: {
    /**
     * Whether to adapt retry behavior to marine conditions
     */
    adaptToMarineConditions: boolean;
    
    /**
     * Whether to adapt to power status
     */
    powerAware: boolean;
    
    /**
     * Operational contexts where retries are allowed
     */
    allowedContexts: OperationalContextType[];
    
    /**
     * Whether to limit retries in rough seas
     */
    limitInRoughSeas: boolean;
  };
}

/**
 * Retry execution context
 */
export interface RetryExecutionContext {
  /**
   * Operation name for logging and metrics
   */
  operationName: string;
  
  /**
   * Current operational context
   */
  operationalContext: OperationalContextType;
  
  /**
   * Marine system type
   */
  marineSystemType: 'navigation' | 'safety' | 'comfort' | 'maintenance';
  
  /**
   * Operation priority
   */
  priority: 'critical' | 'normal' | 'low';
  
  /**
   * Custom retry configuration overrides
   */
  customRetryConfig?: Partial<RetryConfig>;
}

/**
 * Retry execution result
 */
export interface RetryExecutionResult<T> {
  /**
   * Whether the operation was successful
   */
  success: boolean;
  
  /**
   * Result data if successful
   */
  data?: T;
  
  /**
   * Error if failed
   */
  error?: Error;
  
  /**
   * Number of retry attempts made
   */
  retryCount: number;
  
  /**
   * Total execution time including retries
   */
  totalExecutionTime: number;
  
  /**
   * Delays between retry attempts
   */
  retryDelays: number[];
  
  /**
   * Whether maximum retries were reached
   */
  maxRetriesReached: boolean;
  
  /**
   * Marine context during execution
   */
  marineContext: {
    operationalContext: OperationalContextType;
    environmentalConditions?: Partial<MarineEnvironmentStatus>;
  };
}

/**
 * Retry metrics
 */
export interface RetryMetrics {
  /**
   * Total operations attempted
   */
  totalOperations: number;
  
  /**
   * Operations that succeeded without retry
   */
  successWithoutRetry: number;
  
  /**
   * Operations that succeeded after retry
   */
  successWithRetry: number;
  
  /**
   * Operations that failed after all retries
   */
  failedAfterRetry: number;
  
  /**
   * Total retry attempts
   */
  totalRetryAttempts: number;
  
  /**
   * Average retries per operation
   */
  averageRetriesPerOperation: number;
  
  /**
   * Maximum retries for a single operation
   */
  maxRetriesForOperation: number;
  
  /**
   * Average delay between retries
   */
  averageRetryDelay: number;
  
  /**
   * Marine-specific metrics
   */
  marineMetrics: {
    /**
     * Retries due to environmental conditions
     */
    environmentalRetries: number;
    
    /**
     * Retries due to connectivity issues
     */
    connectivityRetries: number;
    
    /**
     * Retries due to power issues
     */
    powerRetries: number;
    
    /**
     * Retries due to sensor failures
     */
    sensorRetries: number;
  };
}

/**
 * Retry manager for marine operations
 */
export class RetryManager {
  private config: RetryConfig;
  private metrics: RetryMetrics;
  private eventListeners: ((event: ResilienceEvent) => void)[] = [];
  private marineEnvironment?: MarineEnvironmentStatus;
  
  constructor(config: Partial<RetryConfig> = {}) {
    this.config = {
      maxRetries: 3,
      baseDelayMs: 1000,
      backoffMultiplier: 2.0,
      maxDelayMs: 30000,
      useJitter: true,
      retryableFailures: [
        MarineFailureType.CONNECTIVITY_LOSS,
        MarineFailureType.TIMEOUT,
        MarineFailureType.ENVIRONMENTAL,
        MarineFailureType.SENSOR_FAILURE
      ],
      marineSettings: {
        adaptToMarineConditions: true,
        powerAware: true,
        allowedContexts: [
          OperationalContext.SAILING,
          OperationalContext.MOTORING,
          OperationalContext.ANCHORED,
          OperationalContext.DOCKED,
          OperationalContext.MAINTENANCE
        ],
        limitInRoughSeas: true
      },
      ...config
    };
    
    this.metrics = this.initializeMetrics();
  }
  
  /**
   * Execute operation with retry
   */
  async execute<T>(
    operation: () => Promise<T>,
    context: RetryExecutionContext
  ): Promise<RetryExecutionResult<T>> {
    const startTime = Date.now();
    let retryCount = 0;
    let lastError: Error | undefined;
    const retryDelays: number[] = [];
    
    // Apply custom config if provided
    const effectiveConfig = this.getEffectiveConfig(context);
    
    // Check if retries are allowed in current context
    if (!this.isRetryAllowed(context.operationalContext)) {
      try {
        const result = await operation();
        this.updateMetrics(0, true);
        
        return {
          success: true,
          data: result,
          retryCount: 0,
          totalExecutionTime: Date.now() - startTime,
          retryDelays: [],
          maxRetriesReached: false,
          marineContext: {
            operationalContext: context.operationalContext,
            environmentalConditions: this.marineEnvironment ? { ...this.marineEnvironment } : undefined as unknown as Partial<MarineEnvironmentStatus>
          }
        };
      } catch (error) {
        this.updateMetrics(0, false);
        
        return {
          success: false,
          error: error as Error,
          retryCount: 0,
          totalExecutionTime: Date.now() - startTime,
          retryDelays: [],
          maxRetriesReached: false,
          marineContext: {
            operationalContext: context.operationalContext,
            environmentalConditions: this.marineEnvironment ? { ...this.marineEnvironment } : undefined as unknown as Partial<MarineEnvironmentStatus>
          }
        };
      }
    }
    
    // Execute with retry
    while (retryCount <= effectiveConfig.maxRetries) {
      try {
        const result = await operation();
        
        // Success - update metrics and return
        if (retryCount === 0) {
          this.updateMetrics(0, true);
        } else {
          this.updateMetrics(retryCount, true);
        }
        
        return {
          success: true,
          data: result,
          retryCount,
          totalExecutionTime: Date.now() - startTime,
          retryDelays,
          maxRetriesReached: false,
          marineContext: {
            operationalContext: context.operationalContext,
            environmentalConditions: this.marineEnvironment ? { ...this.marineEnvironment } : undefined as unknown as Partial<MarineEnvironmentStatus>
          }
        };
      } catch (error) {
        lastError = error as Error;
        
        // Check if we should retry this error
        if (!this.isRetryableError(error as Error, effectiveConfig)) {
          break;
        }
        
        // Check if we have retries remaining
        if (retryCount < effectiveConfig.maxRetries) {
          retryCount++;
          
          // Calculate delay for this retry
          const delay = this.calculateRetryDelay(retryCount, effectiveConfig, context);
          retryDelays.push(delay);
          
          // Emit retry event
          this.emitEvent('retry', context.operationName, {
            retryCount,
            delay,
            error: (error as Error).message,
            marineSystemType: context.marineSystemType,
            priority: context.priority
          }, 'warning');
          
          // Wait before retry
          await this.delay(delay);
        } else {
          break;
        }
      }
    }
    
    // All retries exhausted or non-retryable error
    this.updateMetrics(retryCount, false);
    
    return {
      success: false,
      error: lastError || new Error('Unknown error'),
      retryCount,
      totalExecutionTime: Date.now() - startTime,
      retryDelays,
      maxRetriesReached: retryCount >= effectiveConfig.maxRetries,
      marineContext: {
        operationalContext: context.operationalContext,
        environmentalConditions: this.marineEnvironment ? { ...this.marineEnvironment } : undefined as unknown as Partial<MarineEnvironmentStatus>
      }
    };
  }
  
  /**
   * Update marine environment for adaptive retry behavior
   */
  updateMarineEnvironment(environment: MarineEnvironmentStatus): void {
    this.marineEnvironment = environment;
    
    this.emitEvent('recovery', 'retry-manager', {
      action: 'marine_environment_updated',
      seaState: environment.seaState,
      powerStatus: environment.powerStatus,
      criticalOperationsOnly: environment.criticalOperationsOnly
    }, 'info');
  }
  
  /**
   * Get current retry metrics
   */
  getMetrics(): RetryMetrics {
    return { ...this.metrics };
  }
  
  /**
   * Add event listener
   */
  onEvent(listener: (event: ResilienceEvent) => void): void {
    this.eventListeners.push(listener);
  }
  
  /**
   * Remove event listener
   */
  removeEventListener(listener: (event: ResilienceEvent) => void): void {
    const index = this.eventListeners.indexOf(listener);
    if (index > -1) {
      this.eventListeners.splice(index, 1);
    }
  }
  
  /**
   * Get effective configuration with overrides applied
   */
  private getEffectiveConfig(context: RetryExecutionContext): RetryConfig {
    if (!context.customRetryConfig) {
      return this.config;
    }
    
    return {
      ...this.config,
      ...context.customRetryConfig,
      marineSettings: {
        ...this.config.marineSettings,
        ...(context.customRetryConfig.marineSettings || {})
      }
    };
  }
  
  /**
   * Check if retry is allowed in current operational context
   */
  private isRetryAllowed(operationalContext: OperationalContextType): boolean {
    // Don't retry in emergency context
    if (operationalContext === OperationalContext.EMERGENCY) {
      return false;
    }
    
    // Check if context is in allowed contexts
    if (!this.config.marineSettings.allowedContexts.includes(operationalContext)) {
      return false;
    }
    
    // Check marine environment conditions
    if (this.marineEnvironment) {
      // Check sea conditions if configured
      if (this.config.marineSettings.limitInRoughSeas && 
          (this.marineEnvironment.seaState === 'rough' || 
           this.marineEnvironment.seaState === 'very_rough')) {
        return false;
      }
      
      // Check power status if configured
      if (this.config.marineSettings.powerAware && 
          this.marineEnvironment.powerStatus === 'critical') {
        return false;
      }
      
      // Check critical operations only
      if (this.marineEnvironment.criticalOperationsOnly) {
        return false;
      }
    }
    
    return true;
  }
  
  /**
   * Check if error is retryable
   */
  private isRetryableError(error: Error, config: RetryConfig): boolean {
    const errorMessage = error.message.toLowerCase();
    
    // Check for specific marine failure types
    for (const failureType of config.retryableFailures) {
      if (errorMessage.includes(failureType.toLowerCase())) {
        return true;
      }
    }
    
    // Common retryable errors
    if (errorMessage.includes('timeout') || 
        errorMessage.includes('connection') ||
        errorMessage.includes('network') ||
        errorMessage.includes('temporary')) {
      return true;
    }
    
    // Non-retryable errors
    if (errorMessage.includes('validation') ||
        errorMessage.includes('not found') ||
        errorMessage.includes('permission') ||
        errorMessage.includes('unauthorized')) {
      return false;
    }
    
    // Default to retryable for unknown errors
    return true;
  }
  
  /**
   * Calculate retry delay with exponential backoff and jitter
   */
  private calculateRetryDelay(
    retryCount: number,
    config: RetryConfig,
    context: RetryExecutionContext
  ): number {
    // Base delay with exponential backoff
    let delay = config.baseDelayMs * Math.pow(config.backoffMultiplier, retryCount - 1);
    
    // Add jitter if configured
    if (config.useJitter) {
      const jitter = Math.random() * 0.2 * delay; // 0-20% jitter
      delay += jitter;
    }
    
    // Apply marine environment adjustments
    if (this.marineEnvironment && config.marineSettings.adaptToMarineConditions) {
      // Adjust for sea state
      if (this.marineEnvironment.seaState === 'moderate') {
        delay *= 1.2; // 20% longer delays in moderate seas
      }
      
      // Adjust for power status
      if (this.marineEnvironment.powerStatus === 'conservation') {
        delay *= 1.5; // 50% longer delays in power conservation mode
      }
      
      // Adjust for connectivity
      if (this.marineEnvironment.connectivityQuality < 0.5) {
        delay *= 1.3; // 30% longer delays with poor connectivity
      }
    }
    
    // Adjust for operational context
    switch (context.operationalContext) {
      case OperationalContext.MAINTENANCE:
        delay *= 0.8; // Faster retries during maintenance
        break;
      case OperationalContext.ANCHORED:
        delay *= 1.2; // Slower retries when anchored
        break;
      case OperationalContext.DOCKED:
        delay *= 0.9; // Slightly faster retries when docked
        break;
    }
    
    // Adjust for priority
    switch (context.priority) {
      case 'critical':
        delay *= 0.7; // Faster retries for critical operations
        break;
      case 'low':
        delay *= 1.3; // Slower retries for low priority operations
        break;
    }
    
    // Ensure delay is within bounds
    return Math.min(Math.max(delay, config.baseDelayMs), config.maxDelayMs);
  }
  
  /**
   * Update metrics
   */
  private updateMetrics(retryCount: number, success: boolean): void {
    this.metrics.totalOperations++;
    
    if (success) {
      if (retryCount === 0) {
        this.metrics.successWithoutRetry++;
      } else {
        this.metrics.successWithRetry++;
        this.metrics.totalRetryAttempts += retryCount;
        this.metrics.maxRetriesForOperation = Math.max(
          this.metrics.maxRetriesForOperation,
          retryCount
        );
        
        // Update marine-specific metrics
        if (this.marineEnvironment) {
          if (this.marineEnvironment.seaState === 'rough' || 
              this.marineEnvironment.seaState === 'very_rough') {
            this.metrics.marineMetrics.environmentalRetries += retryCount;
          }
          
          if (this.marineEnvironment.connectivityQuality < 0.5) {
            this.metrics.marineMetrics.connectivityRetries += retryCount;
          }
          
          if (this.marineEnvironment.powerStatus === 'conservation' || 
              this.marineEnvironment.powerStatus === 'critical') {
            this.metrics.marineMetrics.powerRetries += retryCount;
          }
        }
      }
    } else {
      this.metrics.failedAfterRetry++;
      this.metrics.totalRetryAttempts += retryCount;
    }
    
    // Update average metrics
    if (this.metrics.totalOperations > 0) {
      this.metrics.averageRetriesPerOperation = 
        this.metrics.totalRetryAttempts / this.metrics.totalOperations;
    }
  }
  
  /**
   * Delay helper function
   */
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
  
  /**
   * 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: details['operationalContext'] || OperationalContext.SAILING,
        environmentalImpact: this.marineEnvironment?.seaState === 'very_rough',
        safetyImpact: details['marineSystemType'] === 'safety'
      }
    };
    
    this.eventListeners.forEach(listener => {
      try {
        listener(event);
      } catch (error) {
        console.error('Error in retry manager event listener:', error);
      }
    });
  }
  
  /**
   * Initialize metrics
   */
  private initializeMetrics(): RetryMetrics {
    return {
      totalOperations: 0,
      successWithoutRetry: 0,
      successWithRetry: 0,
      failedAfterRetry: 0,
      totalRetryAttempts: 0,
      averageRetriesPerOperation: 0,
      maxRetriesForOperation: 0,
      averageRetryDelay: 0,
      marineMetrics: {
        environmentalRetries: 0,
        connectivityRetries: 0,
        powerRetries: 0,
        sensorRetries: 0
      }
    };
  }
}

/**
 * Default retry manager instance
 */
export const defaultRetryManager = new RetryManager();

/**
 * Convenience function to execute with retry
 */
export async function executeWithRetry<T>(
  operation: () => Promise<T>,
  context: RetryExecutionContext
): Promise<RetryExecutionResult<T>> {
  return defaultRetryManager.execute(operation, context);
}

/**
 * Pre-configured retry contexts for marine operations
 */
export const RetryContexts = {
  /**
   * Navigation sensor operations
   */
  navigationSensor: (operationName: string, operationalContext: OperationalContextType = OperationalContext.SAILING): RetryExecutionContext => ({
    operationName,
    operationalContext,
    marineSystemType: 'navigation',
    priority: 'critical'
  }),
  
  /**
   * Safety system operations
   */
  safetyOperation: (operationName: string, operationalContext: OperationalContextType = OperationalContext.SAILING): RetryExecutionContext => ({
    operationName,
    operationalContext,
    marineSystemType: 'safety',
    priority: 'critical'
  }),
  
  /**
   * Network operations
   */
  networkOperation: (operationName: string, operationalContext: OperationalContextType = OperationalContext.SAILING): RetryExecutionContext => ({
    operationName,
    operationalContext,
    marineSystemType: 'comfort',
    priority: 'normal',
    customRetryConfig: {
      maxRetries: 5,
      baseDelayMs: 2000,
      retryableFailures: [
        MarineFailureType.CONNECTIVITY_LOSS,
        MarineFailureType.TIMEOUT
      ]
    }
  }),
  
  /**
   * Maintenance operations
   */
  maintenanceOperation: (operationName: string): RetryExecutionContext => ({
    operationName,
    operationalContext: OperationalContext.MAINTENANCE,
    marineSystemType: 'maintenance',
    priority: 'normal',
    customRetryConfig: {
      maxRetries: 10,
      baseDelayMs: 5000,
      maxDelayMs: 60000
    }
  }),
  
  /**
   * Comfort system operations
   */
  comfortOperation: (operationName: string, operationalContext: OperationalContextType = OperationalContext.SAILING): RetryExecutionContext => ({
    operationName,
    operationalContext,
    marineSystemType: 'comfort',
    priority: 'low',
    customRetryConfig: {
      maxRetries: 2,
      baseDelayMs: 3000
    }
  })
};
