/**
 * Hierarchical timeout management for marine operations
 */

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

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

/**
 * Timeout execution context
 */
export interface TimeoutExecutionContext {
  operationName: string;
  operationalContext: OperationalContextType;
  marineSystemType: 'navigation' | 'safety' | 'comfort' | 'maintenance';
  priority: 'critical' | 'normal' | 'low';
  customTimeouts?: Partial<TimeoutConfig>;
}

/**
 * Timeout execution result
 */
export interface TimeoutExecutionResult<T> {
  success: boolean;
  data?: T;
  error?: Error;
  executionTime: number;
  timeoutApplied: number;
  timedOut: boolean;
  retryCount: number;
}

/**
 * Active timeout tracking
 */
interface ActiveTimeout {
  id: string;
  operationName: string;
  startTime: Date;
  timeoutMs: number;
  context: TimeoutExecutionContext;
  timer: NodeJS.Timeout;
}

/**
 * Hierarchical timeout manager for marine operations
 */
export class TimeoutManager {
  private config: TimeoutConfig;
  private metrics: TimeoutMetrics;
  private activeTimeouts: Map<string, ActiveTimeout> = new Map();
  private eventListeners: ((event: ResilienceEvent) => void)[] = [];
  private marineEnvironment?: MarineEnvironmentStatus;
  private nextTimeoutId = 1;

  constructor(config: Partial<TimeoutConfig> = {}) {
    this.config = {
      operationTimeout: 5000,      // 5 seconds
      serviceTimeout: 30000,       // 30 seconds
      systemTimeout: 120000,       // 2 minutes
      environmentalMultiplier: 1.0,
      operationalContext: OperationalContext.SAILING,
      maxRetries: 3,
      retryBackoffMultiplier: 2.0,
      maxRetryDelay: 30000,        // 30 seconds
      criticalOperationTimeout: 2000,    // 2 seconds
      sensorReadTimeout: 3000,            // 3 seconds
      networkOperationTimeout: 15000,    // 15 seconds
      ...config
    };

    this.metrics = this.initializeMetrics();
  }

  /**
   * Execute operation with hierarchical timeout protection
   */
  async execute<T>(
    operation: () => Promise<T>,
    context: TimeoutExecutionContext
  ): Promise<TimeoutExecutionResult<T>> {
    const startTime = Date.now();
    let retryCount = 0;
    let lastError: Error | undefined;

    // Calculate timeout based on context and marine conditions
    const timeoutMs = this.calculateTimeout(context);

    while (retryCount <= this.config.maxRetries) {
      try {
        const result = await this.executeWithTimeout(operation, timeoutMs, context);
        const executionTime = Date.now() - startTime;

        // Update metrics for successful operation
        this.updateMetrics(executionTime, false, retryCount);

        return {
          success: true,
          data: result,
          executionTime,
          timeoutApplied: timeoutMs,
          timedOut: false,
          retryCount
        };

      } catch (error) {
        lastError = error as Error;
        const isTimeout = this.isTimeoutError(error as Error);

        if (isTimeout) {
          this.metrics.timedOutOperations++;
          
          // Update marine-specific timeout counters
          this.updateMarineTimeoutCounters(context);
        }

        // Check if we should retry
        if (retryCount < this.config.maxRetries && this.shouldRetry(error as Error, context)) {
          retryCount++;
          this.metrics.totalRetries++;

          // Calculate retry delay with exponential backoff
          const retryDelay = this.calculateRetryDelay(retryCount);
          
          this.emitEvent('retry', context.operationName, {
            retryCount,
            retryDelay,
            error: (error as Error).message,
            isTimeout,
            marineSystemType: context.marineSystemType
          }, 'warning');

          // Wait before retry
          await this.delay(retryDelay);
        } else {
          break;
        }
      }
    }

    // All retries exhausted
    const executionTime = Date.now() - startTime;
    const isTimeout = lastError ? this.isTimeoutError(lastError) : false;

    this.updateMetrics(executionTime, isTimeout, retryCount);

    if (retryCount > 0) {
      this.metrics.failedRetries++;
    }

    return {
      success: false,
      error: lastError || new Error('Unknown error occurred'),
      executionTime,
      timeoutApplied: timeoutMs,
      timedOut: isTimeout,
      retryCount
    };
  }

  /**
   * Update marine environment for adaptive timeouts
   */
  updateMarineEnvironment(environment: MarineEnvironmentStatus): void {
    this.marineEnvironment = environment;
    
    // Update environmental multiplier based on conditions
    this.config.environmentalMultiplier = environment.recommendedTimeoutMultiplier;

    this.emitEvent('recovery', 'timeout-manager', {
      action: 'marine_environment_updated',
      environmentalMultiplier: this.config.environmentalMultiplier,
      seaState: environment.seaState,
      powerStatus: environment.powerStatus
    }, 'info');
  }

  /**
   * Get current timeout metrics
   */
  getMetrics(): TimeoutMetrics {
    return { ...this.metrics };
  }

  /**
   * Get active timeouts status
   */
  getActiveTimeouts() {
    return Array.from(this.activeTimeouts.values()).map(timeout => ({
      id: timeout.id,
      operationName: timeout.operationName,
      startTime: timeout.startTime,
      timeoutMs: timeout.timeoutMs,
      elapsedTime: Date.now() - timeout.startTime.getTime(),
      remainingTime: Math.max(0, timeout.timeoutMs - (Date.now() - timeout.startTime.getTime())),
      context: {
        operationalContext: timeout.context.operationalContext,
        marineSystemType: timeout.context.marineSystemType,
        priority: timeout.context.priority
      }
    }));
  }

  /**
   * Cancel all active timeouts (emergency)
   */
  cancelAllTimeouts(reason: string = 'Emergency cancellation'): void {
    const cancelledCount = this.activeTimeouts.size;

    for (const timeout of this.activeTimeouts.values()) {
      clearTimeout(timeout.timer);
    }

    this.activeTimeouts.clear();

    this.emitEvent('recovery', 'timeout-manager', {
      action: 'cancel_all_timeouts',
      cancelledCount,
      reason
    }, 'warning');
  }

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

  /**
   * Execute operation with timeout
   */
  private async executeWithTimeout<T>(
    operation: () => Promise<T>,
    timeoutMs: number,
    context: TimeoutExecutionContext
  ): Promise<T> {
    const timeoutId = `timeout-${this.nextTimeoutId++}`;
    
    return new Promise<T>((resolve, reject) => {
      // Create timeout
      const timer = setTimeout(() => {
        this.activeTimeouts.delete(timeoutId);
        reject(new Error(`Operation '${context.operationName}' timed out after ${timeoutMs}ms`));
      }, timeoutMs);

      // Track active timeout
      const activeTimeout: ActiveTimeout = {
        id: timeoutId,
        operationName: context.operationName,
        startTime: new Date(),
        timeoutMs,
        context,
        timer
      };

      this.activeTimeouts.set(timeoutId, activeTimeout);

      // Execute operation
      operation()
        .then(result => {
          clearTimeout(timer);
          this.activeTimeouts.delete(timeoutId);
          resolve(result);
        })
        .catch(error => {
          clearTimeout(timer);
          this.activeTimeouts.delete(timeoutId);
          reject(error);
        });
    });
  }

  /**
   * Calculate timeout based on context and marine conditions
   */
  private calculateTimeout(context: TimeoutExecutionContext): number {
    let baseTimeout: number;

    // Determine base timeout based on operation type and priority
    if (context.priority === 'critical') {
      baseTimeout = this.config.criticalOperationTimeout;
    } else {
      switch (context.marineSystemType) {
        case 'navigation':
          baseTimeout = this.config.sensorReadTimeout;
          break;
        case 'safety':
          baseTimeout = this.config.criticalOperationTimeout;
          break;
        case 'comfort':
          baseTimeout = this.config.networkOperationTimeout;
          break;
        case 'maintenance':
          baseTimeout = this.config.operationTimeout * 3; // Longer for maintenance
          break;
        default:
          baseTimeout = this.config.operationTimeout;
      }
    }

    // Apply custom timeouts if provided
    if (context.customTimeouts?.operationTimeout) {
      baseTimeout = context.customTimeouts.operationTimeout;
    }

    // Apply environmental multiplier
    let timeout = baseTimeout * this.config.environmentalMultiplier;

    // Apply operational context adjustments
    switch (context.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;
      case OperationalContext.DOCKED:
        timeout *= 1.2; // Slightly more relaxed when docked
        break;
    }

    // Apply marine environment specific adjustments
    if (this.marineEnvironment) {
      if (this.marineEnvironment.criticalOperationsOnly && context.priority !== 'critical') {
        timeout *= 0.5; // Reduce timeouts for non-critical operations
      }

      if (this.marineEnvironment.powerStatus === 'critical') {
        timeout *= 0.7; // Reduce timeouts to save power
      }
    }

    // Ensure timeout is within reasonable bounds
    const minTimeout = context.priority === 'critical' ? 1000 : 2000; // 1-2 seconds minimum
    const maxTimeout = context.marineSystemType === 'maintenance' ? 300000 : 60000; // 1-5 minutes maximum

    return Math.max(minTimeout, Math.min(timeout, maxTimeout));
  }

  /**
   * Check if error is a timeout error
   */
  private isTimeoutError(error: Error): boolean {
    return error.message.toLowerCase().includes('timeout') ||
           error.message.toLowerCase().includes('timed out');
  }

  /**
   * Check if operation should be retried
   */
  private shouldRetry(error: Error, context: TimeoutExecutionContext): boolean {
    // Always retry timeouts
    if (this.isTimeoutError(error)) {
      return true;
    }

    // Don't retry critical safety operations - fail fast
    if (context.marineSystemType === 'safety' && context.priority === 'critical') {
      return false;
    }

    // Retry network-related errors
    if (error.message.toLowerCase().includes('network') ||
        error.message.toLowerCase().includes('connection')) {
      return true;
    }

    // Don't retry validation errors
    if (error.message.toLowerCase().includes('validation')) {
      return false;
    }

    // Default: retry for most errors
    return true;
  }

  /**
   * Calculate retry delay with exponential backoff
   */
  private calculateRetryDelay(retryCount: number): number {
    const baseDelay = 1000; // 1 second
    const delay = baseDelay * Math.pow(this.config.retryBackoffMultiplier, retryCount - 1);
    
    // Add jitter to prevent thundering herd
    const jitter = Math.random() * 0.1 * delay;
    
    return Math.min(delay + jitter, this.config.maxRetryDelay);
  }

  /**
   * Update marine-specific timeout counters
   */
  private updateMarineTimeoutCounters(context: TimeoutExecutionContext): void {
    if (this.marineEnvironment?.seaState === 'very_rough' || 
        this.marineEnvironment?.weather === 'storm') {
      this.metrics.environmentalTimeouts++;
    }

    if (context.priority === 'critical') {
      this.metrics.criticalOperationTimeouts++;
    }

    if (context.marineSystemType === 'navigation' || 
        context.operationName.toLowerCase().includes('sensor')) {
      this.metrics.sensorTimeouts++;
    }
  }

  /**
   * Update metrics
   */
  private updateMetrics(executionTime: number, timedOut: boolean, retryCount: number): void {
    this.metrics.totalOperations++;
    
    if (timedOut) {
      this.metrics.timedOutOperations++;
    }

    // Update execution time metrics
    const totalTime = this.metrics.averageOperationTime * (this.metrics.totalOperations - 1);
    this.metrics.averageOperationTime = (totalTime + executionTime) / this.metrics.totalOperations;
    this.metrics.maxOperationTime = Math.max(this.metrics.maxOperationTime, executionTime);

    // Update retry metrics
    if (retryCount > 0) {
      if (!timedOut) {
        this.metrics.successfulRetries++;
      }
    }
  }

  /**
   * 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: this.config.operationalContext,
        environmentalImpact: this.marineEnvironment?.seaState === 'very_rough',
        safetyImpact: details['marineSystemType'] === 'safety'
      }
    };

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

  /**
   * Initialize metrics
   */
  private initializeMetrics(): TimeoutMetrics {
    return {
      totalOperations: 0,
      timedOutOperations: 0,
      averageOperationTime: 0,
      maxOperationTime: 0,
      totalRetries: 0,
      successfulRetries: 0,
      failedRetries: 0,
      environmentalTimeouts: 0,
      criticalOperationTimeouts: 0,
      sensorTimeouts: 0
    };
  }
}

/**
 * Default timeout manager instance
 */
export const defaultTimeoutManager = new TimeoutManager();

/**
 * Convenience function to execute with timeout protection
 */
export async function executeWithTimeout<T>(
  operation: () => Promise<T>,
  context: TimeoutExecutionContext
): Promise<TimeoutExecutionResult<T>> {
  return defaultTimeoutManager.execute(operation, context);
}

/**
 * Pre-configured timeout contexts for marine operations
 */
export const TimeoutContexts = {
  /**
   * Navigation sensor reading
   */
  navigationSensor: (operationName: string, operationalContext: OperationalContextType = OperationalContext.SAILING): TimeoutExecutionContext => ({
    operationName,
    operationalContext,
    marineSystemType: 'navigation',
    priority: 'critical'
  }),

  /**
   * Safety system operation
   */
  safetyOperation: (operationName: string, operationalContext: OperationalContextType = OperationalContext.SAILING): TimeoutExecutionContext => ({
    operationName,
    operationalContext,
    marineSystemType: 'safety',
    priority: 'critical'
  }),

  /**
   * Network communication
   */
  networkOperation: (operationName: string, operationalContext: OperationalContextType = OperationalContext.SAILING): TimeoutExecutionContext => ({
    operationName,
    operationalContext,
    marineSystemType: 'comfort',
    priority: 'normal'
  }),

  /**
   * Maintenance operation
   */
  maintenanceOperation: (operationName: string): TimeoutExecutionContext => ({
    operationName,
    operationalContext: OperationalContext.MAINTENANCE,
    marineSystemType: 'maintenance',
    priority: 'normal'
  }),

  /**
   * Comfort system operation
   */
  comfortOperation: (operationName: string, operationalContext: OperationalContextType = OperationalContext.SAILING): TimeoutExecutionContext => ({
    operationName,
    operationalContext,
    marineSystemType: 'comfort',
    priority: 'low'
  })
};
