/**
 * EventoGeocerca Service
 * ERP Transportistas
 *
 * Business logic for geofence events (entry/exit/permanence).
 * Adapted from erp-mecanicas-diesel MMD-014 GPS Integration
 * Module: MAI-006 Tracking
 */

import { Repository, DataSource, Between, IsNull, Not } from 'typeorm';
import { EventoGeocerca, TipoEventoGeocerca } from '../entities/evento-geocerca.entity';
import { DispositivoGps } from '../entities/dispositivo-gps.entity';
import { PosicionGps } from '../entities/posicion-gps.entity';

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

export interface CreateEventoGeocercaDto {
  geocercaId: string;
  dispositivoId: string;
  unidadId: string;
  tipoEvento: TipoEventoGeocerca;
  posicionId?: string;
  latitud: number;
  longitud: number;
  tiempoEvento: Date;
  viajeId?: string;
  metadata?: Record<string, any>;
}

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

export interface EventoGeocercaFilters {
  geocercaId?: string;
  dispositivoId?: string;
  unidadId?: string;
  tipoEvento?: TipoEventoGeocerca;
  viajeId?: string;
}

export interface TiempoEnGeocerca {
  geocercaId: string;
  unidadId: string;
  tiempoTotalMinutos: number;
  visitasTotales: number;
  promedioMinutosPorVisita: number;
  ultimaEntrada?: Date;
  ultimaSalida?: Date;
}

export interface UnidadEnGeocerca {
  unidadId: string;
  dispositivoId: string;
  horaEntrada: Date;
  tiempoTranscurridoMinutos: number;
  latitud: number;
  longitud: number;
  metadata?: Record<string, any>;
}

export interface GeocercaEstadisticas {
  totalEventos: number;
  totalEntradas: number;
  totalSalidas: number;
  totalPermanencias: number;
  unidadesUnicas: number;
  tiempoPromedioMinutos: number;
  alertasGeneradas: number;
  periodoInicio: Date;
  periodoFin: Date;
}

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

export class EventoGeocercaService {
  private eventoGeocercaRepository: Repository<EventoGeocerca>;
  private dispositivoRepository: Repository<DispositivoGps>;
  private posicionRepository: Repository<PosicionGps>;

  constructor(dataSource: DataSource) {
    this.eventoGeocercaRepository = dataSource.getRepository(EventoGeocerca);
    this.dispositivoRepository = dataSource.getRepository(DispositivoGps);
    this.posicionRepository = dataSource.getRepository(PosicionGps);
  }

  /**
   * Record a new geofence event (entry/exit)
   */
  async create(tenantId: string, data: CreateEventoGeocercaDto): Promise<EventoGeocerca> {
    // Validate device exists
    const dispositivo = await this.dispositivoRepository.findOne({
      where: { id: data.dispositivoId, tenantId },
    });

    if (!dispositivo) {
      throw new Error(`Dispositivo ${data.dispositivoId} no encontrado`);
    }

    const evento = this.eventoGeocercaRepository.create({
      tenantId,
      geocercaId: data.geocercaId,
      dispositivoId: data.dispositivoId,
      unidadId: data.unidadId,
      tipoEvento: data.tipoEvento,
      posicionId: data.posicionId,
      latitud: data.latitud,
      longitud: data.longitud,
      tiempoEvento: data.tiempoEvento,
      viajeId: data.viajeId,
      metadata: data.metadata || {},
    });

    return this.eventoGeocercaRepository.save(evento);
  }

  /**
   * Find events for a specific geofence in date range
   */
  async findByGeocerca(
    tenantId: string,
    geocercaId: string,
    dateRange: DateRange
  ): Promise<EventoGeocerca[]> {
    return this.eventoGeocercaRepository.find({
      where: {
        tenantId,
        geocercaId,
        tiempoEvento: Between(dateRange.fechaInicio, dateRange.fechaFin),
      },
      order: { tiempoEvento: 'DESC' },
    });
  }

  /**
   * Find events for a specific vehicle/unit in date range
   */
  async findByUnidad(
    tenantId: string,
    unidadId: string,
    dateRange: DateRange
  ): Promise<EventoGeocerca[]> {
    return this.eventoGeocercaRepository.find({
      where: {
        tenantId,
        unidadId,
        tiempoEvento: Between(dateRange.fechaInicio, dateRange.fechaFin),
      },
      relations: ['dispositivo'],
      order: { tiempoEvento: 'DESC' },
    });
  }

  /**
   * Find recent geofence events across fleet
   */
  async findRecientes(tenantId: string, limit: number = 50): Promise<EventoGeocerca[]> {
    return this.eventoGeocercaRepository.find({
      where: { tenantId },
      relations: ['dispositivo'],
      order: { tiempoEvento: 'DESC' },
      take: limit,
    });
  }

  /**
   * Get entry events only for a geofence
   */
  async getEntradas(
    tenantId: string,
    geocercaId: string,
    dateRange: DateRange
  ): Promise<EventoGeocerca[]> {
    return this.eventoGeocercaRepository.find({
      where: {
        tenantId,
        geocercaId,
        tipoEvento: TipoEventoGeocerca.ENTRADA,
        tiempoEvento: Between(dateRange.fechaInicio, dateRange.fechaFin),
      },
      order: { tiempoEvento: 'DESC' },
    });
  }

  /**
   * Get exit events only for a geofence
   */
  async getSalidas(
    tenantId: string,
    geocercaId: string,
    dateRange: DateRange
  ): Promise<EventoGeocerca[]> {
    return this.eventoGeocercaRepository.find({
      where: {
        tenantId,
        geocercaId,
        tipoEvento: TipoEventoGeocerca.SALIDA,
        tiempoEvento: Between(dateRange.fechaInicio, dateRange.fechaFin),
      },
      order: { tiempoEvento: 'DESC' },
    });
  }

  /**
   * Calculate time spent in a geofence by a unit
   */
  async getTiempoEnGeocerca(
    tenantId: string,
    unidadId: string,
    geocercaId: string
  ): Promise<TiempoEnGeocerca> {
    // Get all entry/exit events for this unit in this geofence
    const eventos = await this.eventoGeocercaRepository.find({
      where: {
        tenantId,
        unidadId,
        geocercaId,
        tipoEvento: Not(TipoEventoGeocerca.PERMANENCIA),
      },
      order: { tiempoEvento: 'ASC' },
    });

    let tiempoTotalMinutos = 0;
    let visitasTotales = 0;
    let ultimaEntrada: Date | undefined;
    let ultimaSalida: Date | undefined;
    let entradaActual: Date | null = null;

    for (const evento of eventos) {
      if (evento.tipoEvento === TipoEventoGeocerca.ENTRADA) {
        entradaActual = evento.tiempoEvento;
        ultimaEntrada = evento.tiempoEvento;
      } else if (evento.tipoEvento === TipoEventoGeocerca.SALIDA && entradaActual) {
        const duracion = (evento.tiempoEvento.getTime() - entradaActual.getTime()) / (1000 * 60);
        tiempoTotalMinutos += duracion;
        visitasTotales++;
        ultimaSalida = evento.tiempoEvento;
        entradaActual = null;
      }
    }

    // If still inside (entry without exit), count time until now
    if (entradaActual) {
      const now = new Date();
      const duracion = (now.getTime() - entradaActual.getTime()) / (1000 * 60);
      tiempoTotalMinutos += duracion;
      visitasTotales++;
    }

    const promedioMinutosPorVisita = visitasTotales > 0
      ? tiempoTotalMinutos / visitasTotales
      : 0;

    return {
      geocercaId,
      unidadId,
      tiempoTotalMinutos: Math.round(tiempoTotalMinutos * 100) / 100,
      visitasTotales,
      promedioMinutosPorVisita: Math.round(promedioMinutosPorVisita * 100) / 100,
      ultimaEntrada,
      ultimaSalida,
    };
  }

  /**
   * Get units currently inside a geofence
   */
  async getUnidadesEnGeocerca(
    tenantId: string,
    geocercaId: string
  ): Promise<UnidadEnGeocerca[]> {
    // Find the latest event for each unit in this geofence
    const subQuery = this.eventoGeocercaRepository
      .createQueryBuilder('sub')
      .select('sub.unidad_id', 'unidad_id')
      .addSelect('MAX(sub.tiempo_evento)', 'max_tiempo')
      .where('sub.tenant_id = :tenantId', { tenantId })
      .andWhere('sub.geocerca_id = :geocercaId', { geocercaId })
      .groupBy('sub.unidad_id');

    const latestEvents = await this.eventoGeocercaRepository
      .createQueryBuilder('evento')
      .innerJoin(
        `(${subQuery.getQuery()})`,
        'latest',
        'evento.unidad_id = latest.unidad_id AND evento.tiempo_evento = latest.max_tiempo'
      )
      .setParameters({ tenantId, geocercaId })
      .where('evento.tenant_id = :tenantId', { tenantId })
      .andWhere('evento.geocerca_id = :geocercaId', { geocercaId })
      .getMany();

    // Filter only those where last event was ENTRADA
    const unidadesDentro: UnidadEnGeocerca[] = [];
    const now = new Date();

    for (const evento of latestEvents) {
      if (evento.tipoEvento === TipoEventoGeocerca.ENTRADA) {
        const tiempoTranscurridoMinutos =
          (now.getTime() - evento.tiempoEvento.getTime()) / (1000 * 60);

        unidadesDentro.push({
          unidadId: evento.unidadId,
          dispositivoId: evento.dispositivoId,
          horaEntrada: evento.tiempoEvento,
          tiempoTranscurridoMinutos: Math.round(tiempoTranscurridoMinutos * 100) / 100,
          latitud: Number(evento.latitud),
          longitud: Number(evento.longitud),
          metadata: evento.metadata,
        });
      }
    }

    return unidadesDentro;
  }

  /**
   * Trigger an alert for a geofence event
   */
  async triggerAlerta(tenantId: string, eventoId: string): Promise<EventoGeocerca | null> {
    const evento = await this.eventoGeocercaRepository.findOne({
      where: { id: eventoId, tenantId },
    });

    if (!evento) return null;

    // Mark the event as having triggered an alert
    evento.metadata = {
      ...evento.metadata,
      alertaTriggered: true,
      alertaTriggeredAt: new Date().toISOString(),
    };

    return this.eventoGeocercaRepository.save(evento);
  }

  /**
   * Get statistics for a geofence
   */
  async getEstadisticas(
    tenantId: string,
    geocercaId: string,
    dateRange: DateRange
  ): Promise<GeocercaEstadisticas> {
    const eventos = await this.eventoGeocercaRepository.find({
      where: {
        tenantId,
        geocercaId,
        tiempoEvento: Between(dateRange.fechaInicio, dateRange.fechaFin),
      },
    });

    let totalEntradas = 0;
    let totalSalidas = 0;
    let totalPermanencias = 0;
    let alertasGeneradas = 0;
    const unidadesSet = new Set<string>();
    const tiemposVisita: number[] = [];
    let entradaActualPorUnidad: Record<string, Date> = {};

    // Sort events by time for proper entry/exit pairing
    const eventosOrdenados = [...eventos].sort(
      (a, b) => a.tiempoEvento.getTime() - b.tiempoEvento.getTime()
    );

    for (const evento of eventosOrdenados) {
      unidadesSet.add(evento.unidadId);

      if (evento.metadata?.alertaTriggered) {
        alertasGeneradas++;
      }

      switch (evento.tipoEvento) {
        case TipoEventoGeocerca.ENTRADA:
          totalEntradas++;
          entradaActualPorUnidad[evento.unidadId] = evento.tiempoEvento;
          break;

        case TipoEventoGeocerca.SALIDA:
          totalSalidas++;
          if (entradaActualPorUnidad[evento.unidadId]) {
            const duracion =
              (evento.tiempoEvento.getTime() - entradaActualPorUnidad[evento.unidadId].getTime()) /
              (1000 * 60);
            tiemposVisita.push(duracion);
            delete entradaActualPorUnidad[evento.unidadId];
          }
          break;

        case TipoEventoGeocerca.PERMANENCIA:
          totalPermanencias++;
          break;
      }
    }

    const tiempoPromedioMinutos =
      tiemposVisita.length > 0
        ? tiemposVisita.reduce((a, b) => a + b, 0) / tiemposVisita.length
        : 0;

    return {
      totalEventos: eventos.length,
      totalEntradas,
      totalSalidas,
      totalPermanencias,
      unidadesUnicas: unidadesSet.size,
      tiempoPromedioMinutos: Math.round(tiempoPromedioMinutos * 100) / 100,
      alertasGeneradas,
      periodoInicio: dateRange.fechaInicio,
      periodoFin: dateRange.fechaFin,
    };
  }

  /**
   * Find events with filters
   */
  async findAll(
    tenantId: string,
    filters: EventoGeocercaFilters = {},
    pagination = { page: 1, limit: 50 }
  ) {
    const queryBuilder = this.eventoGeocercaRepository
      .createQueryBuilder('evento')
      .where('evento.tenant_id = :tenantId', { tenantId });

    if (filters.geocercaId) {
      queryBuilder.andWhere('evento.geocerca_id = :geocercaId', {
        geocercaId: filters.geocercaId,
      });
    }

    if (filters.dispositivoId) {
      queryBuilder.andWhere('evento.dispositivo_id = :dispositivoId', {
        dispositivoId: filters.dispositivoId,
      });
    }

    if (filters.unidadId) {
      queryBuilder.andWhere('evento.unidad_id = :unidadId', {
        unidadId: filters.unidadId,
      });
    }

    if (filters.tipoEvento) {
      queryBuilder.andWhere('evento.tipo_evento = :tipoEvento', {
        tipoEvento: filters.tipoEvento,
      });
    }

    if (filters.viajeId) {
      queryBuilder.andWhere('evento.viaje_id = :viajeId', {
        viajeId: filters.viajeId,
      });
    }

    const skip = (pagination.page - 1) * pagination.limit;

    const [data, total] = await queryBuilder
      .leftJoinAndSelect('evento.dispositivo', 'dispositivo')
      .orderBy('evento.tiempo_evento', 'DESC')
      .skip(skip)
      .take(pagination.limit)
      .getManyAndCount();

    return {
      data,
      total,
      page: pagination.page,
      limit: pagination.limit,
      totalPages: Math.ceil(total / pagination.limit),
    };
  }

  /**
   * Delete old events (for data retention)
   */
  async eliminarEventosAntiguos(tenantId: string, antesDe: Date): Promise<number> {
    const result = await this.eventoGeocercaRepository
      .createQueryBuilder()
      .delete()
      .where('tenant_id = :tenantId', { tenantId })
      .andWhere('tiempo_evento < :antesDe', { antesDe })
      .execute();

    return result.affected || 0;
  }
}
