/**
 * Dispatch Service
 * ERP Transportistas
 *
 * Business logic for dispatch operations.
 * Module: MAI-005 Despacho
 * Adapted from: erp-mecanicas-diesel MMD-011 Dispatch Center
 */

import { Repository, DataSource } from 'typeorm';
import { EstadoUnidad, EstadoUnidadEnum, CapacidadUnidad } from '../entities/estado-unidad.entity';
import { LogDespacho, AccionDespacho } from '../entities/log-despacho.entity';
import { TableroDespacho } from '../entities/dispatch-board.entity';

// DTOs
export interface CreateEstadoUnidadDto {
  unidadId: string;
  codigoUnidad?: string;
  nombreUnidad?: string;
  capacidadUnidad?: CapacidadUnidad;
  puedeRemolcar?: boolean;
  pesoMaxRemolqueKg?: number;
  esRefrigerada?: boolean;
  capacidadPesoKg?: number;
}

export interface UpdateEstadoUnidadDto {
  estado?: EstadoUnidadEnum;
  viajeActualId?: string | null;
  operadorIds?: string[];
  ultimaPosicionLat?: number;
  ultimaPosicionLng?: number;
  ultimaPosicionId?: string;
  disponibleEstimadoEn?: Date;
  notas?: string;
  metadata?: Record<string, any>;
}

export interface AsignacionDto {
  unidadId: string;
  operadorIds?: string[];
  notas?: string;
}

export interface SugerenciaAsignacion {
  unidadId: string;
  codigoUnidad?: string;
  nombreUnidad?: string;
  score: number;
  distanciaKm?: number;
  operadorIds?: string[];
  razones: string[];
}

export interface FiltrosEstadoUnidad {
  estado?: EstadoUnidadEnum;
  capacidadUnidad?: CapacidadUnidad;
  puedeRemolcar?: boolean;
  tieneUbicacion?: boolean;
  esRefrigerada?: boolean;
}

export interface CreateTablerDto {
  nombre: string;
  descripcion?: string;
  defaultZoom?: number;
  centroLat?: number;
  centroLng?: number;
  intervaloRefrescoSegundos?: number;
  mostrarUnidadesOffline?: boolean;
  autoAsignarHabilitado?: boolean;
  createdBy?: string;
}

/**
 * Transport-specific parameters for assignment suggestion
 */
export interface ParametrosSugerenciaTransporte {
  viajeId: string;
  origenLat: number;
  origenLng: number;
  tipoCarga?: string;
  pesoKg?: number;
  distanciaRutaKm?: number;
  requiereFrio?: boolean;
  requiereLicenciaFederal?: boolean;
  requiereCertificadoMP?: boolean;
}

export class DispatchService {
  private estadoUnidadRepository: Repository<EstadoUnidad>;
  private logDespachoRepository: Repository<LogDespacho>;
  private tableroRepository: Repository<TableroDespacho>;

  constructor(dataSource: DataSource) {
    this.estadoUnidadRepository = dataSource.getRepository(EstadoUnidad);
    this.logDespachoRepository = dataSource.getRepository(LogDespacho);
    this.tableroRepository = dataSource.getRepository(TableroDespacho);
  }

  // ==========================================
  // Tablero Despacho Management
  // ==========================================

  async crearTablero(tenantId: string, dto: CreateTablerDto): Promise<TableroDespacho> {
    const tablero = this.tableroRepository.create({
      tenantId,
      nombre: dto.nombre,
      descripcion: dto.descripcion,
      defaultZoom: dto.defaultZoom,
      centroLat: dto.centroLat,
      centroLng: dto.centroLng,
      intervaloRefrescoSegundos: dto.intervaloRefrescoSegundos,
      mostrarUnidadesOffline: dto.mostrarUnidadesOffline,
      autoAsignarHabilitado: dto.autoAsignarHabilitado,
      createdBy: dto.createdBy,
    });
    return this.tableroRepository.save(tablero);
  }

  async getTablero(tenantId: string, id: string): Promise<TableroDespacho | null> {
    return this.tableroRepository.findOne({
      where: { id, tenantId },
    });
  }

  async getTableroActivo(tenantId: string): Promise<TableroDespacho | null> {
    return this.tableroRepository.findOne({
      where: { tenantId, activo: true },
      order: { createdAt: 'DESC' },
    });
  }

  // ==========================================
  // Estado Unidad Management
  // ==========================================

  async crearEstadoUnidad(tenantId: string, dto: CreateEstadoUnidadDto): Promise<EstadoUnidad> {
    const existente = await this.estadoUnidadRepository.findOne({
      where: { tenantId, unidadId: dto.unidadId },
    });

    if (existente) {
      throw new Error(`Estado de unidad para ${dto.unidadId} ya existe`);
    }

    const estadoUnidad = this.estadoUnidadRepository.create({
      tenantId,
      ...dto,
      estado: EstadoUnidadEnum.OFFLINE,
    });

    return this.estadoUnidadRepository.save(estadoUnidad);
  }

  async getEstadoUnidad(tenantId: string, unidadId: string): Promise<EstadoUnidad | null> {
    return this.estadoUnidadRepository.findOne({
      where: { tenantId, unidadId },
    });
  }

  async actualizarEstadoUnidad(
    tenantId: string,
    unidadId: string,
    dto: UpdateEstadoUnidadDto
  ): Promise<EstadoUnidad | null> {
    const estadoUnidad = await this.getEstadoUnidad(tenantId, unidadId);
    if (!estadoUnidad) return null;

    if (dto.ultimaPosicionLat !== undefined && dto.ultimaPosicionLng !== undefined) {
      estadoUnidad.ultimaActualizacionUbicacion = new Date();
    }

    Object.assign(estadoUnidad, dto);
    return this.estadoUnidadRepository.save(estadoUnidad);
  }

  async getUnidadesDisponibles(
    tenantId: string,
    filtros: FiltrosEstadoUnidad = {}
  ): Promise<EstadoUnidad[]> {
    const qb = this.estadoUnidadRepository
      .createQueryBuilder('eu')
      .where('eu.tenant_id = :tenantId', { tenantId })
      .andWhere('eu.estado = :estado', { estado: EstadoUnidadEnum.AVAILABLE });

    if (filtros.capacidadUnidad) {
      qb.andWhere('eu.capacidad_unidad = :capacidad', { capacidad: filtros.capacidadUnidad });
    }
    if (filtros.puedeRemolcar !== undefined) {
      qb.andWhere('eu.puede_remolcar = :puedeRemolcar', { puedeRemolcar: filtros.puedeRemolcar });
    }
    if (filtros.tieneUbicacion) {
      qb.andWhere('eu.ultima_posicion_lat IS NOT NULL AND eu.ultima_posicion_lng IS NOT NULL');
    }
    if (filtros.esRefrigerada !== undefined) {
      qb.andWhere('eu.es_refrigerada = :esRefrigerada', { esRefrigerada: filtros.esRefrigerada });
    }

    return qb.orderBy('eu.ultimo_cambio_estado', 'ASC').getMany();
  }

  async getTodasUnidades(
    tenantId: string,
    incluirOffline: boolean = true
  ): Promise<EstadoUnidad[]> {
    const qb = this.estadoUnidadRepository
      .createQueryBuilder('eu')
      .where('eu.tenant_id = :tenantId', { tenantId });

    if (!incluirOffline) {
      qb.andWhere('eu.estado != :offline', { offline: EstadoUnidadEnum.OFFLINE });
    }

    return qb.orderBy('eu.estado', 'ASC').addOrderBy('eu.codigo_unidad', 'ASC').getMany();
  }

  // ==========================================
  // Assignment Operations
  // ==========================================

  async asignarViaje(
    tenantId: string,
    viajeId: string,
    asignacion: AsignacionDto,
    ejecutadoPor: string
  ): Promise<EstadoUnidad | null> {
    const estadoUnidad = await this.getEstadoUnidad(tenantId, asignacion.unidadId);
    if (!estadoUnidad) {
      throw new Error(`Unidad ${asignacion.unidadId} no encontrada`);
    }

    if (estadoUnidad.estado !== EstadoUnidadEnum.AVAILABLE) {
      throw new Error(`Unidad ${asignacion.unidadId} no disponible (estado: ${estadoUnidad.estado})`);
    }

    // Update unit status
    estadoUnidad.estado = EstadoUnidadEnum.ASSIGNED;
    estadoUnidad.viajeActualId = viajeId;
    estadoUnidad.operadorIds = asignacion.operadorIds || [];
    estadoUnidad.notas = asignacion.notas;
    await this.estadoUnidadRepository.save(estadoUnidad);

    // Log the assignment
    await this.registrarAccion(tenantId, {
      viajeId,
      accion: AccionDespacho.ASSIGNED,
      haciaUnidadId: asignacion.unidadId,
      haciaOperadorId: asignacion.operadorIds?.[0],
      ejecutadoPor,
    });

    return estadoUnidad;
  }

  async reasignarViaje(
    tenantId: string,
    viajeId: string,
    nuevaAsignacion: AsignacionDto,
    razon: string,
    ejecutadoPor: string
  ): Promise<EstadoUnidad | null> {
    // Find current assignment
    const unidadActual = await this.estadoUnidadRepository.findOne({
      where: { tenantId, viajeActualId: viajeId },
    });

    // Release current unit
    if (unidadActual) {
      unidadActual.estado = EstadoUnidadEnum.AVAILABLE;
      unidadActual.viajeActualId = undefined;
      unidadActual.operadorIds = [];
      await this.estadoUnidadRepository.save(unidadActual);
    }

    // Assign new unit
    const nuevaUnidad = await this.getEstadoUnidad(tenantId, nuevaAsignacion.unidadId);
    if (!nuevaUnidad) {
      throw new Error(`Unidad ${nuevaAsignacion.unidadId} no encontrada`);
    }

    if (nuevaUnidad.estado !== EstadoUnidadEnum.AVAILABLE) {
      throw new Error(`Unidad ${nuevaAsignacion.unidadId} no disponible`);
    }

    nuevaUnidad.estado = EstadoUnidadEnum.ASSIGNED;
    nuevaUnidad.viajeActualId = viajeId;
    nuevaUnidad.operadorIds = nuevaAsignacion.operadorIds || [];
    await this.estadoUnidadRepository.save(nuevaUnidad);

    // Log the reassignment
    await this.registrarAccion(tenantId, {
      viajeId,
      accion: AccionDespacho.REASSIGNED,
      desdeUnidadId: unidadActual?.unidadId,
      haciaUnidadId: nuevaAsignacion.unidadId,
      desdeOperadorId: unidadActual?.operadorIds?.[0],
      haciaOperadorId: nuevaAsignacion.operadorIds?.[0],
      razon,
      ejecutadoPor,
    });

    return nuevaUnidad;
  }

  async marcarEnRuta(
    tenantId: string,
    unidadId: string,
    ejecutadoPor: string
  ): Promise<EstadoUnidad | null> {
    const estadoUnidad = await this.getEstadoUnidad(tenantId, unidadId);
    if (!estadoUnidad || !estadoUnidad.viajeActualId) return null;

    estadoUnidad.estado = EstadoUnidadEnum.EN_ROUTE;
    await this.estadoUnidadRepository.save(estadoUnidad);

    await this.registrarAccion(tenantId, {
      viajeId: estadoUnidad.viajeActualId,
      accion: AccionDespacho.ACKNOWLEDGED,
      haciaUnidadId: unidadId,
      ejecutadoPor,
    });

    return estadoUnidad;
  }

  async marcarEnSitio(
    tenantId: string,
    unidadId: string,
    ejecutadoPor: string
  ): Promise<EstadoUnidad | null> {
    const estadoUnidad = await this.getEstadoUnidad(tenantId, unidadId);
    if (!estadoUnidad || !estadoUnidad.viajeActualId) return null;

    estadoUnidad.estado = EstadoUnidadEnum.ON_SITE;
    await this.estadoUnidadRepository.save(estadoUnidad);

    return estadoUnidad;
  }

  async completarViaje(
    tenantId: string,
    unidadId: string,
    ejecutadoPor: string
  ): Promise<EstadoUnidad | null> {
    const estadoUnidad = await this.getEstadoUnidad(tenantId, unidadId);
    if (!estadoUnidad || !estadoUnidad.viajeActualId) return null;

    const viajeId = estadoUnidad.viajeActualId;

    estadoUnidad.estado = EstadoUnidadEnum.RETURNING;
    await this.estadoUnidadRepository.save(estadoUnidad);

    await this.registrarAccion(tenantId, {
      viajeId,
      accion: AccionDespacho.COMPLETED,
      haciaUnidadId: unidadId,
      ejecutadoPor,
    });

    return estadoUnidad;
  }

  async liberarUnidad(
    tenantId: string,
    unidadId: string
  ): Promise<EstadoUnidad | null> {
    const estadoUnidad = await this.getEstadoUnidad(tenantId, unidadId);
    if (!estadoUnidad) return null;

    estadoUnidad.estado = EstadoUnidadEnum.AVAILABLE;
    estadoUnidad.viajeActualId = undefined;
    estadoUnidad.operadorIds = [];
    estadoUnidad.disponibleEstimadoEn = undefined;

    return this.estadoUnidadRepository.save(estadoUnidad);
  }

  // ==========================================
  // Assignment Suggestions - Transport-Specific
  // ==========================================

  async sugerirMejorAsignacion(
    tenantId: string,
    params: ParametrosSugerenciaTransporte
  ): Promise<SugerenciaAsignacion[]> {
    const filtros: FiltrosEstadoUnidad = {
      tieneUbicacion: true,
    };

    // Filter by refrigeration if required
    if (params.requiereFrio) {
      filtros.esRefrigerada = true;
    }

    const unidadesDisponibles = await this.getUnidadesDisponibles(tenantId, filtros);

    const sugerencias: SugerenciaAsignacion[] = [];

    for (const unidad of unidadesDisponibles) {
      if (!unidad.ultimaPosicionLat || !unidad.ultimaPosicionLng) continue;

      const distancia = this.calcularDistancia(
        params.origenLat,
        params.origenLng,
        Number(unidad.ultimaPosicionLat),
        Number(unidad.ultimaPosicionLng)
      );

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

      // Distance evaluation
      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;
      }

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

      // Refrigeration check
      if (params.requiereFrio) {
        if (!unidad.esRefrigerada) {
          // Already filtered above, but double-check
          continue;
        }
        razones.push('Unidad refrigerada');
        score += 10;
      }

      // TODO: Add HOS check when operator data is available
      // const horasDisponibles = calcularHorasDisponibles(operador);
      // if (params.distanciaRutaKm) {
      //   const horasRuta = params.distanciaRutaKm / 60;
      //   if (horasDisponibles < horasRuta) score -= 30;
      // }

      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,
      });
    }

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

  // ==========================================
  // Dispatch Log
  // ==========================================

  async registrarAccion(
    tenantId: string,
    data: {
      viajeId: string;
      accion: AccionDespacho;
      desdeUnidadId?: string;
      haciaUnidadId?: string;
      desdeOperadorId?: string;
      haciaOperadorId?: string;
      razon?: string;
      automatizado?: boolean;
      reglaId?: string;
      escalamientoId?: string;
      tiempoRespuestaSegundos?: number;
      ejecutadoPor?: string;
      metadata?: Record<string, any>;
    }
  ): Promise<LogDespacho> {
    const log = this.logDespachoRepository.create({
      tenantId,
      ...data,
      ejecutadoEn: new Date(),
    });
    return this.logDespachoRepository.save(log);
  }

  async getLogsViaje(
    tenantId: string,
    viajeId: string
  ): Promise<LogDespacho[]> {
    return this.logDespachoRepository.find({
      where: { tenantId, viajeId },
      order: { ejecutadoEn: 'DESC' },
    });
  }

  async getLogsRecientes(
    tenantId: string,
    limite: number = 50
  ): Promise<LogDespacho[]> {
    return this.logDespachoRepository.find({
      where: { tenantId },
      order: { ejecutadoEn: 'DESC' },
      take: limite,
    });
  }

  // ==========================================
  // Statistics
  // ==========================================

  async getEstadisticasDespacho(tenantId: string): Promise<{
    totalUnidades: number;
    disponibles: number;
    asignadas: number;
    enRuta: number;
    enSitio: number;
    offline: number;
    mantenimiento: number;
  }> {
    const unidades = await this.estadoUnidadRepository.find({ where: { tenantId } });

    const stats = {
      totalUnidades: unidades.length,
      disponibles: 0,
      asignadas: 0,
      enRuta: 0,
      enSitio: 0,
      offline: 0,
      mantenimiento: 0,
    };

    for (const unidad of unidades) {
      switch (unidad.estado) {
        case EstadoUnidadEnum.AVAILABLE:
          stats.disponibles++;
          break;
        case EstadoUnidadEnum.ASSIGNED:
          stats.asignadas++;
          break;
        case EstadoUnidadEnum.EN_ROUTE:
          stats.enRuta++;
          break;
        case EstadoUnidadEnum.ON_SITE:
          stats.enSitio++;
          break;
        case EstadoUnidadEnum.OFFLINE:
          stats.offline++;
          break;
        case EstadoUnidadEnum.MAINTENANCE:
          stats.mantenimiento++;
          break;
      }
    }

    return stats;
  }

  // ==========================================
  // Helpers
  // ==========================================

  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);
  }
}
