/**
 * GPS-Dispatch Integration Service
 * ERP Transportistas
 *
 * Integrates GPS module with Dispatch module for real-time position updates.
 * Sprint: S3 - TASK-007
 * Module: MAI-005/MAI-006 Integration
 */

import { DataSource, Repository } from 'typeorm';
import { EstadoUnidad, EstadoUnidadEnum } from '../entities/estado-unidad.entity';
import { DispositivoGps } from '../../gps/entities/dispositivo-gps.entity';
import { PosicionGps } from '../../gps/entities/posicion-gps.entity';
import { SugerenciaAsignacion, ParametrosSugerenciaTransporte } from './dispatch.service';

/**
 * Interface for GPS position with device info
 */
export interface PosicionUnidadGps {
  unidadId: string;
  dispositivoId: string;
  latitud: number;
  longitud: number;
  velocidadKmh?: number;
  timestamp: Date;
  enLinea: boolean;
}

/**
 * Interface for enhanced assignment suggestion with real GPS data
 */
export interface SugerenciaConGps extends SugerenciaAsignacion {
  posicionGpsActualizada: boolean;
  ultimaActualizacionGps?: Date;
  velocidadActualKmh?: number;
}

/**
 * GPS-Dispatch Integration Service
 *
 * Handles synchronization between GPS positions and dispatch unit status,
 * and enhances assignment suggestions with real-time GPS data.
 */
export class GpsDispatchIntegrationService {
  private estadoUnidadRepository: Repository<EstadoUnidad>;
  private dispositivoGpsRepository: Repository<DispositivoGps>;
  private posicionGpsRepository: Repository<PosicionGps>;

  constructor(private dataSource: DataSource) {
    this.estadoUnidadRepository = dataSource.getRepository(EstadoUnidad);
    this.dispositivoGpsRepository = dataSource.getRepository(DispositivoGps);
    this.posicionGpsRepository = dataSource.getRepository(PosicionGps);
  }

  /**
   * Synchronize unit status positions with GPS device positions
   * Updates EstadoUnidad.ultimaPosicion from DispositivoGps.ultimaPosicion
   */
  async sincronizarPosicionesGps(tenantId: string): Promise<number> {
    // Get all active GPS devices with positions
    const dispositivos = await this.dispositivoGpsRepository.find({
      where: { tenantId, activo: true },
    });

    let actualizados = 0;

    for (const dispositivo of dispositivos) {
      if (!dispositivo.ultimaPosicionLat || !dispositivo.ultimaPosicionLng) {
        continue;
      }

      // Find corresponding unit status
      const estadoUnidad = await this.estadoUnidadRepository.findOne({
        where: { tenantId, unidadId: dispositivo.unidadId },
      });

      if (!estadoUnidad) {
        continue;
      }

      // Check if GPS position is newer than stored position
      const gpsTimestamp = dispositivo.ultimaPosicionAt;
      const storedTimestamp = estadoUnidad.ultimaActualizacionUbicacion;

      if (!storedTimestamp || (gpsTimestamp && gpsTimestamp > storedTimestamp)) {
        estadoUnidad.ultimaPosicionLat = dispositivo.ultimaPosicionLat;
        estadoUnidad.ultimaPosicionLng = dispositivo.ultimaPosicionLng;
        estadoUnidad.ultimaActualizacionUbicacion = gpsTimestamp || new Date();
        await this.estadoUnidadRepository.save(estadoUnidad);
        actualizados++;
      }
    }

    return actualizados;
  }

  /**
   * Get all units with their current GPS positions
   */
  async obtenerUnidadesConPosicionGps(tenantId: string): Promise<PosicionUnidadGps[]> {
    const umbralMinutos = 10;
    const threshold = new Date(Date.now() - umbralMinutos * 60 * 1000);

    const dispositivos = await this.dispositivoGpsRepository.find({
      where: { tenantId, activo: true },
    });

    return dispositivos
      .filter(d => d.ultimaPosicionLat && d.ultimaPosicionLng)
      .map(d => ({
        unidadId: d.unidadId,
        dispositivoId: d.id,
        latitud: Number(d.ultimaPosicionLat),
        longitud: Number(d.ultimaPosicionLng),
        timestamp: d.ultimaPosicionAt || new Date(),
        enLinea: d.ultimaPosicionAt ? d.ultimaPosicionAt > threshold : false,
      }));
  }

  /**
   * Get enhanced assignment suggestions using real-time GPS data
   */
  async sugerirAsignacionConGps(
    tenantId: string,
    params: ParametrosSugerenciaTransporte
  ): Promise<SugerenciaConGps[]> {
    // First, sync positions from GPS
    await this.sincronizarPosicionesGps(tenantId);

    // Get available units
    const unidadesDisponibles = await this.estadoUnidadRepository.find({
      where: {
        tenantId,
        estado: EstadoUnidadEnum.AVAILABLE,
      },
    });

    const sugerencias: SugerenciaConGps[] = [];
    const umbralMinutos = 10;
    const threshold = new Date(Date.now() - umbralMinutos * 60 * 1000);

    for (const unidad of unidadesDisponibles) {
      // Get GPS device for unit
      const dispositivo = await this.dispositivoGpsRepository.findOne({
        where: { tenantId, unidadId: unidad.unidadId, activo: true },
      });

      // Determine position source (GPS real-time or cached)
      let lat: number | undefined;
      let lng: number | undefined;
      let posicionGpsActualizada = false;
      let ultimaActualizacionGps: Date | undefined;
      let velocidadActualKmh: number | undefined;

      if (dispositivo?.ultimaPosicionLat && dispositivo?.ultimaPosicionLng) {
        // Use GPS position
        lat = Number(dispositivo.ultimaPosicionLat);
        lng = Number(dispositivo.ultimaPosicionLng);
        ultimaActualizacionGps = dispositivo.ultimaPosicionAt;
        posicionGpsActualizada = dispositivo.ultimaPosicionAt ? dispositivo.ultimaPosicionAt > threshold : false;

        // Get last position record for velocity
        const ultimaPosicion = await this.posicionGpsRepository.findOne({
          where: { dispositivoId: dispositivo.id },
          order: { tiempoDispositivo: 'DESC' },
        });
        if (ultimaPosicion) {
          velocidadActualKmh = ultimaPosicion.velocidad ? Number(ultimaPosicion.velocidad) : undefined;
        }
      } else if (unidad.ultimaPosicionLat && unidad.ultimaPosicionLng) {
        // Fallback to cached position in unit status
        lat = Number(unidad.ultimaPosicionLat);
        lng = Number(unidad.ultimaPosicionLng);
        ultimaActualizacionGps = unidad.ultimaActualizacionUbicacion;
        posicionGpsActualizada = false;
      }

      if (!lat || !lng) continue;

      const distancia = this.calcularDistancia(params.origenLat, params.origenLng, lat, lng);

      // Base score from distance
      let score = Math.max(0, 100 - distancia * 1.5);
      const razones: string[] = [];

      // Distance bonus/penalty
      if (distancia <= 10) {
        razones.push('Unidad cercana (< 10km)');
        score += 15;
      } else if (distancia <= 25) {
        razones.push('Distancia media (10-25km)');
      } else {
        razones.push(`Unidad lejana (${distancia.toFixed(1)}km)`);
        score -= 15;
      }

      // GPS freshness bonus
      if (posicionGpsActualizada) {
        razones.push('GPS en tiempo real');
        score += 10;
      } else {
        razones.push('Posición no actualizada');
        score -= 5;
      }

      // Weight capacity check
      if (params.pesoKg && unidad.capacidadPesoKg) {
        if (params.pesoKg > Number(unidad.capacidadPesoKg)) {
          continue; // Disqualify
        }
        razones.push('Capacidad de peso adecuada');
        score += 5;
      }

      // Refrigeration check
      if (params.requiereFrio) {
        if (!unidad.esRefrigerada) {
          continue; // Disqualify
        }
        razones.push('Unidad refrigerada');
        score += 10;
      }

      // Velocity consideration (moving towards origin is better)
      if (velocidadActualKmh && velocidadActualKmh > 0) {
        razones.push(`En movimiento (${velocidadActualKmh.toFixed(0)} km/h)`);
        // Slight bonus for moving units as they might arrive sooner
        score += 3;
      }

      sugerencias.push({
        unidadId: unidad.unidadId,
        codigoUnidad: unidad.codigoUnidad,
        nombreUnidad: unidad.nombreUnidad,
        score: Math.round(score),
        distanciaKm: Math.round(distancia * 10) / 10,
        operadorIds: unidad.operadorIds,
        razones,
        posicionGpsActualizada,
        ultimaActualizacionGps,
        velocidadActualKmh,
      });
    }

    return sugerencias.sort((a, b) => b.score - a.score).slice(0, 5);
  }

  /**
   * Update unit status when GPS position changes
   * Called by GPS position service after receiving new position
   */
  async actualizarEstadoUnidadPorGps(
    tenantId: string,
    unidadId: string,
    lat: number,
    lng: number,
    timestamp: Date
  ): Promise<EstadoUnidad | null> {
    const estadoUnidad = await this.estadoUnidadRepository.findOne({
      where: { tenantId, unidadId },
    });

    if (!estadoUnidad) return null;

    estadoUnidad.ultimaPosicionLat = lat;
    estadoUnidad.ultimaPosicionLng = lng;
    estadoUnidad.ultimaActualizacionUbicacion = timestamp;

    // Update status based on position context (optional enhancement)
    // Could check if unit is within geofence of assigned destination

    return this.estadoUnidadRepository.save(estadoUnidad);
  }

  /**
   * Calculate Haversine distance between two points
   */
  private calcularDistancia(lat1: number, lng1: number, lat2: number, lng2: number): number {
    const R = 6371; // Earth's radius in km
    const dLat = this.toRad(lat2 - lat1);
    const dLng = this.toRad(lng2 - lng1);
    const a =
      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) *
      Math.sin(dLng / 2) * Math.sin(dLng / 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
  }

  private toRad(deg: number): number {
    return deg * (Math.PI / 180);
  }
}
