import { Repository, FindOptionsWhere, ILike, In, LessThanOrEqual, MoreThanOrEqual, IsNull, Or } from 'typeorm';
import { Tarifa, TipoTarifa, Lane, RecargoCatalogo, FuelSurcharge, TipoRecargo } from '../entities';

export interface TarifaSearchParams {
  tenantId: string;
  search?: string;
  clienteId?: string;
  laneId?: string;
  tipoTarifa?: TipoTarifa;
  activa?: boolean;
  vigente?: boolean;
  moneda?: string;
  limit?: number;
  offset?: number;
}

export interface CreateTarifaDto {
  codigo: string;
  nombre: string;
  descripcion?: string;
  clienteId?: string;
  laneId?: string;
  modalidadServicio?: string;
  tipoEquipo?: string;
  tipoTarifa: TipoTarifa;
  tarifaBase: number;
  tarifaKm?: number;
  tarifaTonelada?: number;
  tarifaM3?: number;
  tarifaPallet?: number;
  tarifaHora?: number;
  minimoFacturar?: number;
  pesoMinimoKg?: number;
  moneda?: string;
  fechaInicio: Date;
  fechaFin?: Date;
}

export interface UpdateTarifaDto extends Partial<CreateTarifaDto> {
  activa?: boolean;
}

export interface CotizarParams {
  km?: number;
  toneladas?: number;
  m3?: number;
  pallets?: number;
  horas?: number;
}

export interface CotizacionResult {
  tarifa: Tarifa;
  montoCalculado: number;
  desglose: {
    tarifaBase: number;
    montoVariable: number;
    minimoAplicado: boolean;
  };
}

/**
 * Service Context - contains tenant and user info
 */
export interface ServiceContext {
  tenantId: string;
  userId: string;
}

/**
 * Parameters for finding tarifa by lane
 */
export interface FindTarifaByLaneParams {
  origenCiudad: string;
  destinoCiudad: string;
  tipoCarga: string;
  tipoEquipo?: string;
}

/**
 * Parameters for calculating shipping cost
 */
export interface CalcularCostoEnvioParams {
  origenCiudad: string;
  destinoCiudad: string;
  tipoCarga: string;
  tipoEquipo: string;
  pesoKg?: number;
  volumenM3?: number;
  valorDeclarado?: number;
}

/**
 * Recargo aplicado in desglose
 */
export interface RecargoAplicado {
  tipo: TipoRecargo;
  nombre: string;
  esPorcentaje: boolean;
  valorBase: number;
  montoCalculado: number;
}

/**
 * Result of cost calculation
 */
export interface CostoEnvioResult {
  tarifaAplicada: Tarifa | null;
  laneEncontrada: Lane | null;
  tarifaBase: number;
  costoCalculadoPorUnidad: number;
  unidadCalculo: string;
  subtotalFlete: number;
  recargosAplicados: RecargoAplicado[];
  totalRecargos: number;
  subtotal: number;
  iva: number;
  total: number;
  moneda: string;
  desglose: {
    pesoKg: number | null;
    volumenM3: number | null;
    pesoVolumen: number | null;
    valorDeclarado: number | null;
    factorCobroUsado: 'peso' | 'volumen';
  };
}

export class TarifasService {
  constructor(
    private readonly tarifaRepository: Repository<Tarifa>,
    private readonly laneRepository: Repository<Lane>,
    private readonly recargoRepository: Repository<RecargoCatalogo>,
    private readonly fuelSurchargeRepository: Repository<FuelSurcharge>,
  ) {}

  async findAll(params: TarifaSearchParams): Promise<{ data: Tarifa[]; total: number }> {
    const {
      tenantId,
      search,
      clienteId,
      laneId,
      tipoTarifa,
      activa,
      vigente,
      moneda,
      limit = 50,
      offset = 0,
    } = params;

    const qb = this.tarifaRepository.createQueryBuilder('t');

    qb.where('t.tenant_id = :tenantId', { tenantId });

    if (clienteId) {
      qb.andWhere('t.cliente_id = :clienteId', { clienteId });
    }

    if (laneId) {
      qb.andWhere('t.lane_id = :laneId', { laneId });
    }

    if (tipoTarifa) {
      qb.andWhere('t.tipo_tarifa = :tipoTarifa', { tipoTarifa });
    }

    if (activa !== undefined) {
      qb.andWhere('t.activa = :activa', { activa });
    }

    if (vigente) {
      const hoy = new Date();
      qb.andWhere('t.activa = true');
      qb.andWhere('t.fecha_inicio <= :hoy', { hoy });
      qb.andWhere('(t.fecha_fin IS NULL OR t.fecha_fin >= :hoy)', { hoy });
    }

    if (moneda) {
      qb.andWhere('t.moneda = :moneda', { moneda });
    }

    if (search) {
      qb.andWhere(
        '(t.codigo ILIKE :search OR t.nombre ILIKE :search OR t.descripcion ILIKE :search)',
        { search: `%${search}%` }
      );
    }

    qb.leftJoinAndSelect('t.lane', 'lane');
    qb.orderBy('t.activa', 'DESC');
    qb.addOrderBy('t.fecha_inicio', 'DESC');
    qb.take(limit);
    qb.skip(offset);

    const [data, total] = await qb.getManyAndCount();

    return { data, total };
  }

  async findOne(id: string, tenantId: string): Promise<Tarifa | null> {
    return this.tarifaRepository.findOne({
      where: { id, tenantId },
      relations: ['lane'],
    });
  }

  async findByCodigo(codigo: string, tenantId: string): Promise<Tarifa | null> {
    return this.tarifaRepository.findOne({
      where: { codigo, tenantId },
      relations: ['lane'],
    });
  }

  async create(tenantId: string, dto: CreateTarifaDto, createdBy: string): Promise<Tarifa> {
    // Check if codigo already exists
    const existingCodigo = await this.findByCodigo(dto.codigo, tenantId);
    if (existingCodigo) {
      throw new Error(`Ya existe una tarifa con el codigo "${dto.codigo}"`);
    }

    // Validate lane if provided
    if (dto.laneId) {
      const lane = await this.laneRepository.findOne({
        where: { id: dto.laneId, tenantId },
      });
      if (!lane) {
        throw new Error('Lane no encontrada');
      }
    }

    const tarifa = this.tarifaRepository.create({
      ...dto,
      tenantId,
      activa: true,
      createdById: createdBy,
    });

    return this.tarifaRepository.save(tarifa);
  }

  async update(
    id: string,
    tenantId: string,
    dto: UpdateTarifaDto,
    updatedBy: string
  ): Promise<Tarifa | null> {
    const tarifa = await this.findOne(id, tenantId);
    if (!tarifa) return null;

    // Check for codigo uniqueness if changing codigo
    if (dto.codigo && dto.codigo !== tarifa.codigo) {
      const existingCodigo = await this.findByCodigo(dto.codigo, tenantId);
      if (existingCodigo && existingCodigo.id !== id) {
        throw new Error(`Ya existe una tarifa con el codigo "${dto.codigo}"`);
      }
    }

    // Validate lane if provided
    if (dto.laneId && dto.laneId !== tarifa.laneId) {
      const lane = await this.laneRepository.findOne({
        where: { id: dto.laneId, tenantId },
      });
      if (!lane) {
        throw new Error('Lane no encontrada');
      }
    }

    Object.assign(tarifa, dto);
    return this.tarifaRepository.save(tarifa);
  }

  async activar(id: string, tenantId: string): Promise<Tarifa | null> {
    const tarifa = await this.findOne(id, tenantId);
    if (!tarifa) return null;

    tarifa.activa = true;
    return this.tarifaRepository.save(tarifa);
  }

  async desactivar(id: string, tenantId: string): Promise<Tarifa | null> {
    const tarifa = await this.findOne(id, tenantId);
    if (!tarifa) return null;

    tarifa.activa = false;
    return this.tarifaRepository.save(tarifa);
  }

  // Get best tariff for a client/lane combination
  async getTarifaAplicable(
    tenantId: string,
    params: {
      clienteId?: string;
      laneId?: string;
      tipoEquipo?: string;
      modalidadServicio?: string;
    }
  ): Promise<Tarifa | null> {
    const hoy = new Date();
    const qb = this.tarifaRepository.createQueryBuilder('t');

    qb.where('t.tenant_id = :tenantId', { tenantId });
    qb.andWhere('t.activa = true');
    qb.andWhere('t.fecha_inicio <= :hoy', { hoy });
    qb.andWhere('(t.fecha_fin IS NULL OR t.fecha_fin >= :hoy)', { hoy });

    // Priority: specific client tariff > lane tariff > general tariff
    if (params.clienteId && params.laneId) {
      // First try client + lane specific
      const specific = await qb
        .clone()
        .andWhere('t.cliente_id = :clienteId', { clienteId: params.clienteId })
        .andWhere('t.lane_id = :laneId', { laneId: params.laneId })
        .getOne();
      if (specific) return specific;
    }

    if (params.clienteId) {
      // Try client specific (any lane)
      const clientTarifa = await qb
        .clone()
        .andWhere('t.cliente_id = :clienteId', { clienteId: params.clienteId })
        .andWhere('t.lane_id IS NULL')
        .getOne();
      if (clientTarifa) return clientTarifa;
    }

    if (params.laneId) {
      // Try lane specific (any client)
      const laneTarifa = await qb
        .clone()
        .andWhere('t.cliente_id IS NULL')
        .andWhere('t.lane_id = :laneId', { laneId: params.laneId })
        .getOne();
      if (laneTarifa) return laneTarifa;
    }

    // Try general tariff with matching equipment/modalidad
    if (params.tipoEquipo || params.modalidadServicio) {
      const generalQb = qb.clone()
        .andWhere('t.cliente_id IS NULL')
        .andWhere('t.lane_id IS NULL');

      if (params.tipoEquipo) {
        generalQb.andWhere('t.tipo_equipo = :tipoEquipo', { tipoEquipo: params.tipoEquipo });
      }
      if (params.modalidadServicio) {
        generalQb.andWhere('t.modalidad_servicio = :modalidadServicio', {
          modalidadServicio: params.modalidadServicio,
        });
      }

      const generalTarifa = await generalQb.getOne();
      if (generalTarifa) return generalTarifa;
    }

    // Fallback to any general tariff
    return qb
      .clone()
      .andWhere('t.cliente_id IS NULL')
      .andWhere('t.lane_id IS NULL')
      .orderBy('t.created_at', 'DESC')
      .getOne();
  }

  // Calculate quote using a tariff
  async cotizar(tarifaId: string, tenantId: string, params: CotizarParams): Promise<CotizacionResult> {
    const tarifa = await this.findOne(tarifaId, tenantId);
    if (!tarifa) {
      throw new Error('Tarifa no encontrada');
    }

    if (!tarifa.vigente) {
      throw new Error('La tarifa no esta vigente');
    }

    let montoVariable = 0;

    switch (tarifa.tipoTarifa) {
      case TipoTarifa.POR_KM:
        montoVariable = (params.km || 0) * (tarifa.tarifaKm || 0);
        break;
      case TipoTarifa.POR_TONELADA:
        montoVariable = (params.toneladas || 0) * (tarifa.tarifaTonelada || 0);
        break;
      case TipoTarifa.POR_M3:
        montoVariable = (params.m3 || 0) * (tarifa.tarifaM3 || 0);
        break;
      case TipoTarifa.POR_PALLET:
        montoVariable = (params.pallets || 0) * (tarifa.tarifaPallet || 0);
        break;
      case TipoTarifa.POR_HORA:
        montoVariable = (params.horas || 0) * (tarifa.tarifaHora || 0);
        break;
      case TipoTarifa.MIXTA:
        montoVariable += (params.km || 0) * (tarifa.tarifaKm || 0);
        montoVariable += (params.toneladas || 0) * (tarifa.tarifaTonelada || 0);
        break;
      case TipoTarifa.POR_VIAJE:
      default:
        montoVariable = 0;
        break;
    }

    let montoCalculado = Number(tarifa.tarifaBase) + montoVariable;
    let minimoAplicado = false;

    if (tarifa.minimoFacturar && montoCalculado < Number(tarifa.minimoFacturar)) {
      montoCalculado = Number(tarifa.minimoFacturar);
      minimoAplicado = true;
    }

    return {
      tarifa,
      montoCalculado,
      desglose: {
        tarifaBase: Number(tarifa.tarifaBase),
        montoVariable,
        minimoAplicado,
      },
    };
  }

  // Get tarifas by cliente
  async getTarifasCliente(clienteId: string, tenantId: string): Promise<Tarifa[]> {
    return this.tarifaRepository.find({
      where: { clienteId, tenantId, activa: true },
      relations: ['lane'],
      order: { fechaInicio: 'DESC' },
    });
  }

  // Get tarifas by lane
  async getTarifasLane(laneId: string, tenantId: string): Promise<Tarifa[]> {
    return this.tarifaRepository.find({
      where: { laneId, tenantId, activa: true },
      relations: ['lane'],
      order: { fechaInicio: 'DESC' },
    });
  }

  // Get tarifas expiring soon
  async getTarifasPorVencer(tenantId: string, diasAntelacion: number = 30): Promise<Tarifa[]> {
    const hoy = new Date();
    const fechaLimite = new Date();
    fechaLimite.setDate(fechaLimite.getDate() + diasAntelacion);

    return this.tarifaRepository
      .createQueryBuilder('t')
      .where('t.tenant_id = :tenantId', { tenantId })
      .andWhere('t.activa = true')
      .andWhere('t.fecha_fin IS NOT NULL')
      .andWhere('t.fecha_fin > :hoy', { hoy })
      .andWhere('t.fecha_fin <= :fechaLimite', { fechaLimite })
      .leftJoinAndSelect('t.lane', 'lane')
      .orderBy('t.fecha_fin', 'ASC')
      .getMany();
  }

  // Clone a tariff with new validity period
  async clonarTarifa(
    id: string,
    tenantId: string,
    nuevoCodigo: string,
    nuevaFechaInicio: Date,
    nuevaFechaFin?: Date,
    createdBy?: string
  ): Promise<Tarifa | null> {
    const sourceTarifa = await this.findOne(id, tenantId);
    if (!sourceTarifa) return null;

    // Check if new codigo exists
    const existingCodigo = await this.findByCodigo(nuevoCodigo, tenantId);
    if (existingCodigo) {
      throw new Error(`Ya existe una tarifa con el codigo "${nuevoCodigo}"`);
    }

    const newTarifa = this.tarifaRepository.create({
      tenantId,
      codigo: nuevoCodigo,
      nombre: sourceTarifa.nombre,
      descripcion: sourceTarifa.descripcion,
      clienteId: sourceTarifa.clienteId,
      laneId: sourceTarifa.laneId,
      modalidadServicio: sourceTarifa.modalidadServicio,
      tipoEquipo: sourceTarifa.tipoEquipo,
      tipoTarifa: sourceTarifa.tipoTarifa,
      tarifaBase: sourceTarifa.tarifaBase,
      tarifaKm: sourceTarifa.tarifaKm,
      tarifaTonelada: sourceTarifa.tarifaTonelada,
      tarifaM3: sourceTarifa.tarifaM3,
      tarifaPallet: sourceTarifa.tarifaPallet,
      tarifaHora: sourceTarifa.tarifaHora,
      minimoFacturar: sourceTarifa.minimoFacturar,
      pesoMinimoKg: sourceTarifa.pesoMinimoKg,
      moneda: sourceTarifa.moneda,
      fechaInicio: nuevaFechaInicio,
      fechaFin: nuevaFechaFin ?? null,
      activa: true,
      createdById: createdBy ?? sourceTarifa.createdById,
    });

    return this.tarifaRepository.save(newTarifa);
  }

  async delete(id: string, tenantId: string): Promise<boolean> {
    const tarifa = await this.findOne(id, tenantId);
    if (!tarifa) return false;

    const result = await this.tarifaRepository.delete(id);
    return (result.affected ?? 0) > 0;
  }

  // ============================================
  // MAI-002: METODOS ADICIONALES DE TARIFAS
  // ============================================

  /**
   * Find tarifa by lane (origin-destination combination)
   * Lane = origen_ciudad + destino_ciudad + tipo_carga + tipo_equipo
   * Falls back to generic tarifa if exact match not found
   */
  async findTarifaByLane(
    params: FindTarifaByLaneParams,
    ctx: ServiceContext
  ): Promise<Tarifa | null> {
    const { origenCiudad, destinoCiudad, tipoCarga, tipoEquipo } = params;
    const hoy = new Date();

    // First, find the lane by origin-destination
    const lane = await this.laneRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        origenCiudad: ILike(origenCiudad),
        destinoCiudad: ILike(destinoCiudad),
        activo: true,
      },
    });

    // Build base query for vigente tarifas
    const buildBaseQuery = () => {
      return this.tarifaRepository
        .createQueryBuilder('t')
        .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('t.activa = true')
        .andWhere('t.fecha_inicio <= :hoy', { hoy })
        .andWhere('(t.fecha_fin IS NULL OR t.fecha_fin >= :hoy)', { hoy });
    };

    // Try 1: Exact match - lane + tipo_carga + tipo_equipo
    if (lane && tipoEquipo) {
      const exactMatch = await buildBaseQuery()
        .andWhere('t.lane_id = :laneId', { laneId: lane.id })
        .andWhere('LOWER(t.modalidad_servicio) = LOWER(:tipoCarga)', { tipoCarga })
        .andWhere('LOWER(t.tipo_equipo) = LOWER(:tipoEquipo)', { tipoEquipo })
        .leftJoinAndSelect('t.lane', 'lane')
        .getOne();
      if (exactMatch) return exactMatch;
    }

    // Try 2: Lane + tipo_carga (without tipo_equipo)
    if (lane) {
      const laneAndCarga = await buildBaseQuery()
        .andWhere('t.lane_id = :laneId', { laneId: lane.id })
        .andWhere('LOWER(t.modalidad_servicio) = LOWER(:tipoCarga)', { tipoCarga })
        .andWhere('t.tipo_equipo IS NULL')
        .leftJoinAndSelect('t.lane', 'lane')
        .getOne();
      if (laneAndCarga) return laneAndCarga;
    }

    // Try 3: Lane only (any tipo_carga)
    if (lane) {
      const laneOnly = await buildBaseQuery()
        .andWhere('t.lane_id = :laneId', { laneId: lane.id })
        .leftJoinAndSelect('t.lane', 'lane')
        .orderBy('t.fecha_inicio', 'DESC')
        .getOne();
      if (laneOnly) return laneOnly;
    }

    // Try 4: Generic tarifa with tipo_carga + tipo_equipo (no lane)
    if (tipoEquipo) {
      const genericWithEquipo = await buildBaseQuery()
        .andWhere('t.lane_id IS NULL')
        .andWhere('LOWER(t.modalidad_servicio) = LOWER(:tipoCarga)', { tipoCarga })
        .andWhere('LOWER(t.tipo_equipo) = LOWER(:tipoEquipo)', { tipoEquipo })
        .getOne();
      if (genericWithEquipo) return genericWithEquipo;
    }

    // Try 5: Generic tarifa with tipo_carga only (no lane, no equipo)
    const genericCarga = await buildBaseQuery()
      .andWhere('t.lane_id IS NULL')
      .andWhere('LOWER(t.modalidad_servicio) = LOWER(:tipoCarga)', { tipoCarga })
      .andWhere('t.tipo_equipo IS NULL')
      .getOne();
    if (genericCarga) return genericCarga;

    // Try 6: Any generic active tarifa as last resort
    const anyGeneric = await buildBaseQuery()
      .andWhere('t.lane_id IS NULL')
      .andWhere('t.cliente_id IS NULL')
      .orderBy('t.fecha_inicio', 'DESC')
      .getOne();

    return anyGeneric;
  }

  /**
   * Calculate full shipping cost including surcharges
   */
  async calcularCostoEnvio(
    params: CalcularCostoEnvioParams,
    ctx: ServiceContext
  ): Promise<CostoEnvioResult> {
    const {
      origenCiudad,
      destinoCiudad,
      tipoCarga,
      tipoEquipo,
      pesoKg,
      volumenM3,
      valorDeclarado,
    } = params;

    // Get the applicable tarifa
    const tarifa = await this.findTarifaByLane(
      { origenCiudad, destinoCiudad, tipoCarga, tipoEquipo },
      ctx
    );

    // Find the lane for reference
    const lane = await this.laneRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        origenCiudad: ILike(origenCiudad),
        destinoCiudad: ILike(destinoCiudad),
        activo: true,
      },
    });

    // Calculate peso volumetrico (industria: 1m3 = 250kg)
    const FACTOR_VOLUMETRICO = 250;
    const pesoVolumetrico = volumenM3 ? volumenM3 * FACTOR_VOLUMETRICO : null;
    const pesoReal = pesoKg ?? 0;
    const pesoCobrar = Math.max(pesoReal, pesoVolumetrico ?? 0);
    const factorUsado: 'peso' | 'volumen' =
      pesoVolumetrico && pesoVolumetrico > pesoReal ? 'volumen' : 'peso';

    // Initialize result
    const result: CostoEnvioResult = {
      tarifaAplicada: tarifa,
      laneEncontrada: lane,
      tarifaBase: 0,
      costoCalculadoPorUnidad: 0,
      unidadCalculo: 'viaje',
      subtotalFlete: 0,
      recargosAplicados: [],
      totalRecargos: 0,
      subtotal: 0,
      iva: 0,
      total: 0,
      moneda: tarifa?.moneda ?? 'MXN',
      desglose: {
        pesoKg: pesoKg ?? null,
        volumenM3: volumenM3 ?? null,
        pesoVolumen: pesoVolumetrico,
        valorDeclarado: valorDeclarado ?? null,
        factorCobroUsado: factorUsado,
      },
    };

    if (!tarifa) {
      return result;
    }

    // Calculate base fare
    result.tarifaBase = Number(tarifa.tarifaBase);
    let subtotalFlete = result.tarifaBase;

    // Add variable costs based on tarifa type
    switch (tarifa.tipoTarifa) {
      case TipoTarifa.POR_KM:
        if (lane?.distanciaKm) {
          result.costoCalculadoPorUnidad = Number(tarifa.tarifaKm ?? 0);
          subtotalFlete += Number(lane.distanciaKm) * result.costoCalculadoPorUnidad;
          result.unidadCalculo = 'km';
        }
        break;
      case TipoTarifa.POR_TONELADA:
        result.costoCalculadoPorUnidad = Number(tarifa.tarifaTonelada ?? 0);
        subtotalFlete += (pesoCobrar / 1000) * result.costoCalculadoPorUnidad;
        result.unidadCalculo = 'tonelada';
        break;
      case TipoTarifa.POR_M3:
        result.costoCalculadoPorUnidad = Number(tarifa.tarifaM3 ?? 0);
        subtotalFlete += (volumenM3 ?? 0) * result.costoCalculadoPorUnidad;
        result.unidadCalculo = 'm3';
        break;
      case TipoTarifa.MIXTA:
        // km + peso
        if (lane?.distanciaKm && tarifa.tarifaKm) {
          subtotalFlete += Number(lane.distanciaKm) * Number(tarifa.tarifaKm);
        }
        if (tarifa.tarifaTonelada) {
          subtotalFlete += (pesoCobrar / 1000) * Number(tarifa.tarifaTonelada);
        }
        result.unidadCalculo = 'mixta';
        break;
      case TipoTarifa.POR_VIAJE:
      default:
        result.unidadCalculo = 'viaje';
        break;
    }

    // Apply minimum if configured
    if (tarifa.minimoFacturar && subtotalFlete < Number(tarifa.minimoFacturar)) {
      subtotalFlete = Number(tarifa.minimoFacturar);
    }

    result.subtotalFlete = subtotalFlete;

    // Get and apply surcharges
    const recargosVigentes = await this.getRecargosVigentes(ctx);
    let totalRecargos = 0;

    for (const recargo of recargosVigentes) {
      let montoRecargo = 0;

      if (recargo.tipo === TipoRecargo.FUEL_SURCHARGE) {
        // Get current fuel surcharge percentage
        const fuelSurcharge = await this.getFuelSurchargeVigente(ctx.tenantId);
        if (fuelSurcharge) {
          montoRecargo = subtotalFlete * (Number(fuelSurcharge.porcentajeSurcharge) / 100);
          result.recargosAplicados.push({
            tipo: recargo.tipo,
            nombre: `Fuel Surcharge (${fuelSurcharge.porcentajeSurcharge}%)`,
            esPorcentaje: true,
            valorBase: Number(fuelSurcharge.porcentajeSurcharge),
            montoCalculado: montoRecargo,
          });
          totalRecargos += montoRecargo;
        }
      } else if (recargo.tipo === TipoRecargo.SEGURO_ADICIONAL && valorDeclarado) {
        // Insurance on declared value
        if (recargo.esPorcentaje) {
          montoRecargo = valorDeclarado * (Number(recargo.monto) / 100);
        } else {
          montoRecargo = Number(recargo.monto);
        }
        result.recargosAplicados.push({
          tipo: recargo.tipo,
          nombre: recargo.nombre,
          esPorcentaje: recargo.esPorcentaje,
          valorBase: Number(recargo.monto),
          montoCalculado: montoRecargo,
        });
        totalRecargos += montoRecargo;
      } else if (recargo.aplicaAutomatico) {
        // Other automatic surcharges
        if (recargo.esPorcentaje) {
          montoRecargo = subtotalFlete * (Number(recargo.monto) / 100);
        } else {
          montoRecargo = Number(recargo.monto);
        }
        result.recargosAplicados.push({
          tipo: recargo.tipo,
          nombre: recargo.nombre,
          esPorcentaje: recargo.esPorcentaje,
          valorBase: Number(recargo.monto),
          montoCalculado: montoRecargo,
        });
        totalRecargos += montoRecargo;
      }
    }

    result.totalRecargos = totalRecargos;
    result.subtotal = subtotalFlete + totalRecargos;

    // Calculate IVA (16% in Mexico)
    const IVA_RATE = 0.16;
    result.iva = result.subtotal * IVA_RATE;
    result.total = result.subtotal + result.iva;

    return result;
  }

  /**
   * Get all active surcharges
   */
  async getRecargosVigentes(ctx: ServiceContext): Promise<RecargoCatalogo[]> {
    return this.recargoRepository.find({
      where: {
        tenantId: ctx.tenantId,
        activo: true,
      },
      order: { tipo: 'ASC', codigo: 'ASC' },
    });
  }

  /**
   * Get current fuel surcharge
   */
  async getFuelSurchargeVigente(tenantId: string): Promise<FuelSurcharge | null> {
    const hoy = new Date();
    return this.fuelSurchargeRepository.findOne({
      where: {
        tenantId,
        activo: true,
        fechaInicio: LessThanOrEqual(hoy),
        fechaFin: MoreThanOrEqual(hoy),
      },
      order: { fechaInicio: 'DESC' },
    });
  }

  /**
   * Check for duplicate lane tarifa
   * Lane = origen_ciudad + destino_ciudad + tipo_carga + tipo_equipo
   */
  async existsTarifaDuplicada(
    params: {
      laneId: string;
      tipoCarga?: string;
      tipoEquipo?: string;
      clienteId?: string;
      excludeId?: string;
    },
    ctx: ServiceContext
  ): Promise<boolean> {
    const qb = this.tarifaRepository
      .createQueryBuilder('t')
      .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('t.lane_id = :laneId', { laneId: params.laneId })
      .andWhere('t.activa = true');

    if (params.tipoCarga) {
      qb.andWhere('LOWER(t.modalidad_servicio) = LOWER(:tipoCarga)', {
        tipoCarga: params.tipoCarga,
      });
    } else {
      qb.andWhere('t.modalidad_servicio IS NULL');
    }

    if (params.tipoEquipo) {
      qb.andWhere('LOWER(t.tipo_equipo) = LOWER(:tipoEquipo)', {
        tipoEquipo: params.tipoEquipo,
      });
    } else {
      qb.andWhere('t.tipo_equipo IS NULL');
    }

    if (params.clienteId) {
      qb.andWhere('t.cliente_id = :clienteId', { clienteId: params.clienteId });
    } else {
      qb.andWhere('t.cliente_id IS NULL');
    }

    if (params.excludeId) {
      qb.andWhere('t.id != :excludeId', { excludeId: params.excludeId });
    }

    const count = await qb.getCount();
    return count > 0;
  }

  /**
   * Create tarifa with duplicate validation
   */
  async createWithValidation(
    dto: CreateTarifaDto,
    ctx: ServiceContext
  ): Promise<Tarifa> {
    // Check for codigo duplicate
    const existingCodigo = await this.findByCodigo(dto.codigo, ctx.tenantId);
    if (existingCodigo) {
      throw new Error(`Ya existe una tarifa con el codigo "${dto.codigo}"`);
    }

    // Check for lane duplicate if lane is specified
    if (dto.laneId) {
      const isDuplicate = await this.existsTarifaDuplicada(
        {
          laneId: dto.laneId,
          tipoCarga: dto.modalidadServicio,
          tipoEquipo: dto.tipoEquipo,
          clienteId: dto.clienteId,
        },
        ctx
      );
      if (isDuplicate) {
        throw new Error(
          'Ya existe una tarifa activa para este lane con la misma combinacion de tipo de carga y equipo'
        );
      }
    }

    // Validate lane exists
    if (dto.laneId) {
      const lane = await this.laneRepository.findOne({
        where: { id: dto.laneId, tenantId: ctx.tenantId },
      });
      if (!lane) {
        throw new Error('Lane no encontrada');
      }
    }

    const tarifa = this.tarifaRepository.create({
      ...dto,
      tenantId: ctx.tenantId,
      activa: true,
      createdById: ctx.userId,
    });

    return this.tarifaRepository.save(tarifa);
  }

  /**
   * Update tarifa with history tracking
   */
  async updateWithHistory(
    id: string,
    dto: UpdateTarifaDto,
    ctx: ServiceContext
  ): Promise<Tarifa | null> {
    const tarifa = await this.findOne(id, ctx.tenantId);
    if (!tarifa) return null;

    // Check for codigo uniqueness if changing
    if (dto.codigo && dto.codigo !== tarifa.codigo) {
      const existingCodigo = await this.findByCodigo(dto.codigo, ctx.tenantId);
      if (existingCodigo && existingCodigo.id !== id) {
        throw new Error(`Ya existe una tarifa con el codigo "${dto.codigo}"`);
      }
    }

    // Check for lane duplicate if lane changed
    if (dto.laneId && dto.laneId !== tarifa.laneId) {
      const isDuplicate = await this.existsTarifaDuplicada(
        {
          laneId: dto.laneId,
          tipoCarga: dto.modalidadServicio ?? tarifa.modalidadServicio ?? undefined,
          tipoEquipo: dto.tipoEquipo ?? tarifa.tipoEquipo ?? undefined,
          clienteId: dto.clienteId ?? tarifa.clienteId ?? undefined,
          excludeId: id,
        },
        ctx
      );
      if (isDuplicate) {
        throw new Error(
          'Ya existe una tarifa activa para este lane con la misma combinacion de tipo de carga y equipo'
        );
      }
    }

    // Validate new lane if provided
    if (dto.laneId && dto.laneId !== tarifa.laneId) {
      const lane = await this.laneRepository.findOne({
        where: { id: dto.laneId, tenantId: ctx.tenantId },
      });
      if (!lane) {
        throw new Error('Lane no encontrada');
      }
    }

    Object.assign(tarifa, dto);
    return this.tarifaRepository.save(tarifa);
  }
}
