/**
 * EventoTracking Service
 * ERP Transportistas
 *
 * Business logic for tracking events, alerts, and trip timelines.
 * Module: MAI-006 Tracking
 */

import { Repository, DataSource, Between, In, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
import { EventoTracking, TipoEventoTracking, FuenteEvento } from '../entities/evento-tracking.entity';
import { Geocerca } from '../entities/geocerca.entity';

// ==================== DTOs ====================

export interface CreateEventoTrackingDto {
  viajeId?: string;
  unidadId?: string;
  operadorId?: string;
  tipoEvento: TipoEventoTracking;
  fuente?: FuenteEvento;
  latitud?: number;
  longitud?: number;
  direccion?: string;
  timestamp?: Date;
  timestampEvento?: Date;
  velocidad?: number;
  rumbo?: number;
  altitud?: number;
  precision?: number;
  odometro?: number;
  nivelCombustible?: number;
  motorEncendido?: boolean;
  descripcion?: string;
  datosAdicionales?: Record<string, any>;
  datos?: Record<string, any>;
  paradaId?: string;
  generadoPorId?: string;
  generadoPorTipo?: string;
  evidencias?: Record<string, any>;
  observaciones?: string;
}

export interface DateRange {
  fechaInicio: Date;
  fechaFin: Date;
}

export interface CreateAlertaDto {
  unidadId?: string;
  operadorId?: string;
  tipoAlerta: string;
  severidad: 'baja' | 'media' | 'alta' | 'critica';
  latitud?: number;
  longitud?: number;
  descripcion: string;
  datos?: Record<string, any>;
}

export interface PosicionGpsInput {
  latitud: number;
  longitud: number;
  velocidad?: number;
  rumbo?: number;
  altitud?: number;
  precision?: number;
  odometro?: number;
  nivelCombustible?: number;
  motorEncendido?: boolean;
  timestamp?: Date;
}

export interface EventoEstadisticas {
  totalEventos: number;
  eventosPorTipo: Record<string, number>;
  eventosPorFuente: Record<string, number>;
  alertasTotales: number;
  alertasPorSeveridad: Record<string, number>;
  alertasSinAcknowledge: number;
  promedioEventosPorDia: number;
}

// ==================== Service ====================

export class EventoTrackingService {
  private eventoRepository: Repository<EventoTracking>;
  private geocercaRepository: Repository<Geocerca>;

  constructor(dataSource: DataSource) {
    this.eventoRepository = dataSource.getRepository(EventoTracking);
    this.geocercaRepository = dataSource.getRepository(Geocerca);
  }

  /**
   * Create a new tracking event
   */
  async create(tenantId: string, viajeId: string, data: CreateEventoTrackingDto): Promise<EventoTracking> {
    const now = new Date();

    const evento = this.eventoRepository.create({
      tenantId,
      viajeId,
      unidadId: data.unidadId,
      operadorId: data.operadorId,
      tipoEvento: data.tipoEvento,
      fuente: data.fuente || FuenteEvento.SISTEMA,
      latitud: data.latitud,
      longitud: data.longitud,
      direccion: data.direccion,
      timestamp: data.timestamp || now,
      timestampEvento: data.timestampEvento || data.timestamp || now,
      velocidad: data.velocidad,
      rumbo: data.rumbo,
      altitud: data.altitud,
      precision: data.precision,
      odometro: data.odometro,
      nivelCombustible: data.nivelCombustible,
      motorEncendido: data.motorEncendido,
      descripcion: data.descripcion,
      datosAdicionales: data.datosAdicionales,
      datos: data.datos,
      paradaId: data.paradaId,
      generadoPorId: data.generadoPorId,
      generadoPorTipo: data.generadoPorTipo,
      evidencias: data.evidencias,
      observaciones: data.observaciones,
    });

    return this.eventoRepository.save(evento);
  }

  /**
   * Find all events for a trip
   */
  async findByViaje(tenantId: string, viajeId: string): Promise<EventoTracking[]> {
    return this.eventoRepository.find({
      where: { tenantId, viajeId },
      order: { timestampEvento: 'ASC' },
    });
  }

  /**
   * Find events by vehicle/unit in date range
   */
  async findByUnidad(
    tenantId: string,
    unidadId: string,
    dateRange: DateRange
  ): Promise<EventoTracking[]> {
    return this.eventoRepository.find({
      where: {
        tenantId,
        unidadId,
        timestampEvento: Between(dateRange.fechaInicio, dateRange.fechaFin),
      },
      order: { timestampEvento: 'DESC' },
    });
  }

  /**
   * Find events by type in date range
   */
  async findByTipo(
    tenantId: string,
    tipo: TipoEventoTracking,
    dateRange: DateRange
  ): Promise<EventoTracking[]> {
    return this.eventoRepository.find({
      where: {
        tenantId,
        tipoEvento: tipo,
        timestampEvento: Between(dateRange.fechaInicio, dateRange.fechaFin),
      },
      order: { timestampEvento: 'DESC' },
    });
  }

  /**
   * Get the last event for a trip
   */
  async getUltimoEvento(tenantId: string, viajeId: string): Promise<EventoTracking | null> {
    return this.eventoRepository.findOne({
      where: { tenantId, viajeId },
      order: { timestampEvento: 'DESC' },
    });
  }

  /**
   * Get recent events across the fleet
   */
  async getEventosRecientes(tenantId: string, limit: number = 50): Promise<EventoTracking[]> {
    return this.eventoRepository.find({
      where: { tenantId },
      order: { timestampEvento: 'DESC' },
      take: limit,
    });
  }

  /**
   * Get full timeline for a trip (ordered events)
   */
  async getTimeline(tenantId: string, viajeId: string): Promise<EventoTracking[]> {
    return this.eventoRepository.find({
      where: { tenantId, viajeId },
      order: { timestampEvento: 'ASC' },
    });
  }

  /**
   * Auto-create event from GPS position data
   */
  async createFromGPS(
    tenantId: string,
    viajeId: string,
    posicionGps: PosicionGpsInput & { unidadId: string; operadorId?: string }
  ): Promise<EventoTracking> {
    const now = new Date();

    const evento = this.eventoRepository.create({
      tenantId,
      viajeId,
      unidadId: posicionGps.unidadId,
      operadorId: posicionGps.operadorId,
      tipoEvento: TipoEventoTracking.GPS_POSICION,
      fuente: FuenteEvento.GPS,
      latitud: posicionGps.latitud,
      longitud: posicionGps.longitud,
      timestamp: posicionGps.timestamp || now,
      timestampEvento: posicionGps.timestamp || now,
      velocidad: posicionGps.velocidad,
      rumbo: posicionGps.rumbo,
      altitud: posicionGps.altitud,
      precision: posicionGps.precision,
      odometro: posicionGps.odometro,
      nivelCombustible: posicionGps.nivelCombustible,
      motorEncendido: posicionGps.motorEncendido,
    });

    const savedEvento = await this.eventoRepository.save(evento);

    // Check geofences asynchronously
    this.checkGeocercas(tenantId, savedEvento).catch(err => {
      console.error('Error checking geofences:', err);
    });

    return savedEvento;
  }

  /**
   * Create an alert event
   */
  async createAlerta(
    tenantId: string,
    viajeId: string,
    alertaData: CreateAlertaDto
  ): Promise<EventoTracking> {
    const now = new Date();

    // Map alert type to event type
    let tipoEvento = TipoEventoTracking.INCIDENTE;
    if (alertaData.tipoAlerta === 'desvio') {
      tipoEvento = TipoEventoTracking.DESVIO;
    } else if (alertaData.tipoAlerta === 'parada') {
      tipoEvento = TipoEventoTracking.PARADA;
    }

    const evento = this.eventoRepository.create({
      tenantId,
      viajeId,
      unidadId: alertaData.unidadId,
      operadorId: alertaData.operadorId,
      tipoEvento,
      fuente: FuenteEvento.SISTEMA,
      latitud: alertaData.latitud,
      longitud: alertaData.longitud,
      timestamp: now,
      timestampEvento: now,
      descripcion: alertaData.descripcion,
      datos: {
        esAlerta: true,
        tipoAlerta: alertaData.tipoAlerta,
        severidad: alertaData.severidad,
        acknowledged: false,
        acknowledgedAt: null,
        acknowledgedBy: null,
        ...alertaData.datos,
      },
    });

    return this.eventoRepository.save(evento);
  }

  /**
   * Get alerts in date range, optionally filtered by severity
   */
  async getAlertas(
    tenantId: string,
    dateRange: DateRange,
    severidad?: 'baja' | 'media' | 'alta' | 'critica'
  ): Promise<EventoTracking[]> {
    const queryBuilder = this.eventoRepository
      .createQueryBuilder('evento')
      .where('evento.tenant_id = :tenantId', { tenantId })
      .andWhere("evento.datos->>'esAlerta' = :esAlerta", { esAlerta: 'true' })
      .andWhere('evento.timestamp_evento BETWEEN :fechaInicio AND :fechaFin', {
        fechaInicio: dateRange.fechaInicio,
        fechaFin: dateRange.fechaFin,
      });

    if (severidad) {
      queryBuilder.andWhere("evento.datos->>'severidad' = :severidad", { severidad });
    }

    return queryBuilder
      .orderBy('evento.timestamp_evento', 'DESC')
      .getMany();
  }

  /**
   * Acknowledge an alert
   */
  async acknowledgeAlerta(
    tenantId: string,
    alertaId: string,
    userId: string
  ): Promise<EventoTracking | null> {
    const evento = await this.eventoRepository.findOne({
      where: { id: alertaId, tenantId },
    });

    if (!evento || !evento.datos?.esAlerta) {
      return null;
    }

    const now = new Date();
    evento.datos = {
      ...evento.datos,
      acknowledged: true,
      acknowledgedAt: now.toISOString(),
      acknowledgedBy: userId,
    };

    return this.eventoRepository.save(evento);
  }

  /**
   * Get event statistics for date range
   */
  async getEstadisticas(tenantId: string, dateRange: DateRange): Promise<EventoEstadisticas> {
    // Get events in range
    const eventos = await this.eventoRepository.find({
      where: {
        tenantId,
        timestampEvento: Between(dateRange.fechaInicio, dateRange.fechaFin),
      },
    });

    // Calculate statistics
    const eventosPorTipo: Record<string, number> = {};
    const eventosPorFuente: Record<string, number> = {};
    const alertasPorSeveridad: Record<string, number> = {};
    let alertasTotales = 0;
    let alertasSinAcknowledge = 0;

    for (const evento of eventos) {
      // Count by type
      eventosPorTipo[evento.tipoEvento] = (eventosPorTipo[evento.tipoEvento] || 0) + 1;

      // Count by source
      if (evento.fuente) {
        eventosPorFuente[evento.fuente] = (eventosPorFuente[evento.fuente] || 0) + 1;
      }

      // Count alerts
      if (evento.datos?.esAlerta) {
        alertasTotales++;
        const severidad = evento.datos.severidad || 'media';
        alertasPorSeveridad[severidad] = (alertasPorSeveridad[severidad] || 0) + 1;

        if (!evento.datos.acknowledged) {
          alertasSinAcknowledge++;
        }
      }
    }

    // Calculate average events per day
    const diasEnRango = Math.max(
      1,
      Math.ceil(
        (dateRange.fechaFin.getTime() - dateRange.fechaInicio.getTime()) / (1000 * 60 * 60 * 24)
      )
    );
    const promedioEventosPorDia = eventos.length / diasEnRango;

    return {
      totalEventos: eventos.length,
      eventosPorTipo,
      eventosPorFuente,
      alertasTotales,
      alertasPorSeveridad,
      alertasSinAcknowledge,
      promedioEventosPorDia: Math.round(promedioEventosPorDia * 100) / 100,
    };
  }

  /**
   * Check if a position triggers any geofence events
   */
  private async checkGeocercas(tenantId: string, evento: EventoTracking): Promise<void> {
    if (!evento.latitud || !evento.longitud) return;

    // Get active geofences
    const geocercas = await this.geocercaRepository.find({
      where: { tenantId, activa: true },
    });

    for (const geocerca of geocercas) {
      const dentroGeocerca = this.puntoEnGeocerca(
        Number(evento.latitud),
        Number(evento.longitud),
        geocerca
      );

      if (dentroGeocerca && geocerca.alertaEntrada) {
        // Create geofence entry event
        await this.create(tenantId, evento.viajeId, {
          unidadId: evento.unidadId,
          operadorId: evento.operadorId,
          tipoEvento: TipoEventoTracking.GEOCERCA_ENTRADA,
          fuente: FuenteEvento.GEOCERCA,
          latitud: evento.latitud,
          longitud: evento.longitud,
          timestamp: evento.timestamp,
          descripcion: `Entrada a geocerca: ${geocerca.nombre}`,
          datosAdicionales: {
            geocercaId: geocerca.id,
            geocercaNombre: geocerca.nombre,
            geocercaTipo: geocerca.tipo,
          },
        });
      }
    }
  }

  /**
   * Check if a point is inside a geofence
   */
  private puntoEnGeocerca(lat: number, lon: number, geocerca: Geocerca): boolean {
    if (geocerca.esCircular && geocerca.centroLatitud && geocerca.centroLongitud && geocerca.radioMetros) {
      const distancia = this.calcularDistancia(
        lat,
        lon,
        Number(geocerca.centroLatitud),
        Number(geocerca.centroLongitud)
      );
      return distancia <= Number(geocerca.radioMetros);
    }

    // For circular geofences using geometria
    if (geocerca.geometria?.type === 'Point' && geocerca.radio) {
      const centro = geocerca.geometria.coordinates;
      if (centro && centro.length >= 2) {
        const distancia = this.calcularDistancia(lat, lon, centro[1], centro[0]);
        return distancia <= Number(geocerca.radio);
      }
    }

    // For polygon geofences, would need proper geometric check (PostGIS recommended)
    return false;
  }

  /**
   * Calculate distance between two points using Haversine formula
   */
  private calcularDistancia(lat1: number, lon1: number, lat2: number, lon2: number): number {
    const R = 6371000; // Earth radius in meters
    const dLat = this.toRad(lat2 - lat1);
    const dLon = this.toRad(lon2 - lon1);
    const a =
      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(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 toRad(deg: number): number {
    return deg * (Math.PI / 180);
  }
}
