/**
 * Service de tracking GPS avancé pour MediFlash SOS+
 * Gère la géolocalisation temps réel, optimisation des routes et estimations précises
 */

export interface LocationData {
  lat: number;
  lng: number;
  timestamp: Date;
  accuracy?: number;
  heading?: number;
  speed?: number;
  altitude?: number;
}

export interface TrackingSession {
  sessionId: string;
  courierId: string;
  orderId?: string;
  isActive: boolean;
  startTime: Date;
  endTime?: Date;
  route: LocationData[];
  totalDistance: number;
  estimatedArrival?: Date;
  currentSpeed?: number;
}

export interface RouteEstimation {
  distance: number; // en mètres
  duration: number; // en secondes
  route: LocationData[];
  trafficFactor: number;
  estimatedArrival: Date;
}

export interface GeofenceArea {
  id: string;
  name: string;
  center: LocationData;
  radius: number; // en mètres
  type: 'pickup' | 'delivery' | 'pharmacy' | 'danger_zone';
}

export class TrackingService {
  private watchId: number | null = null;
  private isTracking = false;
  private sessionData: TrackingSession | null = null;
  private wsConnection: WebSocket | null = null;
  private locationHistory: LocationData[] = [];
  private geofences: GeofenceArea[] = [];
  private callbacks: Map<string, Function> = new Map();
  
  // Configuration
  private config = {
    highAccuracy: true,
    timeout: 15000,
    maximumAge: 5000,
    minUpdateInterval: 2000, // 2 secondes minimum entre les mises à jour
    distanceThreshold: 5, // 5 mètres minimum pour déclencher une mise à jour
    speedSmoothingFactor: 0.3 // Lissage des données de vitesse
  };

  constructor(private userId: string, private userRole: string) {
    this.connectWebSocket();
  }

  private connectWebSocket() {
    try {
      const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
      const wsUrl = `${protocol}//${window.location.host}/ws?userId=${this.userId}&role=${this.userRole}`;
      
      this.wsConnection = new WebSocket(wsUrl);

      this.wsConnection.onopen = () => {
        console.log('Tracking WebSocket connected');
      };

      this.wsConnection.onmessage = (event) => {
        try {
          const message = JSON.parse(event.data);
          this.handleWebSocketMessage(message);
        } catch (error) {
          console.error('Error parsing tracking WebSocket message:', error);
        }
      };

      this.wsConnection.onclose = () => {
        console.log('Tracking WebSocket disconnected');
        // Reconnexion automatique après 3 secondes
        setTimeout(() => this.connectWebSocket(), 3000);
      };
    } catch (error) {
      console.error('Error connecting tracking WebSocket:', error);
    }
  }

  private handleWebSocketMessage(message: any) {
    switch (message.type) {
      case 'courier_location_update':
        this.handleCourierLocationUpdate(message.data);
        break;

      case 'route_update':
        this.handleRouteUpdate(message.data);
        break;

      case 'geofence_event':
        this.handleGeofenceEvent(message.data);
        break;

      case 'traffic_update':
        this.handleTrafficUpdate(message.data);
        break;
    }
  }

  /**
   * Démarrer le tracking pour un livreur
   */
  async startTracking(orderId?: string): Promise<boolean> {
    if (this.userRole !== 'courier') {
      console.warn('Only couriers can start tracking');
      return false;
    }

    if (this.isTracking) {
      console.warn('Tracking already active');
      return true;
    }

    try {
      // Demander permission de géolocalisation
      const permission = await this.requestLocationPermission();
      if (permission !== 'granted') {
        throw new Error('Geolocation permission denied');
      }

      // Créer une nouvelle session
      this.sessionData = {
        sessionId: `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
        courierId: this.userId,
        orderId,
        isActive: true,
        startTime: new Date(),
        route: [],
        totalDistance: 0
      };

      // Démarrer le suivi GPS
      this.watchId = navigator.geolocation.watchPosition(
        (position) => this.handleLocationUpdate(position),
        (error) => this.handleLocationError(error),
        {
          enableHighAccuracy: this.config.highAccuracy,
          timeout: this.config.timeout,
          maximumAge: this.config.maximumAge
        }
      );

      this.isTracking = true;
      
      // Notifier le serveur
      this.sendWebSocketMessage({
        type: 'start_tracking',
        sessionId: this.sessionData.sessionId,
        orderId
      });

      console.log(`📍 Tracking started for courier ${this.userId}`);
      this.triggerCallback('trackingStarted', this.sessionData);

      return true;

    } catch (error) {
      console.error('Error starting tracking:', error);
      this.triggerCallback('trackingError', error);
      return false;
    }
  }

  /**
   * Arrêter le tracking
   */
  stopTracking(): void {
    if (!this.isTracking) {
      return;
    }

    if (this.watchId !== null) {
      navigator.geolocation.clearWatch(this.watchId);
      this.watchId = null;
    }

    if (this.sessionData) {
      this.sessionData.isActive = false;
      this.sessionData.endTime = new Date();

      // Calculer statistiques finales
      const stats = this.calculateSessionStats();
      
      // Notifier le serveur
      this.sendWebSocketMessage({
        type: 'stop_tracking',
        sessionId: this.sessionData.sessionId,
        stats
      });

      this.triggerCallback('trackingStopped', { 
        session: this.sessionData, 
        stats 
      });
    }

    this.isTracking = false;
    this.locationHistory = [];
    
    console.log('📍 Tracking stopped');
  }

  private async requestLocationPermission(): Promise<PermissionState> {
    if (!('permissions' in navigator)) {
      // Fallback pour les navigateurs qui ne supportent pas l'API Permissions
      return 'granted';
    }

    try {
      const result = await navigator.permissions.query({ name: 'geolocation' });
      
      if (result.state === 'prompt') {
        // Déclencher une demande de géolocalisation pour obtenir la permission
        await new Promise((resolve, reject) => {
          navigator.geolocation.getCurrentPosition(resolve, reject);
        });
        return 'granted';
      }
      
      return result.state;
    } catch (error) {
      console.error('Error requesting location permission:', error);
      return 'denied';
    }
  }

  private handleLocationUpdate(position: GeolocationPosition) {
    const location: LocationData = {
      lat: position.coords.latitude,
      lng: position.coords.longitude,
      timestamp: new Date(),
      accuracy: position.coords.accuracy,
      heading: position.coords.heading || undefined,
      speed: position.coords.speed || undefined,
      altitude: position.coords.altitude || undefined
    };

    // Filtrer les mises à jour trop fréquentes ou imprécises
    if (!this.shouldUpdateLocation(location)) {
      return;
    }

    // Ajouter à l'historique
    this.locationHistory.push(location);
    if (this.locationHistory.length > 1000) {
      this.locationHistory = this.locationHistory.slice(-500); // Garder les 500 dernières positions
    }

    // Mettre à jour la session
    if (this.sessionData) {
      this.sessionData.route.push(location);
      this.sessionData.currentSpeed = this.calculateSmoothedSpeed(location);
      this.sessionData.totalDistance = this.calculateTotalDistance();
    }

    // Vérifier les géofences
    this.checkGeofences(location);

    // Envoyer au serveur
    this.sendWebSocketMessage({
      type: 'location_update',
      location,
      sessionId: this.sessionData?.sessionId,
      orderId: this.sessionData?.orderId
    });

    // Déclencher les callbacks
    this.triggerCallback('locationUpdate', location);
  }

  private shouldUpdateLocation(newLocation: LocationData): boolean {
    if (this.locationHistory.length === 0) {
      return true;
    }

    const lastLocation = this.locationHistory[this.locationHistory.length - 1];
    const timeDiff = newLocation.timestamp.getTime() - lastLocation.timestamp.getTime();
    
    // Vérifier interval minimum
    if (timeDiff < this.config.minUpdateInterval) {
      return false;
    }

    // Vérifier distance minimum
    const distance = this.calculateDistance(lastLocation, newLocation);
    if (distance < this.config.distanceThreshold) {
      return false;
    }

    return true;
  }

  private calculateSmoothedSpeed(currentLocation: LocationData): number {
    if (this.locationHistory.length < 2) {
      return currentLocation.speed || 0;
    }

    const previousLocation = this.locationHistory[this.locationHistory.length - 2];
    const distance = this.calculateDistance(previousLocation, currentLocation);
    const timeDiff = (currentLocation.timestamp.getTime() - previousLocation.timestamp.getTime()) / 1000; // en secondes
    
    const calculatedSpeed = timeDiff > 0 ? distance / timeDiff : 0;
    const gpsSpeed = currentLocation.speed || 0;
    
    // Lissage entre vitesse calculée et vitesse GPS
    return (calculatedSpeed * (1 - this.config.speedSmoothingFactor)) + 
           (gpsSpeed * this.config.speedSmoothingFactor);
  }

  private calculateTotalDistance(): number {
    if (!this.sessionData || this.sessionData.route.length < 2) {
      return 0;
    }

    let totalDistance = 0;
    for (let i = 1; i < this.sessionData.route.length; i++) {
      totalDistance += this.calculateDistance(
        this.sessionData.route[i - 1],
        this.sessionData.route[i]
      );
    }

    return totalDistance;
  }

  private handleLocationError(error: GeolocationPositionError) {
    let errorMessage = 'Unknown geolocation error';
    
    switch (error.code) {
      case error.PERMISSION_DENIED:
        errorMessage = 'Permission de géolocalisation refusée';
        break;
      case error.POSITION_UNAVAILABLE:
        errorMessage = 'Position indisponible';
        break;
      case error.TIMEOUT:
        errorMessage = 'Timeout de géolocalisation';
        break;
    }

    console.error('Geolocation error:', errorMessage);
    this.triggerCallback('locationError', { error, message: errorMessage });
  }

  /**
   * Estimation de route avancée
   */
  async estimateRoute(
    from: LocationData,
    to: LocationData,
    mode: 'driving' | 'walking' | 'cycling' = 'driving'
  ): Promise<RouteEstimation> {
    // Calcul de distance directe (Haversine)
    const directDistance = this.calculateDistance(from, to);
    
    // Facteur de route (route réelle vs distance directe)
    const routeFactor = mode === 'driving' ? 1.3 : mode === 'walking' ? 1.2 : 1.25;
    const estimatedDistance = directDistance * routeFactor;
    
    // Vitesse moyenne selon le mode de transport
    const avgSpeed = this.getAverageSpeed(mode);
    
    // Facteur trafic (simulation - en production intégrer avec Google Traffic API)
    const trafficFactor = this.getTrafficFactor();
    
    const estimatedDuration = (estimatedDistance / avgSpeed) * trafficFactor;
    const estimatedArrival = new Date(Date.now() + estimatedDuration * 1000);

    // Générer points de route simplifiés
    const route = this.generateSimpleRoute(from, to, 5);

    return {
      distance: estimatedDistance,
      duration: estimatedDuration,
      route,
      trafficFactor,
      estimatedArrival
    };
  }

  private getAverageSpeed(mode: 'driving' | 'walking' | 'cycling'): number {
    // Vitesses moyennes en m/s
    switch (mode) {
      case 'driving': return 13.89; // ~50 km/h
      case 'cycling': return 4.17;  // ~15 km/h
      case 'walking': return 1.39;  // ~5 km/h
      default: return 13.89;
    }
  }

  private getTrafficFactor(): number {
    const hour = new Date().getHours();
    // Simulation du trafic selon l'heure
    if (hour >= 7 && hour <= 9) return 1.5; // Heure de pointe matin
    if (hour >= 17 && hour <= 19) return 1.6; // Heure de pointe soir
    if (hour >= 12 && hour <= 14) return 1.2; // Heure de déjeuner
    return 1.1; // Trafic normal
  }

  /**
   * Gestion des géofences
   */
  addGeofence(geofence: GeofenceArea) {
    this.geofences.push(geofence);
  }

  removeGeofence(geofenceId: string) {
    this.geofences = this.geofences.filter(g => g.id !== geofenceId);
  }

  private checkGeofences(location: LocationData) {
    for (const geofence of this.geofences) {
      const distance = this.calculateDistance(location, geofence.center);
      const isInside = distance <= geofence.radius;
      
      // Déclencher événement si entrée/sortie de géofence
      this.triggerCallback('geofenceEvent', {
        geofence,
        location,
        isInside,
        distance
      });
    }
  }

  /**
   * Utilitaires
   */
  private calculateDistance(point1: LocationData, point2: LocationData): number {
    const R = 6371e3; // Rayon de la Terre en mètres
    const φ1 = point1.lat * Math.PI / 180;
    const φ2 = point2.lat * Math.PI / 180;
    const Δφ = (point2.lat - point1.lat) * Math.PI / 180;
    const Δλ = (point2.lng - point1.lng) * Math.PI / 180;

    const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
              Math.cos(φ1) * Math.cos(φ2) *
              Math.sin(Δλ/2) * Math.sin(Δλ/2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

    return R * c;
  }

  private generateSimpleRoute(from: LocationData, to: LocationData, points: number): LocationData[] {
    const route: LocationData[] = [from];
    
    for (let i = 1; i < points - 1; i++) {
      const progress = i / (points - 1);
      const lat = from.lat + (to.lat - from.lat) * progress;
      const lng = from.lng + (to.lng - from.lng) * progress;
      
      route.push({
        lat,
        lng,
        timestamp: new Date(Date.now() + i * 60000) // Points toutes les minutes
      });
    }
    
    route.push(to);
    return route;
  }

  private calculateSessionStats() {
    if (!this.sessionData) return {};

    const duration = this.sessionData.endTime 
      ? this.sessionData.endTime.getTime() - this.sessionData.startTime.getTime()
      : Date.now() - this.sessionData.startTime.getTime();

    const avgSpeed = this.sessionData.totalDistance / (duration / 1000); // m/s

    return {
      duration: duration / 1000, // en secondes
      totalDistance: this.sessionData.totalDistance,
      avgSpeed,
      maxSpeed: Math.max(...this.sessionData.route.map(r => r.speed || 0)),
      pointsCount: this.sessionData.route.length
    };
  }

  /**
   * Callbacks et événements
   */
  onLocationUpdate(callback: (location: LocationData) => void) {
    this.callbacks.set('locationUpdate', callback);
  }

  onTrackingStarted(callback: (session: TrackingSession) => void) {
    this.callbacks.set('trackingStarted', callback);
  }

  onTrackingStopped(callback: (data: { session: TrackingSession; stats: any }) => void) {
    this.callbacks.set('trackingStopped', callback);
  }

  onGeofenceEvent(callback: (event: any) => void) {
    this.callbacks.set('geofenceEvent', callback);
  }

  onLocationError(callback: (error: any) => void) {
    this.callbacks.set('locationError', callback);
  }

  private triggerCallback(eventName: string, data: any) {
    const callback = this.callbacks.get(eventName);
    if (callback) {
      try {
        callback(data);
      } catch (error) {
        console.error(`Error in ${eventName} callback:`, error);
      }
    }
  }

  private sendWebSocketMessage(message: any) {
    if (this.wsConnection?.readyState === WebSocket.OPEN) {
      this.wsConnection.send(JSON.stringify(message));
    }
  }

  private handleCourierLocationUpdate(data: any) {
    this.triggerCallback('courierLocationReceived', data);
  }

  private handleRouteUpdate(data: any) {
    this.triggerCallback('routeUpdate', data);
  }

  private handleGeofenceEvent(data: any) {
    this.triggerCallback('geofenceEventReceived', data);
  }

  private handleTrafficUpdate(data: any) {
    this.triggerCallback('trafficUpdate', data);
  }

  /**
   * API publique
   */
  getCurrentLocation(): LocationData | null {
    return this.locationHistory.length > 0 
      ? this.locationHistory[this.locationHistory.length - 1] 
      : null;
  }

  getSession(): TrackingSession | null {
    return this.sessionData;
  }

  isCurrentlyTracking(): boolean {
    return this.isTracking;
  }

  getLocationHistory(): LocationData[] {
    return [...this.locationHistory];
  }

  cleanup() {
    this.stopTracking();
    if (this.wsConnection) {
      this.wsConnection.close();
    }
    this.callbacks.clear();
  }
}

export default TrackingService;