/**
 * Recovery orchestrator for self-healing mechanisms
 */

import { v4 as uuidv4 } from 'uuid';
import {
  HealthCheckResult,
  HealthStatus,
  HealthStatusType,
  OperationalContext,
  OperationalContextType,
  MarineSystemTypeType,
  MarineEnvironmentStatus
} from '../types';

import {
  BaseRecoveryAction,
  RecoveryActionConfig,
  RecoveryActionResult,
  RecoveryPolicy,
  RecoveryEvent,
  RecoveryStatus,
  RecoveryPriority
} from './RecoveryAction';

/**
 * Recovery orchestrator configuration
 */
export interface RecoveryOrchestratorConfig {
  /**
   * Whether automated recovery is enabled
   */
  automatedRecoveryEnabled: boolean;
  
  /**
   * Maximum concurrent recovery actions
   */
  maxConcurrentActions: number;
  
  /**
   * Default timeout for recovery actions in ms
   */
  defaultActionTimeout: number;
  
  /**
   * Default retry count for recovery actions
   */
  defaultRetryCount: number;
  
  /**
   * Default retry delay in ms
   */
  defaultRetryDelay: number;
  
  /**
   * Whether to prioritize safety-critical systems
   */
  prioritizeSafetyCritical: boolean;
  
  /**
   * Marine-specific settings
   */
  marineSettings: {
    /**
     * Whether to adapt recovery to marine conditions
     */
    adaptToMarineConditions: boolean;
    
    /**
     * Whether to adapt to power status
     */
    powerAware: boolean;
    
    /**
     * Whether to limit recovery actions in rough seas
     */
    limitInRoughSeas: boolean;
    
    /**
     * Whether to require approval in emergency situations
     */
    requireApprovalInEmergency: boolean;
  };
}

/**
 * Recovery orchestrator for managing self-healing actions
 */
export class RecoveryOrchestrator {
  private config: RecoveryOrchestratorConfig;
  private recoveryActions: Map<string, BaseRecoveryAction> = new Map();
  private recoveryPolicies: Map<string, RecoveryPolicy> = new Map();
  private activeRecoveries: Map<string, Promise<RecoveryActionResult>> = new Map();
  private recoveryHistory: RecoveryActionResult[] = [];
  private eventListeners: ((event: RecoveryEvent) => void)[] = [];
  private currentOperationalContext: OperationalContextType = OperationalContext.DOCKED;
  private currentMarineEnvironment?: MarineEnvironmentStatus;
  
  constructor(config: RecoveryOrchestratorConfig) {
    this.config = config;
  }
  
  /**
   * Register a recovery action
   */
  registerRecoveryAction(action: BaseRecoveryAction): void {
    const config = action.getConfig();
    this.recoveryActions.set(config.actionId, action);
  }
  
  /**
   * Register a recovery policy
   */
  registerRecoveryPolicy(policy: RecoveryPolicy): void {
    this.recoveryPolicies.set(policy.policyId, policy);
  }
  
  /**
   * Update operational context
   */
  updateOperationalContext(context: OperationalContextType): void {
    this.currentOperationalContext = context;
  }
  
  /**
   * Update marine environment
   */
  updateMarineEnvironment(environment: MarineEnvironmentStatus): void {
    this.currentMarineEnvironment = environment;
  }
  
  /**
   * Process a health check result and trigger recovery if needed
   */
  async processHealthCheckResult(result: HealthCheckResult): Promise<void> {
    // Find applicable policies for this health check
    const applicablePolicies = Array.from(this.recoveryPolicies.values())
      .filter(policy => policy.checkId === result.checkId && policy.enabled);
    
    if (applicablePolicies.length === 0) {
      return; // No policies for this health check
    }
    
    // Check if recovery is needed based on policies
    const triggeringPolicies = applicablePolicies.filter(policy => 
      (result.status === policy.triggerStatus || result.status === HealthStatus.CRITICAL) && 
      result.score <= policy.triggerScore
    );
    
    if (triggeringPolicies.length === 0) {
      return; // No triggered policies
    }
    
    // Sort policies by priority (critical systems first)
    triggeringPolicies.sort((a, b) => {
      // Safety-critical systems first if configured
      if (this.config.prioritizeSafetyCritical) {
        const aSafety = a.marineSettings.safetyImpact;
        const bSafety = b.marineSettings.safetyImpact;
        if (aSafety && !bSafety) return -1;
        if (!aSafety && bSafety) return 1;
      }
      
      // Then by trigger score (lower score = higher priority)
      return a.triggerScore - b.triggerScore;
    });
    
    // Execute recovery for the highest priority policy
    const policy = triggeringPolicies[0];
    
    // Check if policy is applicable in current context
    if (!policy || !this.isPolicyApplicable(policy)) {
      this.emitEvent({
        eventId: uuidv4(),
        timestamp: new Date(),
        eventType: 'policy_triggered',
        systemId: result.marineSystemType,
        checkId: result.checkId,
        policyId: policy ? policy.policyId : 'unknown',
        data: {
          message: `Recovery policy triggered but not applicable in current context: ${this.currentOperationalContext}`,
          marineContext: {
            operationalContext: this.currentOperationalContext,
            environmentalConditions: this.currentMarineEnvironment ? { ...this.currentMarineEnvironment } : undefined
          }
        }
      });
      return;
    }
    
    // Check if we can start a new recovery (max concurrent actions)
    if (this.activeRecoveries.size >= this.config.maxConcurrentActions) {
      this.emitEvent({
        eventId: uuidv4(),
        timestamp: new Date(),
        eventType: 'recovery_failed',
        systemId: result.marineSystemType,
        checkId: result.checkId,
        policyId: policy.policyId,
        data: {
          message: `Recovery delayed: maximum concurrent actions (${this.config.maxConcurrentActions}) reached`,
          marineContext: {
            operationalContext: this.currentOperationalContext,
            environmentalConditions: this.currentMarineEnvironment ? { ...this.currentMarineEnvironment } : undefined
          }
        }
      });
      return;
    }
    
    // Start recovery process
    await this.executeRecoveryPolicy(policy, result);
  }
  
  /**
   * Execute a recovery policy
   */
  private async executeRecoveryPolicy(policy: RecoveryPolicy, triggeringCheck: HealthCheckResult): Promise<void> {
    const recoveryId = uuidv4();
    
    this.emitEvent({
      eventId: uuidv4(),
      timestamp: new Date(),
      eventType: 'recovery_started',
      systemId: triggeringCheck.marineSystemType,
      checkId: triggeringCheck.checkId,
      policyId: policy.policyId,
      data: {
        message: `Starting recovery for ${triggeringCheck.name} (${triggeringCheck.status}, score: ${triggeringCheck.score})`,
        marineContext: {
          operationalContext: this.currentOperationalContext,
          environmentalConditions: this.currentMarineEnvironment ? { ...this.currentMarineEnvironment } : undefined
        }
      }
    });
    
    // Execute actions in sequence
    let recoverySucceeded = false;
    
    for (const actionId of policy.actions) {
      const action = this.recoveryActions.get(actionId);
      
      if (!action) {
        this.emitEvent({
          eventId: uuidv4(),
          timestamp: new Date(),
          eventType: 'recovery_failed',
          systemId: triggeringCheck.marineSystemType,
          checkId: triggeringCheck.checkId,
          policyId: policy.policyId,
          actionId,
          data: {
            message: `Recovery action not found: ${actionId}`,
            marineContext: {
              operationalContext: this.currentOperationalContext,
              environmentalConditions: this.currentMarineEnvironment ? { ...this.currentMarineEnvironment } : undefined
            }
          }
        });
        continue;
      }
      
      // Execute the action
      const actionPromise = action.execute(
        triggeringCheck,
        this.currentOperationalContext,
        this.currentMarineEnvironment
      );
      
      // Track active recovery
      this.activeRecoveries.set(recoveryId, actionPromise);
      
      try {
        const result = await actionPromise;
        this.recoveryHistory.push(result);
        
        // Emit event
        this.emitEvent({
          eventId: uuidv4(),
          timestamp: new Date(),
          eventType: 'action_executed',
          systemId: triggeringCheck.marineSystemType,
          checkId: triggeringCheck.checkId,
          policyId: policy.policyId,
          actionId,
          data: {
            message: result.message,
            details: result.details,
            marineContext: {
              operationalContext: this.currentOperationalContext,
              environmentalConditions: this.currentMarineEnvironment ? { ...this.currentMarineEnvironment } : undefined
            }
          },
          result
        });
        
        if (result.success) {
          recoverySucceeded = true;
          if (policy.stopOnSuccess) {
            break;
          }
        }
      } catch (error) {
        this.emitEvent({
          eventId: uuidv4(),
          timestamp: new Date(),
          eventType: 'recovery_failed',
          systemId: triggeringCheck.marineSystemType,
          checkId: triggeringCheck.checkId,
          policyId: policy.policyId,
          actionId,
          data: {
            message: `Recovery action error: ${(error as Error).message}`,
            marineContext: {
              operationalContext: this.currentOperationalContext,
              environmentalConditions: this.currentMarineEnvironment ? { ...this.currentMarineEnvironment } : undefined
            }
          }
        });
      } finally {
        // Remove from active recoveries
        this.activeRecoveries.delete(recoveryId);
      }
    }
    
    // Emit final event
    this.emitEvent({
      eventId: uuidv4(),
      timestamp: new Date(),
      eventType: recoverySucceeded ? 'recovery_completed' : 'recovery_failed',
      systemId: triggeringCheck.marineSystemType,
      checkId: triggeringCheck.checkId,
      policyId: policy.policyId,
      data: {
        message: recoverySucceeded 
          ? `Recovery completed successfully for ${triggeringCheck.name}`
          : `Recovery failed for ${triggeringCheck.name}`,
        marineContext: {
          operationalContext: this.currentOperationalContext,
          environmentalConditions: this.currentMarineEnvironment ? { ...this.currentMarineEnvironment } : undefined
        }
      }
    });
  }
  
  /**
   * Check if a policy is applicable in the current context
   */
  private isPolicyApplicable(policy: RecoveryPolicy): boolean {
    // Check operational context
    if (!policy.marineSettings.activeContexts.includes(this.currentOperationalContext)) {
      return false;
    }
    
    // Check sea conditions if configured
    if (this.config.marineSettings.limitInRoughSeas && 
        !policy.marineSettings.activeInRoughSeas && 
        this.currentMarineEnvironment) {
      const seaState = this.currentMarineEnvironment.seaState;
      if (seaState === 'rough' || seaState === 'very_rough') {
        return false;
      }
    }
    
    // Check power status if configured
    if (this.config.marineSettings.powerAware && 
        policy.marineSettings.powerAware && 
        this.currentMarineEnvironment?.powerStatus === 'critical') {
      // Only allow safety-critical policies when power is critical
      return policy.marineSettings.safetyImpact;
    }
    
    // Check emergency context
    if (this.currentOperationalContext === OperationalContext.EMERGENCY && 
        this.config.marineSettings.requireApprovalInEmergency) {
      // In emergency, only automated policies are allowed
      return policy.marineSettings.safetyImpact;
    }
    
    return true;
  }
  
  /**
   * Subscribe to recovery events
   */
  onEvent(listener: (event: RecoveryEvent) => void): () => void {
    this.eventListeners.push(listener);
    return () => {
      this.eventListeners = this.eventListeners.filter(l => l !== listener);
    };
  }
  
  /**
   * Emit a recovery event
   */
  private emitEvent(event: RecoveryEvent): void {
    for (const listener of this.eventListeners) {
      try {
        listener(event);
      } catch (error) {
        console.error('Error in recovery event listener:', error);
      }
    }
  }
  
  /**
   * Get recovery history
   */
  getRecoveryHistory(): RecoveryActionResult[] {
    return [...this.recoveryHistory];
  }
  
  /**
   * Get active recoveries
   */
  getActiveRecoveries(): string[] {
    return Array.from(this.activeRecoveries.keys());
  }
  
  /**
   * Get registered recovery actions
   */
  getRegisteredActions(): string[] {
    return Array.from(this.recoveryActions.keys());
  }
  
  /**
   * Get registered recovery policies
   */
  getRegisteredPolicies(): string[] {
    return Array.from(this.recoveryPolicies.keys());
  }
}

/**
 * Create a default recovery orchestrator
 */
export const defaultRecoveryOrchestrator = new RecoveryOrchestrator({
  automatedRecoveryEnabled: true,
  maxConcurrentActions: 3,
  defaultActionTimeout: 30000,
  defaultRetryCount: 2,
  defaultRetryDelay: 5000,
  prioritizeSafetyCritical: true,
  marineSettings: {
    adaptToMarineConditions: true,
    powerAware: true,
    limitInRoughSeas: true,
    requireApprovalInEmergency: true
  }
});
