import { Repository, FindOptionsWhere, ILike, In, Between, MoreThanOrEqual, LessThanOrEqual, IsNull, Not } from 'typeorm';
import {
  Viaje,
  EstadoViaje,
  InfoSello,
  ParadaViaje,
  TipoParada,
  EstadoParada,
  Pod,
  EstadoPod,
  FotoEvidencia,
} from '../entities';

// =============================================================================
// INTERFACES Y TIPOS
// =============================================================================

export interface ViajeSearchParams {
  tenantId: string;
  search?: string;
  estado?: EstadoViaje;
  estados?: EstadoViaje[];
  unidadId?: string;
  operadorId?: string;
  clienteId?: string;
  fechaDesde?: Date;
  fechaHasta?: Date;
  limit?: number;
  offset?: number;
  orderBy?: 'createdAt' | 'fechaSalida' | 'fechaLlegada' | 'codigo';
  orderDir?: 'ASC' | 'DESC';
}

export interface CreateViajeDto {
  // Unidad y operador
  unidadId: string;
  remolqueId?: string;
  operadorId: string;

  // Cliente (opcional)
  clienteId?: string;

  // Ruta
  origenPrincipal: string;
  origenCiudad?: string;
  destinoPrincipal: string;
  destinoCiudad?: string;
  distanciaEstimadaKm?: number;
  tiempoEstimadoHoras?: number;

  // Fechas
  fechaSalidaProgramada?: Date;
  fechaLlegadaProgramada?: Date;
}

export interface UpdateViajeDto extends Partial<CreateViajeDto> {
  estado?: EstadoViaje;
  kmInicio?: number;
  kmFin?: number;
  costoCombustible?: number;
  costoPeajes?: number;
  costoViaticos?: number;
  costoOtros?: number;
  ingresoTotal?: number;
}

export interface CreateParadaDto {
  tipo: TipoParada;
  direccion: string;
  ciudad?: string;
  estado?: string;
  codigoPostal?: string;
  latitud?: number;
  longitud?: number;
  contactoNombre?: string;
  contactoTelefono?: string;
  horaProgramadaLlegada?: Date;
  horaProgramadaSalida?: Date;
  otsIds?: string[];
  observaciones?: string;
}

export interface UpdateParadaDto {
  horaProgramadaLlegada?: Date;
  horaProgramadaSalida?: Date;
  horaRealLlegada?: Date;
  horaRealSalida?: Date;
  estadoParada?: EstadoParada;
  observaciones?: string;
}

export interface CompletarParadaDto {
  receptorNombre: string;
  receptorIdentificacion?: string;
  piezasEntregadas?: number;
  piezasRechazadas?: number;
  piezasDanadas?: number;
  firmaDigital?: string;
  fotosEntrega?: FotoEvidencia[];
  observaciones?: string;
  motivoRechazo?: string;
}

export interface DespachoDto {
  kmInicio: number;
  sellosSalida?: InfoSello[];
  checklistCompletado: boolean;
  checklistObservaciones?: string;
}

export interface ETAInfo {
  viajeId: string;
  paradaActualSecuencia: number;
  paradaSiguiente?: {
    id: string;
    secuencia: number;
    direccion: string;
    ciudad?: string;
    etaEstimada: Date;
    distanciaRestanteKm: number;
    tiempoRestanteMinutos: number;
  };
  destinoFinal: {
    etaEstimada: Date;
    distanciaRestanteKm: number;
    tiempoRestanteMinutos: number;
  };
  progresoViaje: number; // Porcentaje 0-100
  ultimaActualizacion: Date;
}

export interface ViajeStats {
  total: number;
  porEstado: Record<EstadoViaje, number>;
  kmTotales: number;
  costoTotal: number;
  ingresoTotal: number;
  margenTotal: number;
  promedioKmPorViaje: number;
  promedioCostoPorViaje: number;
  tiempoPromedioHoras: number;
}

export interface DateRange {
  from: Date;
  to: Date;
}

// =============================================================================
// SERVICIO DE VIAJES
// =============================================================================

export class ViajesService {
  constructor(
    private readonly viajeRepository: Repository<Viaje>,
    private readonly paradaRepository: Repository<ParadaViaje>,
    private readonly podRepository: Repository<Pod>
  ) {}

  // ===========================================================================
  // METODOS CRUD BASICOS
  // ===========================================================================

  /**
   * Crear nuevo viaje
   */
  async create(tenantId: string, dto: CreateViajeDto, createdById: string): Promise<Viaje> {
    const codigo = await this.generateCodigo(tenantId);

    const viaje = this.viajeRepository.create({
      tenantId,
      codigo,
      numeroViaje: codigo,

      // Unidad y operador
      unidadId: dto.unidadId,
      remolqueId: dto.remolqueId,
      operadorId: dto.operadorId,

      // Cliente
      clienteId: dto.clienteId,

      // Ruta
      origenPrincipal: dto.origenPrincipal,
      origenCiudad: dto.origenCiudad,
      destinoPrincipal: dto.destinoPrincipal,
      destinoCiudad: dto.destinoCiudad,
      distanciaEstimadaKm: dto.distanciaEstimadaKm,
      tiempoEstimadoHoras: dto.tiempoEstimadoHoras,

      // Fechas
      fechaSalidaProgramada: dto.fechaSalidaProgramada,
      fechaProgramadaSalida: dto.fechaSalidaProgramada,
      fechaLlegadaProgramada: dto.fechaLlegadaProgramada,

      // Estado inicial
      estado: EstadoViaje.BORRADOR,

      // Auditoria
      createdById,
    });

    return this.viajeRepository.save(viaje);
  }

  /**
   * Buscar viaje por ID con relaciones
   */
  async findById(tenantId: string, id: string): Promise<Viaje | null> {
    const viaje = await this.viajeRepository.findOne({
      where: { id, tenantId },
    });

    if (viaje) {
      // Cargar paradas manualmente si existen
      const paradas = await this.paradaRepository.find({
        where: { tenantId, viajeId: id },
        order: { secuencia: 'ASC' },
      });
      (viaje as any).paradas = paradas;

      // Cargar PODs
      const pods = await this.podRepository.find({
        where: { tenantId, viajeId: id },
      });
      (viaje as any).pods = pods;
    }

    return viaje;
  }

  /**
   * Buscar viaje por codigo
   */
  async findByCodigo(tenantId: string, codigo: string): Promise<Viaje | null> {
    return this.viajeRepository.findOne({
      where: { codigo, tenantId },
    });
  }

  /**
   * Listar viajes con filtros y paginacion
   */
  /**
   * Listar viajes con filtros y paginacion
   * Soporta dos firmas:
   * - findAll(tenantId: string, filters: object) - nueva firma
   * - findAll(params: { tenantId, ...filters }) - compatibilidad con controller existente
   */
  async findAll(
    tenantIdOrParams: string | ViajeSearchParams,
    filters: Omit<ViajeSearchParams, 'tenantId'> = {}
  ): Promise<{ data: Viaje[]; total: number; page: number; pageSize: number }> {
    // Soportar ambas firmas
    let tenantId: string;
    let actualFilters: Omit<ViajeSearchParams, 'tenantId'>;

    if (typeof tenantIdOrParams === 'string') {
      tenantId = tenantIdOrParams;
      actualFilters = filters;
    } else {
      tenantId = tenantIdOrParams.tenantId;
      const { tenantId: _, ...rest } = tenantIdOrParams;
      actualFilters = rest;
    }

    const {
      search,
      estado,
      estados,
      unidadId,
      operadorId,
      clienteId,
      fechaDesde,
      fechaHasta,
      limit = 50,
      offset = 0,
      orderBy = 'createdAt',
      orderDir = 'DESC',
    } = actualFilters;

    const where: FindOptionsWhere<Viaje>[] = [];
    const baseWhere: FindOptionsWhere<Viaje> = { tenantId };

    // Filtros de estado
    if (estado) {
      baseWhere.estado = estado;
    } else if (estados && estados.length > 0) {
      baseWhere.estado = In(estados);
    }

    // Filtros de recursos
    if (unidadId) baseWhere.unidadId = unidadId;
    if (operadorId) baseWhere.operadorId = operadorId;
    if (clienteId) baseWhere.clienteId = clienteId;

    // Filtros de fecha
    if (fechaDesde && fechaHasta) {
      baseWhere.fechaSalidaProgramada = Between(fechaDesde, fechaHasta);
    } else if (fechaDesde) {
      baseWhere.fechaSalidaProgramada = MoreThanOrEqual(fechaDesde);
    } else if (fechaHasta) {
      baseWhere.fechaSalidaProgramada = LessThanOrEqual(fechaHasta);
    }

    // Busqueda por texto
    if (search) {
      where.push(
        { ...baseWhere, codigo: ILike(`%${search}%`) },
        { ...baseWhere, numeroViaje: ILike(`%${search}%`) },
        { ...baseWhere, origenPrincipal: ILike(`%${search}%`) },
        { ...baseWhere, origenCiudad: ILike(`%${search}%`) },
        { ...baseWhere, destinoPrincipal: ILike(`%${search}%`) },
        { ...baseWhere, destinoCiudad: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    // Mapeo de campo de ordenamiento
    const orderField = this.mapOrderField(orderBy);

    const [data, total] = await this.viajeRepository.findAndCount({
      where,
      take: limit,
      skip: offset,
      order: { [orderField]: orderDir },
    });

    return {
      data,
      total,
      page: Math.floor(offset / limit) + 1,
      pageSize: limit,
    };
  }

  /**
   * Buscar viajes por unidad
   */
  async findByUnidad(
    tenantId: string,
    unidadId: string,
    limit: number = 50
  ): Promise<Viaje[]> {
    return this.viajeRepository.find({
      where: { tenantId, unidadId },
      order: { fechaSalidaProgramada: 'DESC' },
      take: limit,
    });
  }

  /**
   * Buscar viajes por operador
   */
  async findByOperador(
    tenantId: string,
    operadorId: string,
    limit: number = 50
  ): Promise<Viaje[]> {
    return this.viajeRepository.find({
      where: { tenantId, operadorId },
      order: { fechaSalidaProgramada: 'DESC' },
      take: limit,
    });
  }

  /**
   * Obtener viajes activos (en progreso)
   */
  async getActiveTrips(tenantId: string): Promise<Viaje[]> {
    const estadosActivos = [
      EstadoViaje.DESPACHADO,
      EstadoViaje.EN_TRANSITO,
      EstadoViaje.EN_DESTINO,
    ];

    const viajes = await this.viajeRepository.find({
      where: { tenantId, estado: In(estadosActivos) },
      order: { fechaSalidaReal: 'ASC' },
    });

    // Cargar paradas para cada viaje activo
    for (const viaje of viajes) {
      const paradas = await this.paradaRepository.find({
        where: { tenantId, viajeId: viaje.id },
        order: { secuencia: 'ASC' },
      });
      (viaje as any).paradas = paradas;
    }

    return viajes;
  }

  /**
   * Actualizar viaje
   */
  async update(
    tenantId: string,
    id: string,
    dto: UpdateViajeDto,
    updatedById: string
  ): Promise<Viaje | null> {
    const viaje = await this.findById(tenantId, id);
    if (!viaje) return null;

    // No permitir modificaciones en estados finales
    const estadosFinales = [
      EstadoViaje.CERRADO,
      EstadoViaje.FACTURADO,
      EstadoViaje.COBRADO,
      EstadoViaje.CANCELADO,
    ];
    if (estadosFinales.includes(viaje.estado)) {
      throw new Error(`No se puede modificar un viaje en estado ${viaje.estado}`);
    }

    // Recalcular costo total si se actualizan costos
    let costoTotal = viaje.costoTotal;
    if (dto.costoCombustible !== undefined ||
        dto.costoPeajes !== undefined ||
        dto.costoViaticos !== undefined ||
        dto.costoOtros !== undefined) {
      costoTotal =
        (dto.costoCombustible ?? viaje.costoCombustible) +
        (dto.costoPeajes ?? viaje.costoPeajes) +
        (dto.costoViaticos ?? viaje.costoViaticos) +
        (dto.costoOtros ?? viaje.costoOtros);
    }

    Object.assign(viaje, {
      ...dto,
      costoTotal,
      updatedById,
    });

    return this.viajeRepository.save(viaje);
  }

  // ===========================================================================
  // CICLO DE VIDA DEL VIAJE
  // ===========================================================================

  /**
   * Planear viaje (pasar de borrador a planeado)
   */
  async plan(tenantId: string, id: string, updatedById: string): Promise<Viaje | null> {
    const viaje = await this.findById(tenantId, id);
    if (!viaje) return null;

    if (viaje.estado !== EstadoViaje.BORRADOR) {
      throw new Error('El viaje debe estar en BORRADOR para ser planeado');
    }

    viaje.estado = EstadoViaje.PLANEADO;
    viaje.updatedById = updatedById;

    return this.viajeRepository.save(viaje);
  }

  /**
   * Iniciar viaje (despachar)
   */
  async start(
    tenantId: string,
    id: string,
    despachoData: DespachoDto,
    startedById: string
  ): Promise<Viaje | null> {
    const viaje = await this.findById(tenantId, id);
    if (!viaje) return null;

    if (viaje.estado !== EstadoViaje.PLANEADO) {
      throw new Error('El viaje debe estar PLANEADO para ser despachado');
    }

    if (!despachoData.checklistCompletado) {
      throw new Error('El checklist pre-viaje debe estar completado');
    }

    viaje.estado = EstadoViaje.DESPACHADO;
    viaje.fechaSalidaReal = new Date();
    viaje.fechaRealSalida = new Date();
    viaje.kmInicio = despachoData.kmInicio;
    viaje.sellosSalida = despachoData.sellosSalida || [];
    viaje.checklistCompletado = true;
    viaje.checklistFecha = new Date();
    viaje.checklistObservaciones = despachoData.checklistObservaciones || '';
    viaje.updatedById = startedById;

    return this.viajeRepository.save(viaje);
  }

  /**
   * Iniciar transito
   */
  async startTransit(tenantId: string, id: string, updatedById: string): Promise<Viaje | null> {
    const viaje = await this.findById(tenantId, id);
    if (!viaje) return null;

    if (viaje.estado !== EstadoViaje.DESPACHADO) {
      throw new Error('El viaje debe estar DESPACHADO para iniciar transito');
    }

    viaje.estado = EstadoViaje.EN_TRANSITO;
    viaje.updatedById = updatedById;

    return this.viajeRepository.save(viaje);
  }

  /**
   * Registrar llegada a destino
   */
  async arriveAtDestination(
    tenantId: string,
    id: string,
    kmActual: number,
    updatedById: string
  ): Promise<Viaje | null> {
    const viaje = await this.findById(tenantId, id);
    if (!viaje) return null;

    if (viaje.estado !== EstadoViaje.EN_TRANSITO) {
      throw new Error('El viaje debe estar EN_TRANSITO para registrar llegada');
    }

    viaje.estado = EstadoViaje.EN_DESTINO;
    viaje.kmFin = kmActual;
    viaje.updatedById = updatedById;

    return this.viajeRepository.save(viaje);
  }

  /**
   * Completar viaje (todas las entregas realizadas)
   */
  async complete(
    tenantId: string,
    id: string,
    completionData: {
      kmFin: number;
      sellosLlegada?: InfoSello[];
    },
    completedById: string
  ): Promise<Viaje | null> {
    const viaje = await this.findById(tenantId, id);
    if (!viaje) return null;

    if (viaje.estado !== EstadoViaje.EN_DESTINO && viaje.estado !== EstadoViaje.EN_TRANSITO) {
      throw new Error('El viaje debe estar EN_DESTINO o EN_TRANSITO para completarse');
    }

    // Verificar que todas las paradas esten completadas
    const paradas = await this.paradaRepository.find({
      where: { tenantId, viajeId: id },
    });

    const paradasPendientes = paradas.filter(
      p => p.estadoParada !== EstadoParada.COMPLETADA && p.estadoParada !== EstadoParada.OMITIDA
    );

    if (paradasPendientes.length > 0) {
      throw new Error(`Hay ${paradasPendientes.length} parada(s) pendientes de completar`);
    }

    viaje.estado = EstadoViaje.ENTREGADO;
    viaje.fechaLlegadaReal = new Date();
    viaje.fechaRealLlegada = new Date();
    viaje.kmFin = completionData.kmFin;
    viaje.sellosLlegada = completionData.sellosLlegada || [];
    viaje.updatedById = completedById;

    return this.viajeRepository.save(viaje);
  }

  /**
   * Cerrar viaje (revision completada, listo para facturar)
   */
  async close(tenantId: string, id: string, closedById: string): Promise<Viaje | null> {
    const viaje = await this.findById(tenantId, id);
    if (!viaje) return null;

    if (viaje.estado !== EstadoViaje.ENTREGADO) {
      throw new Error('El viaje debe estar ENTREGADO para cerrarse');
    }

    viaje.estado = EstadoViaje.CERRADO;
    viaje.updatedById = closedById;

    return this.viajeRepository.save(viaje);
  }

  /**
   * Cancelar viaje
   */
  async cancel(
    tenantId: string,
    id: string,
    reason: string,
    cancelledById: string
  ): Promise<Viaje | null> {
    const viaje = await this.findById(tenantId, id);
    if (!viaje) return null;

    // No se puede cancelar un viaje ya despachado/en transito
    const estadosNoCancelables = [
      EstadoViaje.DESPACHADO,
      EstadoViaje.EN_TRANSITO,
      EstadoViaje.EN_DESTINO,
      EstadoViaje.ENTREGADO,
      EstadoViaje.CERRADO,
      EstadoViaje.FACTURADO,
      EstadoViaje.COBRADO,
      EstadoViaje.CANCELADO,
    ];

    if (estadosNoCancelables.includes(viaje.estado)) {
      throw new Error(`No se puede cancelar un viaje en estado ${viaje.estado}`);
    }

    viaje.estado = EstadoViaje.CANCELADO;
    viaje.checklistObservaciones = `${viaje.checklistObservaciones || ''}\n[CANCELADO] ${reason}`.trim();
    viaje.updatedById = cancelledById;

    return this.viajeRepository.save(viaje);
  }

  // ===========================================================================
  // GESTION DE PARADAS
  // ===========================================================================

  /**
   * Agregar parada al viaje
   */
  async addParada(
    tenantId: string,
    viajeId: string,
    paradaData: CreateParadaDto,
    createdById: string
  ): Promise<ParadaViaje> {
    const viaje = await this.findById(tenantId, viajeId);
    if (!viaje) {
      throw new Error('Viaje no encontrado');
    }

    // No agregar paradas a viajes ya despachados o posteriores
    const estadosModificables = [EstadoViaje.BORRADOR, EstadoViaje.PLANEADO];
    if (!estadosModificables.includes(viaje.estado)) {
      throw new Error(`No se pueden agregar paradas a un viaje en estado ${viaje.estado}`);
    }

    // Obtener siguiente secuencia
    const ultimaParada = await this.paradaRepository.findOne({
      where: { tenantId, viajeId },
      order: { secuencia: 'DESC' },
    });
    const nuevaSecuencia = (ultimaParada?.secuencia || 0) + 1;

    const parada = this.paradaRepository.create({
      tenantId,
      viajeId,
      secuencia: nuevaSecuencia,
      tipo: paradaData.tipo,
      direccion: paradaData.direccion,
      ciudad: paradaData.ciudad,
      estado: paradaData.estado,
      codigoPostal: paradaData.codigoPostal,
      latitud: paradaData.latitud,
      longitud: paradaData.longitud,
      contactoNombre: paradaData.contactoNombre,
      contactoTelefono: paradaData.contactoTelefono,
      horaProgramadaLlegada: paradaData.horaProgramadaLlegada,
      horaProgramadaSalida: paradaData.horaProgramadaSalida,
      otsIds: paradaData.otsIds || [],
      estadoParada: EstadoParada.PENDIENTE,
      observaciones: paradaData.observaciones,
    });

    return this.paradaRepository.save(parada);
  }

  /**
   * Actualizar parada
   */
  async updateParada(
    tenantId: string,
    paradaId: string,
    data: UpdateParadaDto,
    updatedById: string
  ): Promise<ParadaViaje | null> {
    const parada = await this.paradaRepository.findOne({
      where: { id: paradaId, tenantId },
    });

    if (!parada) return null;

    Object.assign(parada, data);

    return this.paradaRepository.save(parada);
  }

  /**
   * Registrar llegada a parada
   */
  async arrivedAtParada(
    tenantId: string,
    paradaId: string,
    updatedById: string
  ): Promise<ParadaViaje | null> {
    const parada = await this.paradaRepository.findOne({
      where: { id: paradaId, tenantId },
    });

    if (!parada) return null;

    parada.estadoParada = EstadoParada.LLEGADA;
    parada.horaRealLlegada = new Date();

    return this.paradaRepository.save(parada);
  }

  /**
   * Iniciar proceso en parada (descarga/carga)
   */
  async startParadaProcess(
    tenantId: string,
    paradaId: string,
    updatedById: string
  ): Promise<ParadaViaje | null> {
    const parada = await this.paradaRepository.findOne({
      where: { id: paradaId, tenantId },
    });

    if (!parada) return null;

    parada.estadoParada = EstadoParada.EN_PROCESO;

    return this.paradaRepository.save(parada);
  }

  /**
   * Completar parada con POD
   */
  async completeParada(
    tenantId: string,
    paradaId: string,
    podData: CompletarParadaDto,
    completedById: string
  ): Promise<{ parada: ParadaViaje; pod: Pod }> {
    const parada = await this.paradaRepository.findOne({
      where: { id: paradaId, tenantId },
    });

    if (!parada) {
      throw new Error('Parada no encontrada');
    }

    // Actualizar parada
    parada.estadoParada = EstadoParada.COMPLETADA;
    parada.horaRealSalida = new Date();
    parada.observaciones = podData.observaciones || parada.observaciones;

    // Determinar estado del POD
    let estadoPod = EstadoPod.COMPLETO;
    if (podData.motivoRechazo) {
      estadoPod = EstadoPod.RECHAZADO;
    } else if ((podData.piezasRechazadas || 0) > 0 || (podData.piezasDanadas || 0) > 0) {
      estadoPod = EstadoPod.PARCIAL;
    }

    // Crear POD
    const pod = this.podRepository.create({
      tenantId,
      viajeId: parada.viajeId,
      paradaId: parada.id,
      estado: estadoPod,
      receptorNombre: podData.receptorNombre,
      receptorIdentificacion: podData.receptorIdentificacion,
      fechaRecepcion: new Date(),
      firmaDigital: podData.firmaDigital,
      fotosEntrega: podData.fotosEntrega || [],
      piezasEntregadas: podData.piezasEntregadas,
      piezasRechazadas: podData.piezasRechazadas,
      piezasDanadas: podData.piezasDanadas,
      observaciones: podData.observaciones,
      motivoRechazo: podData.motivoRechazo,
      createdById: completedById,
    });

    // Guardar ambos
    const [savedParada, savedPod] = await Promise.all([
      this.paradaRepository.save(parada),
      this.podRepository.save(pod),
    ]);

    return { parada: savedParada, pod: savedPod };
  }

  /**
   * Omitir parada
   */
  async skipParada(
    tenantId: string,
    paradaId: string,
    reason: string,
    skippedById: string
  ): Promise<ParadaViaje | null> {
    const parada = await this.paradaRepository.findOne({
      where: { id: paradaId, tenantId },
    });

    if (!parada) return null;

    parada.estadoParada = EstadoParada.OMITIDA;
    parada.observaciones = `${parada.observaciones || ''}\n[OMITIDA] ${reason}`.trim();

    return this.paradaRepository.save(parada);
  }

  /**
   * Obtener paradas de un viaje
   */
  async getParadas(tenantId: string, viajeId: string): Promise<ParadaViaje[]> {
    return this.paradaRepository.find({
      where: { tenantId, viajeId },
      order: { secuencia: 'ASC' },
    });
  }

  /**
   * Reordenar paradas
   */
  async reorderParadas(
    tenantId: string,
    viajeId: string,
    newOrder: string[], // Array de IDs en nuevo orden
    updatedById: string
  ): Promise<ParadaViaje[]> {
    const viaje = await this.findById(tenantId, viajeId);
    if (!viaje) {
      throw new Error('Viaje no encontrado');
    }

    // Solo permitir reordenar en estados modificables
    const estadosModificables = [EstadoViaje.BORRADOR, EstadoViaje.PLANEADO];
    if (!estadosModificables.includes(viaje.estado)) {
      throw new Error(`No se pueden reordenar paradas en viaje con estado ${viaje.estado}`);
    }

    const paradas = await this.paradaRepository.find({
      where: { tenantId, viajeId },
    });

    // Actualizar secuencias
    for (let i = 0; i < newOrder.length; i++) {
      const parada = paradas.find(p => p.id === newOrder[i]);
      if (parada) {
        parada.secuencia = i + 1;
        await this.paradaRepository.save(parada);
      }
    }

    return this.getParadas(tenantId, viajeId);
  }

  // ===========================================================================
  // CALCULO DE ETA
  // ===========================================================================

  /**
   * Calcular ETA basado en ruta y paradas
   */
  async calculateETA(tenantId: string, id: string): Promise<ETAInfo | null> {
    const viaje = await this.findById(tenantId, id);
    if (!viaje) return null;

    const paradas = await this.getParadas(tenantId, id);

    // Encontrar parada actual (ultima completada o primera pendiente)
    let paradaActualSecuencia = 0;
    for (const parada of paradas) {
      if (parada.estadoParada === EstadoParada.COMPLETADA) {
        paradaActualSecuencia = parada.secuencia;
      }
    }

    // Siguiente parada pendiente
    const paradaSiguiente = paradas.find(
      p => p.secuencia > paradaActualSecuencia &&
           p.estadoParada !== EstadoParada.COMPLETADA &&
           p.estadoParada !== EstadoParada.OMITIDA
    );

    // Calcular progreso
    const paradasCompletadas = paradas.filter(
      p => p.estadoParada === EstadoParada.COMPLETADA || p.estadoParada === EstadoParada.OMITIDA
    ).length;
    const progresoViaje = paradas.length > 0 ? (paradasCompletadas / paradas.length) * 100 : 0;

    // Calcular distancia y tiempo restante (aproximacion simplificada)
    const distanciaTotal = viaje.distanciaEstimadaKm || 0;
    const tiempoTotalHoras = viaje.tiempoEstimadoHoras || (distanciaTotal / 60);
    const distanciaRestanteKm = distanciaTotal * (1 - progresoViaje / 100);
    const tiempoRestanteMinutos = tiempoTotalHoras * 60 * (1 - progresoViaje / 100);

    // Calcular ETA estimada
    const ahora = new Date();
    const etaFinal = new Date(ahora.getTime() + tiempoRestanteMinutos * 60 * 1000);

    const result: ETAInfo = {
      viajeId: viaje.id,
      paradaActualSecuencia,
      destinoFinal: {
        etaEstimada: etaFinal,
        distanciaRestanteKm: Math.round(distanciaRestanteKm * 10) / 10,
        tiempoRestanteMinutos: Math.round(tiempoRestanteMinutos),
      },
      progresoViaje: Math.round(progresoViaje * 10) / 10,
      ultimaActualizacion: ahora,
    };

    // Agregar info de siguiente parada si existe
    if (paradaSiguiente) {
      const distanciaASiguiente = distanciaRestanteKm / (paradas.length - paradasCompletadas);
      const tiempoASiguiente = tiempoRestanteMinutos / (paradas.length - paradasCompletadas);

      result.paradaSiguiente = {
        id: paradaSiguiente.id,
        secuencia: paradaSiguiente.secuencia,
        direccion: paradaSiguiente.direccion,
        ciudad: paradaSiguiente.ciudad,
        etaEstimada: new Date(ahora.getTime() + tiempoASiguiente * 60 * 1000),
        distanciaRestanteKm: Math.round(distanciaASiguiente * 10) / 10,
        tiempoRestanteMinutos: Math.round(tiempoASiguiente),
      };
    }

    return result;
  }

  // ===========================================================================
  // ESTADISTICAS
  // ===========================================================================

  /**
   * Obtener estadisticas de viajes
   */
  async getStatistics(tenantId: string, dateRange: DateRange): Promise<ViajeStats> {
    const { from, to } = dateRange;

    const viajes = await this.viajeRepository.find({
      where: {
        tenantId,
        createdAt: Between(from, to),
      },
    });

    // Conteo por estado
    const porEstado: Record<EstadoViaje, number> = {
      [EstadoViaje.BORRADOR]: 0,
      [EstadoViaje.PLANEADO]: 0,
      [EstadoViaje.DESPACHADO]: 0,
      [EstadoViaje.EN_TRANSITO]: 0,
      [EstadoViaje.EN_DESTINO]: 0,
      [EstadoViaje.ENTREGADO]: 0,
      [EstadoViaje.CERRADO]: 0,
      [EstadoViaje.FACTURADO]: 0,
      [EstadoViaje.COBRADO]: 0,
      [EstadoViaje.CANCELADO]: 0,
    };

    let kmTotales = 0;
    let costoTotal = 0;
    let ingresoTotal = 0;
    let tiempoTotal = 0;
    let viajesConTiempo = 0;

    for (const viaje of viajes) {
      porEstado[viaje.estado]++;
      kmTotales += viaje.kmRecorridos || 0;
      costoTotal += Number(viaje.costoTotal) || 0;
      ingresoTotal += Number(viaje.ingresoTotal) || 0;

      // Calcular tiempo de viaje para viajes completados
      if (viaje.fechaSalidaReal && viaje.fechaLlegadaReal) {
        const tiempoViaje = viaje.fechaLlegadaReal.getTime() - viaje.fechaSalidaReal.getTime();
        tiempoTotal += tiempoViaje / (1000 * 60 * 60); // Convertir a horas
        viajesConTiempo++;
      }
    }

    const total = viajes.length;
    const margenTotal = ingresoTotal - costoTotal;

    return {
      total,
      porEstado,
      kmTotales: Math.round(kmTotales),
      costoTotal: Math.round(costoTotal * 100) / 100,
      ingresoTotal: Math.round(ingresoTotal * 100) / 100,
      margenTotal: Math.round(margenTotal * 100) / 100,
      promedioKmPorViaje: total > 0 ? Math.round(kmTotales / total) : 0,
      promedioCostoPorViaje: total > 0 ? Math.round((costoTotal / total) * 100) / 100 : 0,
      tiempoPromedioHoras: viajesConTiempo > 0 ? Math.round((tiempoTotal / viajesConTiempo) * 10) / 10 : 0,
    };
  }

  // ===========================================================================
  // CONSULTAS ESPECIALIZADAS
  // ===========================================================================

  /**
   * Obtener viajes programados para una fecha
   */
  async getScheduledForDate(tenantId: string, date: Date): Promise<Viaje[]> {
    const inicioDia = new Date(date);
    inicioDia.setHours(0, 0, 0, 0);
    const finDia = new Date(date);
    finDia.setHours(23, 59, 59, 999);

    return this.viajeRepository.find({
      where: {
        tenantId,
        fechaSalidaProgramada: Between(inicioDia, finDia),
        estado: In([EstadoViaje.BORRADOR, EstadoViaje.PLANEADO]),
      },
      order: { fechaSalidaProgramada: 'ASC' },
    });
  }

  /**
   * Verificar disponibilidad de unidad en fecha
   */
  async isUnidadAvailable(
    tenantId: string,
    unidadId: string,
    fechaInicio: Date,
    fechaFin: Date,
    excludeViajeId?: string
  ): Promise<boolean> {
    const whereCondition: FindOptionsWhere<Viaje> = {
      tenantId,
      unidadId,
      estado: Not(In([EstadoViaje.CANCELADO, EstadoViaje.CERRADO, EstadoViaje.FACTURADO, EstadoViaje.COBRADO])),
    };

    if (excludeViajeId) {
      whereCondition.id = Not(excludeViajeId);
    }

    const viajesConflicto = await this.viajeRepository.find({
      where: whereCondition,
    });

    // Verificar si hay solapamiento de fechas
    for (const viaje of viajesConflicto) {
      const viajeInicio = viaje.fechaSalidaProgramada || viaje.fechaSalidaReal;
      const viajeFin = viaje.fechaLlegadaProgramada || viaje.fechaLlegadaReal || viajeInicio;

      if (viajeInicio && viajeFin) {
        // Hay conflicto si: no (fechaFin < viajeInicio || fechaInicio > viajeFin)
        const sinConflicto = fechaFin < viajeInicio || fechaInicio > viajeFin;
        if (!sinConflicto) {
          return false;
        }
      }
    }

    return true;
  }

  /**
   * Verificar disponibilidad de operador en fecha
   */
  async isOperadorAvailable(
    tenantId: string,
    operadorId: string,
    fechaInicio: Date,
    fechaFin: Date,
    excludeViajeId?: string
  ): Promise<boolean> {
    const whereCondition: FindOptionsWhere<Viaje> = {
      tenantId,
      operadorId,
      estado: Not(In([EstadoViaje.CANCELADO, EstadoViaje.CERRADO, EstadoViaje.FACTURADO, EstadoViaje.COBRADO])),
    };

    if (excludeViajeId) {
      whereCondition.id = Not(excludeViajeId);
    }

    const viajesConflicto = await this.viajeRepository.find({
      where: whereCondition,
    });

    // Verificar si hay solapamiento de fechas
    for (const viaje of viajesConflicto) {
      const viajeInicio = viaje.fechaSalidaProgramada || viaje.fechaSalidaReal;
      const viajeFin = viaje.fechaLlegadaProgramada || viaje.fechaLlegadaReal || viajeInicio;

      if (viajeInicio && viajeFin) {
        const sinConflicto = fechaFin < viajeInicio || fechaInicio > viajeFin;
        if (!sinConflicto) {
          return false;
        }
      }
    }

    return true;
  }

  // ===========================================================================
  // HELPERS PRIVADOS
  // ===========================================================================

  private async generateCodigo(tenantId: string): Promise<string> {
    const year = new Date().getFullYear();
    const month = String(new Date().getMonth() + 1).padStart(2, '0');
    const count = await this.viajeRepository.count({ where: { tenantId } });
    return `VJ-${year}${month}-${String(count + 1).padStart(6, '0')}`;
  }

  private mapOrderField(orderBy: string): string {
    const mapping: Record<string, string> = {
      createdAt: 'createdAt',
      fechaSalida: 'fechaSalidaProgramada',
      fechaLlegada: 'fechaLlegadaProgramada',
      codigo: 'codigo',
    };
    return mapping[orderBy] || 'createdAt';
  }

  private isValidTransition(from: EstadoViaje, to: EstadoViaje): boolean {
    const transitions: Record<EstadoViaje, EstadoViaje[]> = {
      [EstadoViaje.BORRADOR]: [EstadoViaje.PLANEADO, EstadoViaje.CANCELADO],
      [EstadoViaje.PLANEADO]: [EstadoViaje.DESPACHADO, EstadoViaje.CANCELADO],
      [EstadoViaje.DESPACHADO]: [EstadoViaje.EN_TRANSITO, EstadoViaje.CANCELADO],
      [EstadoViaje.EN_TRANSITO]: [EstadoViaje.EN_DESTINO],
      [EstadoViaje.EN_DESTINO]: [EstadoViaje.ENTREGADO],
      [EstadoViaje.ENTREGADO]: [EstadoViaje.CERRADO],
      [EstadoViaje.CERRADO]: [EstadoViaje.FACTURADO],
      [EstadoViaje.FACTURADO]: [EstadoViaje.COBRADO],
      [EstadoViaje.COBRADO]: [],
      [EstadoViaje.CANCELADO]: [],
    };

    return transitions[from]?.includes(to) ?? false;
  }

  // ===========================================================================
  // ALIAS METHODS - Backward compatibility with existing controllers
  // ===========================================================================

  /** @deprecated Use findById instead */
  async findOne(tenantId: string, id: string) {
    return this.findById(tenantId, id);
  }

  /** @deprecated Use findByCodigo instead */
  async findByNumero(tenantId: string, codigo: string) {
    return this.findByCodigo(tenantId, codigo);
  }

  /** @deprecated Use specific state transition methods instead */
  async cambiarEstado(tenantId: string, id: string, nuevoEstado: EstadoViaje, updatedById: string) {
    const viaje = await this.findById(tenantId, id);
    if (!viaje) return null;
    viaje.estado = nuevoEstado;
    viaje.updatedById = updatedById;
    return this.viajeRepository.save(viaje);
  }

  /** @deprecated Use start instead */
  async despachar(
    idOrTenantId: string,
    tenantIdOrData: string | { kmInicial?: number; kmInicio?: number; sellos?: InfoSello[]; checklistObservaciones?: string },
    despachoDataOrUserId?: { kmInicial?: number; kmInicio?: number; sellos?: InfoSello[]; checklistObservaciones?: string } | string,
    startedById?: string
  ) {
    // Handle legacy signature: despachar(id, tenantId, { kmInicial, sellos }, userId)
    let tenantId: string;
    let id: string;
    let data: DespachoDto;
    let userId: string;

    if (typeof tenantIdOrData === 'string' && typeof despachoDataOrUserId === 'object') {
      // Legacy: despachar(id, tenantId, data, userId)
      id = idOrTenantId;
      tenantId = tenantIdOrData;
      const legacyData = despachoDataOrUserId as { kmInicial?: number; kmInicio?: number; sellos?: InfoSello[] };
      data = {
        kmInicio: legacyData.kmInicio || legacyData.kmInicial || 0,
        sellosSalida: legacyData.sellos,
        checklistCompletado: true,
      };
      userId = startedById || '';
    } else {
      // New: despachar(tenantId, id, data, userId)
      tenantId = idOrTenantId;
      id = tenantIdOrData as string;
      data = despachoDataOrUserId as DespachoDto;
      userId = startedById || '';
    }

    return this.start(tenantId, id, data, userId);
  }

  /** @deprecated Use startTransit instead */
  async iniciarTransito(idOrTenantId: string, tenantIdOrUserId: string, userIdOpt?: string) {
    // Handle legacy signature: iniciarTransito(id, tenantId, userId)
    if (userIdOpt) {
      return this.startTransit(tenantIdOrUserId, idOrTenantId, userIdOpt);
    }
    return this.startTransit(idOrTenantId, tenantIdOrUserId, '');
  }

  /** @deprecated Use arriveAtDestination instead */
  async registrarLlegada(
    idOrTenantId: string,
    tenantIdOrKm: string | number | { kmFinal?: number },
    kmOrUserId?: number | { kmFinal?: number } | string,
    userIdOpt?: string
  ) {
    // Handle legacy signature: registrarLlegada(id, tenantId, { kmFinal }, userId)
    let tenantId: string;
    let id: string;
    let kmActual: number;
    let userId: string;

    if (typeof tenantIdOrKm === 'string' && typeof kmOrUserId === 'object') {
      // Legacy: registrarLlegada(id, tenantId, { kmFinal }, userId)
      id = idOrTenantId;
      tenantId = tenantIdOrKm;
      kmActual = (kmOrUserId as { kmFinal?: number }).kmFinal || 0;
      userId = userIdOpt || '';
    } else {
      // New: registrarLlegada(tenantId, id, kmActual, userId)
      tenantId = idOrTenantId;
      id = tenantIdOrKm as string;
      kmActual = kmOrUserId as number;
      userId = userIdOpt || '';
    }

    return this.arriveAtDestination(tenantId, id, kmActual, userId);
  }

  /** @deprecated Use complete or close instead */
  async registrarEntrega(
    idOrTenantId: string,
    tenantIdOrCompletionData: string | { kmFin?: number; kmFinal?: number; sellosLlegada?: InfoSello[]; observaciones?: string },
    completionDataOrCompletedById?: { kmFin?: number; kmFinal?: number; sellosLlegada?: InfoSello[]; observaciones?: string } | string,
    completedByIdOpt?: string
  ) {
    // Handle legacy signature: registrarEntrega(id, tenantId, data, userId)
    let tenantId: string;
    let id: string;
    let completionData: { kmFin?: number; kmFinal?: number; sellosLlegada?: InfoSello[]; observaciones?: string };
    let completedById: string;

    if (typeof tenantIdOrCompletionData === 'string' && typeof completionDataOrCompletedById === 'object') {
      // Legacy: registrarEntrega(id, tenantId, { kmFinal, observaciones }, userId)
      id = idOrTenantId;
      tenantId = tenantIdOrCompletionData;
      completionData = completionDataOrCompletedById;
      completedById = completedByIdOpt || '';
    } else {
      // New: registrarEntrega(tenantId, id, data, userId)
      tenantId = idOrTenantId;
      id = tenantIdOrCompletionData as string;
      completionData = completionDataOrCompletedById as { kmFin?: number; kmFinal?: number; sellosLlegada?: InfoSello[] };
      completedById = completedByIdOpt || '';
    }

    return this.complete(tenantId, id, {
      kmFin: completionData.kmFin || completionData.kmFinal || 0,
      sellosLlegada: completionData.sellosLlegada,
    }, completedById);
  }

  /** @deprecated Use getActiveTrips instead */
  async getViajesEnProgreso(tenantId: string) {
    return this.getActiveTrips(tenantId);
  }

  /** @deprecated Use findByOperador instead */
  async getViajesOperador(tenantId: string, operadorId: string) {
    return this.findByOperador(tenantId, operadorId);
  }

  /** @deprecated Use findByUnidad instead */
  async getViajesUnidad(tenantId: string, unidadId: string) {
    return this.findByUnidad(tenantId, unidadId);
  }
}
