/**
 * Core health check engine for marine systems
 */

import {
  HealthCheckResult,
  HealthCheckConfig,
  HealthCheckType,
  HealthStatus,
  HealthStatusType,
  OperationalContext,
  OperationalContextType,
  MarineSystemType,
  MarineSystemTypeType,
  MarineEnvironmentStatus,
  HealthMonitoringEvent
} from '../types';

import { executeWithTimeout, TimeoutContexts } from '@sailboat-computer/resilience';

/**
 * Abstract base class for health checks
 */
export abstract class BaseHealthCheck {
  protected config: HealthCheckConfig;
  protected lastResult?: HealthCheckResult;
  protected consecutiveFailures = 0;
  protected lastExecutionTime = 0;

  constructor(config: HealthCheckConfig) {
    this.config = config;
  }

  /**
   * Execute the health check
   */
  async execute(
    operationalContext: OperationalContextType,
    marineEnvironment?: MarineEnvironmentStatus
  ): Promise<HealthCheckResult> {
    const startTime = Date.now();

    try {
      // Check if this health check should run in current operational context
      if (!this.shouldExecute(operationalContext, marineEnvironment)) {
        return this.createSkippedResult(operationalContext, 'Skipped due to operational context');
      }

      // Execute the actual health check with timeout protection
      const timeoutContext = this.getTimeoutContext(operationalContext);
      const result = await executeWithTimeout(
        () => this.performCheck(operationalContext, marineEnvironment),
        timeoutContext
      );

      if (result.success && result.data) {
        this.consecutiveFailures = 0;
        this.lastResult = result.data;
        this.lastExecutionTime = Date.now() - startTime;
        return result.data;
      } else {
        throw result.error || new Error('Health check failed');
      }

    } catch (error) {
      this.consecutiveFailures++;
      const failureResult = this.createFailureResult(
        operationalContext,
        error as Error,
        Date.now() - startTime
      );
      this.lastResult = failureResult;
      this.lastExecutionTime = Date.now() - startTime;
      return failureResult;
    }
  }

  /**
   * Get the last health check result
   */
  getLastResult(): HealthCheckResult | undefined {
    return this.lastResult;
  }

  /**
   * Get health check configuration
   */
  getConfig(): HealthCheckConfig {
    return { ...this.config };
  }

  /**
   * Check if health check should execute in current context
   */
  protected shouldExecute(
    operationalContext: OperationalContextType,
    marineEnvironment?: MarineEnvironmentStatus
  ): boolean {
    // Check operational context
    if (!this.config.marineSettings.operationalContexts.includes(operationalContext)) {
      return false;
    }

    // Check power awareness
    if (this.config.marineSettings.powerAware && marineEnvironment?.powerStatus === 'critical') {
      // Only run critical health checks when power is critical
      return this.config.marineSystemType === MarineSystemType.SAFETY ||
             this.config.marineSystemType === MarineSystemType.NAVIGATION;
    }

    // Check environmental conditions
    if (marineEnvironment?.criticalOperationsOnly && !this.config.marineSettings.safetyImpact) {
      return false;
    }

    return true;
  }

  /**
   * Get timeout context for this health check
   */
  protected getTimeoutContext(operationalContext: OperationalContextType) {
    switch (this.config.marineSystemType) {
      case MarineSystemType.SAFETY:
        return TimeoutContexts.safetyOperation(this.config.name, operationalContext);
      case MarineSystemType.NAVIGATION:
        return TimeoutContexts.navigationSensor(this.config.name, operationalContext);
      case MarineSystemType.COMMUNICATION:
        return TimeoutContexts.networkOperation(this.config.name, operationalContext);
      case MarineSystemType.MAINTENANCE:
        return TimeoutContexts.maintenanceOperation(this.config.name);
      default:
        return TimeoutContexts.comfortOperation(this.config.name, operationalContext);
    }
  }

  /**
   * Create a skipped result
   */
  protected createSkippedResult(
    operationalContext: OperationalContextType,
    reason: string
  ): HealthCheckResult {
    return {
      checkId: this.config.checkId,
      name: this.config.name,
      type: this.config.type,
      marineSystemType: this.config.marineSystemType,
      status: HealthStatus.UNKNOWN,
      score: 0.5, // Neutral score for skipped checks
      message: reason,
      timestamp: new Date(),
      executionTime: 0,
      marineContext: {
        operationalContext,
        environmentalImpact: false,
        safetyImpact: this.config.marineSettings.safetyImpact,
        powerImpact: 0
      }
    };
  }

  /**
   * Create a failure result
   */
  protected createFailureResult(
    operationalContext: OperationalContextType,
    error: Error,
    executionTime: number
  ): HealthCheckResult {
    // Determine severity based on consecutive failures
    let status: HealthStatusType;
    let score: number;

    if (this.consecutiveFailures >= 3) {
      status = HealthStatus.CRITICAL;
      score = 0.0;
    } else if (this.consecutiveFailures >= 2) {
      status = HealthStatus.UNHEALTHY;
      score = 0.2;
    } else {
      status = HealthStatus.DEGRADED;
      score = 0.4;
    }

    return {
      checkId: this.config.checkId,
      name: this.config.name,
      type: this.config.type,
      marineSystemType: this.config.marineSystemType,
      status,
      score,
      message: `Health check failed: ${error.message}`,
      details: {
        error: error.message,
        consecutiveFailures: this.consecutiveFailures,
        stack: error.stack
      },
      timestamp: new Date(),
      executionTime,
      marineContext: {
        operationalContext,
        environmentalImpact: false,
        safetyImpact: this.config.marineSettings.safetyImpact,
        powerImpact: 0
      }
    };
  }

  /**
   * Abstract method to perform the actual health check
   */
  protected abstract performCheck(
    operationalContext: OperationalContextType,
    marineEnvironment?: MarineEnvironmentStatus
  ): Promise<HealthCheckResult>;
}

/**
 * Health check scheduler and executor
 */
export class HealthCheckEngine {
  private healthChecks: Map<string, BaseHealthCheck> = new Map();
  private scheduledChecks: Map<string, NodeJS.Timeout> = new Map();
  private eventListeners: ((event: HealthMonitoringEvent) => void)[] = [];
  private isRunning = false;
  private currentOperationalContext: OperationalContextType = OperationalContext.DOCKED;
  private currentMarineEnvironment?: MarineEnvironmentStatus;

  /**
   * Register a health check
   */
  registerHealthCheck(healthCheck: BaseHealthCheck): void {
    const config = healthCheck.getConfig();
    this.healthChecks.set(config.checkId, healthCheck);

    // Schedule the health check if engine is running
    if (this.isRunning) {
      this.scheduleHealthCheck(config.checkId);
    }

    this.emitEvent('health_check_completed', config.checkId, {
      message: `Health check '${config.name}' registered`,
      details: { action: 'registered' }
    });
  }

  /**
   * Unregister a health check
   */
  unregisterHealthCheck(checkId: string): void {
    const healthCheck = this.healthChecks.get(checkId);
    if (!healthCheck) {
      return;
    }

    // Cancel scheduled execution
    const timer = this.scheduledChecks.get(checkId);
    if (timer) {
      clearTimeout(timer);
      this.scheduledChecks.delete(checkId);
    }

    this.healthChecks.delete(checkId);

    this.emitEvent('health_check_completed', checkId, {
      message: `Health check unregistered`,
      details: { action: 'unregistered' }
    });
  }

  /**
   * Start the health check engine
   */
  start(): void {
    if (this.isRunning) {
      return;
    }

    this.isRunning = true;

    // Schedule all registered health checks
    for (const checkId of this.healthChecks.keys()) {
      this.scheduleHealthCheck(checkId);
    }

    this.emitEvent('system_recovery', 'health-engine', {
      message: 'Health check engine started',
      details: { 
        registeredChecks: this.healthChecks.size,
        operationalContext: this.currentOperationalContext
      }
    });
  }

  /**
   * Stop the health check engine
   */
  stop(): void {
    if (!this.isRunning) {
      return;
    }

    this.isRunning = false;

    // Cancel all scheduled checks
    for (const [checkId, timer] of this.scheduledChecks) {
      clearTimeout(timer);
    }
    this.scheduledChecks.clear();

    this.emitEvent('system_recovery', 'health-engine', {
      message: 'Health check engine stopped',
      details: { cancelledChecks: this.scheduledChecks.size }
    });
  }

  /**
   * Update operational context
   */
  updateOperationalContext(context: OperationalContextType): void {
    const previousContext = this.currentOperationalContext;
    this.currentOperationalContext = context;

    this.emitEvent('system_recovery', 'health-engine', {
      message: 'Operational context updated',
      details: { 
        previousContext,
        newContext: context,
        affectedChecks: this.getAffectedChecks(context)
      }
    });

    // Reschedule health checks if context change affects them
    this.rescheduleAffectedChecks(context);
  }

  /**
   * Update marine environment status
   */
  updateMarineEnvironment(environment: MarineEnvironmentStatus): void {
    this.currentMarineEnvironment = environment;

    this.emitEvent('system_recovery', 'health-engine', {
      message: 'Marine environment updated',
      details: {
        seaState: environment.seaState,
        weather: environment.weather,
        powerStatus: environment.powerStatus,
        criticalOperationsOnly: environment.criticalOperationsOnly
      }
    });

    // Adjust health check scheduling based on environment
    this.adjustForEnvironment(environment);
  }

  /**
   * Execute a specific health check immediately
   */
  async executeHealthCheck(checkId: string): Promise<HealthCheckResult | null> {
    const healthCheck = this.healthChecks.get(checkId);
    if (!healthCheck) {
      return null;
    }

    try {
      const result = await healthCheck.execute(
        this.currentOperationalContext,
        this.currentMarineEnvironment
      );

      this.emitEvent('health_check_completed', checkId, {
        message: `Health check completed: ${result.status}`,
        score: result.score,
        details: {
          status: result.status,
          executionTime: result.executionTime,
          manual: true
        }
      });

      return result;
    } catch (error) {
      this.emitEvent('alert_triggered', checkId, {
        message: `Health check execution failed: ${(error as Error).message}`,
        details: { error: (error as Error).message }
      });
      return null;
    }
  }

  /**
   * Get all health check results
   */
  getAllResults(): Map<string, HealthCheckResult> {
    const results = new Map<string, HealthCheckResult>();
    
    for (const [checkId, healthCheck] of this.healthChecks) {
      const result = healthCheck.getLastResult();
      if (result) {
        results.set(checkId, result);
      }
    }

    return results;
  }

  /**
   * Get health checks by system type
   */
  getHealthChecksBySystem(systemType: MarineSystemTypeType): BaseHealthCheck[] {
    return Array.from(this.healthChecks.values()).filter(
      check => check.getConfig().marineSystemType === systemType
    );
  }

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

  /**
   * Remove event listener
   */
  removeEventListener(listener: (event: HealthMonitoringEvent) => void): void {
    const index = this.eventListeners.indexOf(listener);
    if (index > -1) {
      this.eventListeners.splice(index, 1);
    }
  }

  /**
   * Schedule a health check
   */
  private scheduleHealthCheck(checkId: string): void {
    const healthCheck = this.healthChecks.get(checkId);
    if (!healthCheck) {
      return;
    }

    const config = healthCheck.getConfig();
    
    // Cancel existing timer
    const existingTimer = this.scheduledChecks.get(checkId);
    if (existingTimer) {
      clearTimeout(existingTimer);
    }

    // Calculate interval based on marine environment
    let interval = config.interval;
    if (this.currentMarineEnvironment) {
      // Adjust interval based on environmental conditions
      if (this.currentMarineEnvironment.criticalOperationsOnly) {
        interval *= 0.5; // More frequent checks in critical conditions
      } else if (this.currentMarineEnvironment.powerStatus === 'conservation') {
        interval *= 2.0; // Less frequent checks to save power
      }
    }

    // Schedule next execution
    const timer = setTimeout(async () => {
      await this.executeHealthCheck(checkId);
      
      // Reschedule if still running
      if (this.isRunning) {
        this.scheduleHealthCheck(checkId);
      }
    }, interval);

    this.scheduledChecks.set(checkId, timer);
  }

  /**
   * Get health checks affected by operational context change
   */
  private getAffectedChecks(context: OperationalContextType): string[] {
    const affected: string[] = [];
    
    for (const [checkId, healthCheck] of this.healthChecks) {
      const config = healthCheck.getConfig();
      if (!config.marineSettings.operationalContexts.includes(context)) {
        affected.push(checkId);
      }
    }

    return affected;
  }

  /**
   * Reschedule health checks affected by context change
   */
  private rescheduleAffectedChecks(context: OperationalContextType): void {
    for (const checkId of this.healthChecks.keys()) {
      this.scheduleHealthCheck(checkId);
    }
  }

  /**
   * Adjust health check scheduling for marine environment
   */
  private adjustForEnvironment(environment: MarineEnvironmentStatus): void {
    // Reschedule all checks with new environmental considerations
    for (const checkId of this.healthChecks.keys()) {
      this.scheduleHealthCheck(checkId);
    }
  }

  /**
   * Emit health monitoring event
   */
  private emitEvent(
    eventType: HealthMonitoringEvent['eventType'],
    checkId: string,
    data: {
      message: string;
      score?: number;
      details?: Record<string, any>;
    }
  ): void {
    const event: HealthMonitoringEvent = {
      eventId: `${eventType}-${checkId}-${Date.now()}`,
      timestamp: new Date(),
      eventType,
      systemId: 'health-engine',
      checkId,
      currentStatus: HealthStatus.UNKNOWN, // Will be updated by specific implementations
      data: {
        ...data,
        marineContext: {
          operationalContext: this.currentOperationalContext,
          environmentalConditions: this.currentMarineEnvironment ? { ...this.currentMarineEnvironment } : {},
          safetyImpact: false
        }
      }
    };

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

/**
 * Default health check engine instance
 */
export const defaultHealthCheckEngine = new HealthCheckEngine();
