/**
 * GPS Health Check for marine navigation systems
 */

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

/**
 * GPS data interface for health checking
 */
export interface GPSData {
  latitude: number;
  longitude: number;
  altitude: number;
  satelliteCount: number;
  hdop: number;              // Horizontal Dilution of Precision
  vdop: number;              // Vertical Dilution of Precision
  speed: number;             // Speed over ground in knots
  course: number;            // Course over ground in degrees
  timestamp: Date;
  fix: 'none' | '2d' | '3d';
}

/**
 * GPS Health Check implementation
 */
export class GPSHealthCheck extends BaseHealthCheck {
  private gpsDataProvider: () => Promise<GPSData>;
  private lastKnownPosition?: { lat: number; lon: number; timestamp: Date };

  constructor(
    config: HealthCheckConfig,
    gpsDataProvider: () => Promise<GPSData>
  ) {
    super(config);
    this.gpsDataProvider = gpsDataProvider;
  }

  protected async performCheck(
    operationalContext: OperationalContextType,
    marineEnvironment?: MarineEnvironmentStatus
  ): Promise<HealthCheckResult> {
    try {
      const gpsData = await this.gpsDataProvider();
      
      // Calculate health score based on multiple factors
      let score = 1.0;
      let status: HealthStatusType = HealthStatus.HEALTHY;
      const issues: string[] = [];
      const details: Record<string, any> = {
        satelliteCount: gpsData.satelliteCount,
        hdop: gpsData.hdop,
        vdop: gpsData.vdop,
        fix: gpsData.fix,
        dataAge: Date.now() - gpsData.timestamp.getTime()
      };

      // Check GPS fix quality
      if (gpsData.fix === 'none') {
        score = 0.0;
        status = HealthStatus.CRITICAL;
        issues.push('No GPS fix available');
      } else if (gpsData.fix === '2d') {
        score -= 0.3;
        issues.push('Only 2D GPS fix available');
      }

      // Check satellite count
      if (gpsData.satelliteCount < 4) {
        score -= 0.4;
        issues.push(`Low satellite count: ${gpsData.satelliteCount}`);
      } else if (gpsData.satelliteCount < 6) {
        score -= 0.2;
        issues.push(`Marginal satellite count: ${gpsData.satelliteCount}`);
      }

      // Check HDOP (Horizontal Dilution of Precision)
      if (gpsData.hdop > 5.0) {
        score -= 0.3;
        issues.push(`Poor horizontal accuracy: HDOP ${gpsData.hdop}`);
      } else if (gpsData.hdop > 2.0) {
        score -= 0.1;
        issues.push(`Marginal horizontal accuracy: HDOP ${gpsData.hdop}`);
      }

      // Check data freshness
      const dataAge = Date.now() - gpsData.timestamp.getTime();
      if (dataAge > 30000) { // 30 seconds
        score -= 0.4;
        issues.push(`Stale GPS data: ${Math.round(dataAge / 1000)}s old`);
      } else if (dataAge > 10000) { // 10 seconds
        score -= 0.2;
        issues.push(`Old GPS data: ${Math.round(dataAge / 1000)}s old`);
      }

      // Check for position jumps (if we have previous position)
      if (this.lastKnownPosition) {
        const distance = this.calculateDistance(
          this.lastKnownPosition.lat,
          this.lastKnownPosition.lon,
          gpsData.latitude,
          gpsData.longitude
        );
        const timeDiff = (gpsData.timestamp.getTime() - this.lastKnownPosition.timestamp.getTime()) / 1000;
        const maxReasonableSpeed = 50; // knots
        const maxDistance = (maxReasonableSpeed * 0.514444 * timeDiff) / 1000; // km

        if (distance > maxDistance * 2) {
          score -= 0.3;
          issues.push(`Suspicious position jump: ${distance.toFixed(2)}km`);
          details['positionJump'] = distance;
        }
      }

      // Environmental adjustments
      if (marineEnvironment) {
        if (marineEnvironment.weather === 'storm') {
          // GPS can be affected by heavy weather
          score = Math.max(score - 0.1, 0);
          details['environmentalImpact'] = 'Storm conditions may affect GPS accuracy';
        }
      }

      // Update last known position
      if (gpsData.fix !== 'none') {
        this.lastKnownPosition = {
          lat: gpsData.latitude,
          lon: gpsData.longitude,
          timestamp: gpsData.timestamp
        };
      }

      // Determine final status
      score = Math.max(0, Math.min(1, score));
      if (score >= 0.8) {
        status = HealthStatus.HEALTHY;
      } else if (score >= 0.6) {
        status = HealthStatus.DEGRADED;
      } else if (score >= 0.3) {
        status = HealthStatus.UNHEALTHY;
      } else {
        status = HealthStatus.CRITICAL;
      }

      const message = issues.length > 0 
        ? `GPS issues detected: ${issues.join(', ')}`
        : 'GPS operating normally';

      return {
        checkId: this.config.checkId,
        name: this.config.name,
        type: this.config.type,
        marineSystemType: this.config.marineSystemType,
        status,
        score,
        message,
        details,
        timestamp: new Date(),
        executionTime: 0, // Will be set by base class
        marineContext: {
          operationalContext,
          environmentalImpact: marineEnvironment?.weather === 'storm',
          safetyImpact: true, // GPS is critical for navigation safety
          powerImpact: 5 // GPS typically uses ~5W
        },
        thresholds: this.config.thresholds
      };

    } catch (error) {
      throw new Error(`GPS health check failed: ${(error as Error).message}`);
    }
  }

  /**
   * Calculate distance between two GPS coordinates using Haversine formula
   */
  private calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
    const R = 6371; // Earth's radius in kilometers
    const dLat = this.toRadians(lat2 - lat1);
    const dLon = this.toRadians(lon2 - lon1);
    const a = 
      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(this.toRadians(lat1)) * Math.cos(this.toRadians(lat2)) *
      Math.sin(dLon / 2) * Math.sin(dLon / 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
  }

  private toRadians(degrees: number): number {
    return degrees * (Math.PI / 180);
  }
}

/**
 * Factory function to create GPS health check with standard marine configuration
 */
export function createGPSHealthCheck(
  gpsDataProvider: () => Promise<GPSData>,
  customConfig?: Partial<HealthCheckConfig>
): GPSHealthCheck {
  const defaultConfig: HealthCheckConfig = {
    checkId: 'gps-health',
    name: 'GPS Navigation Health',
    type: HealthCheckType.SENSOR,
    marineSystemType: MarineSystemType.NAVIGATION as MarineSystemTypeType,
    interval: 10000, // Check every 10 seconds
    timeout: 5000,   // 5 second timeout
    retryCount: 2,
    thresholds: {
      warning: 0.6,
      critical: 0.3,
      unit: 'health_score'
    },
    marineSettings: {
      operationalContexts: [
        OperationalContext.SAILING,
        OperationalContext.MOTORING,
        OperationalContext.ANCHORED,
        OperationalContext.DOCKED
      ] as OperationalContextType[],
      environmentalSensitivity: 0.3, // Moderately sensitive to weather
      powerAware: true,
      safetyImpact: true
    },
    advanced: {
      trendAnalysis: true,
      predictiveAlerts: true,
      adaptiveThresholds: false,
      historicalComparison: true
    },
    ...customConfig
  };

  return new GPSHealthCheck(defaultConfig, gpsDataProvider);
}
