import {
  Repository,
  FindOptionsWhere,
  ILike,
  LessThanOrEqual,
  MoreThanOrEqual,
} from 'typeorm';
import { RecargoCatalogo, TipoRecargo, FuelSurcharge, LineaFactura } from '../entities';

export interface ServiceContext {
  tenantId: string;
  userId: string;
}

export interface RecargoSearchParams {
  tenantId: string;
  search?: string;
  tipo?: TipoRecargo;
  activo?: boolean;
  aplicaAutomatico?: boolean;
  limit?: number;
  offset?: number;
}

export interface CreateRecargoDto {
  codigo: string;
  nombre: string;
  tipo: TipoRecargo;
  descripcion?: string;
  esPorcentaje?: boolean;
  monto: number;
  moneda?: string;
  aplicaAutomatico?: boolean;
  condicionAplicacion?: string;
}

export interface UpdateRecargoDto extends Partial<CreateRecargoDto> {
  activo?: boolean;
}

export interface CreateFuelSurchargeDto {
  fechaInicio: Date;
  fechaFin: Date;
  precioDieselReferencia?: number;
  precioDieselActual?: number;
  porcentajeSurcharge: number;
}

export interface UpdateFuelSurchargeDto extends Partial<CreateFuelSurchargeDto> {
  activo?: boolean;
}

// Additional calculation DTOs
export interface FuelSurchargeCalculation {
  distanciaKm: number;
  precioLitro: number;
  rendimientoKmPorLitro: number;
  litrosEstimados: number;
  costoBaseEstimado: number;
  porcentajeSurcharge: number;
  montoSurcharge: number;
  total: number;
}

export interface DetentionCalculation {
  horasEspera: number;
  tarifaBase: number;
  horasLibres: number;
  horasCobrables: number;
  tarifaPorHora: number;
  montoDetention: number;
}

export interface RecargoHistorialItem {
  id: string;
  fecha: Date;
  tipo: TipoRecargo | 'FUEL_SURCHARGE_PERIODO';
  descripcion: string;
  valor: number;
  esPorcentaje: boolean;
}

// Constants
const IVA_TASA_DEFAULT = 16; // 16% IVA Mexico
const DEFAULT_RENDIMIENTO_KM_LITRO = 3.5; // Default km/liter for diesel trucks
const DEFAULT_HORAS_LIBRES_DETENTION = 2; // Free hours before detention charges
const DEFAULT_TARIFA_HORA_DETENTION = 500; // Default MXN per hour

export class RecargosService {
  constructor(
    private readonly recargoRepository: Repository<RecargoCatalogo>,
    private readonly fuelSurchargeRepository: Repository<FuelSurcharge>,
    private readonly lineaFacturaRepository?: Repository<LineaFactura>
  ) {}

  // ============================================
  // RECARGOS CATALOGO
  // ============================================

  async findAll(params: RecargoSearchParams): Promise<{ data: RecargoCatalogo[]; total: number }> {
    const {
      tenantId,
      search,
      tipo,
      activo,
      aplicaAutomatico,
      limit = 50,
      offset = 0,
    } = params;

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

    if (activo !== undefined) {
      baseWhere.activo = activo;
    }

    if (tipo) {
      baseWhere.tipo = tipo;
    }

    if (aplicaAutomatico !== undefined) {
      baseWhere.aplicaAutomatico = aplicaAutomatico;
    }

    if (search) {
      where.push(
        { ...baseWhere, codigo: ILike(`%${search}%`) },
        { ...baseWhere, nombre: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const [data, total] = await this.recargoRepository.findAndCount({
      where,
      take: limit,
      skip: offset,
      order: { tipo: 'ASC', codigo: 'ASC' },
    });

    return { data, total };
  }

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

  async findByCodigo(codigo: string, tenantId: string): Promise<RecargoCatalogo | null> {
    return this.recargoRepository.findOne({
      where: { codigo, tenantId },
    });
  }

  async findByTipo(tipo: TipoRecargo, tenantId: string): Promise<RecargoCatalogo[]> {
    return this.recargoRepository.find({
      where: { tipo, tenantId, activo: true },
      order: { codigo: 'ASC' },
    });
  }

  async create(dto: CreateRecargoDto, ctx: ServiceContext): Promise<RecargoCatalogo> {
    // Check for duplicate codigo
    const existing = await this.findByCodigo(dto.codigo, ctx.tenantId);
    if (existing) {
      throw new Error(`Ya existe un recargo con el codigo "${dto.codigo}"`);
    }

    const recargo = this.recargoRepository.create({
      ...dto,
      tenantId: ctx.tenantId,
      activo: true,
      createdById: ctx.userId,
    });

    return this.recargoRepository.save(recargo);
  }

  async update(
    id: string,
    dto: UpdateRecargoDto,
    ctx: ServiceContext
  ): Promise<RecargoCatalogo | null> {
    const recargo = await this.findOne(id, ctx.tenantId);
    if (!recargo) return null;

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

    Object.assign(recargo, dto);
    return this.recargoRepository.save(recargo);
  }

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

    recargo.activo = true;
    return this.recargoRepository.save(recargo);
  }

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

    recargo.activo = false;
    return this.recargoRepository.save(recargo);
  }

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

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

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

  async getRecargosAutomaticos(ctx: ServiceContext): Promise<RecargoCatalogo[]> {
    return this.recargoRepository.find({
      where: {
        tenantId: ctx.tenantId,
        activo: true,
        aplicaAutomatico: true,
      },
      order: { tipo: 'ASC' },
    });
  }

  // ============================================
  // FUEL SURCHARGE
  // ============================================

  async findAllFuelSurcharges(
    tenantId: string,
    limit: number = 50,
    offset: number = 0
  ): Promise<{ data: FuelSurcharge[]; total: number }> {
    const [data, total] = await this.fuelSurchargeRepository.findAndCount({
      where: { tenantId },
      take: limit,
      skip: offset,
      order: { fechaInicio: 'DESC' },
    });

    return { data, total };
  }

  async findFuelSurcharge(id: string, tenantId: string): Promise<FuelSurcharge | null> {
    return this.fuelSurchargeRepository.findOne({
      where: { id, tenantId },
    });
  }

  async getFuelSurchargeVigente(tenantId: string): Promise<FuelSurcharge | null> {
    const hoy = new Date();
    return this.fuelSurchargeRepository
      .createQueryBuilder('fs')
      .where('fs.tenant_id = :tenantId', { tenantId })
      .andWhere('fs.activo = true')
      .andWhere('fs.fecha_inicio <= :hoy', { hoy })
      .andWhere('fs.fecha_fin >= :hoy', { hoy })
      .orderBy('fs.fecha_inicio', 'DESC')
      .getOne();
  }

  async createFuelSurcharge(
    dto: CreateFuelSurchargeDto,
    ctx: ServiceContext
  ): Promise<FuelSurcharge> {
    // Check for overlapping periods
    const overlapping = await this.fuelSurchargeRepository
      .createQueryBuilder('fs')
      .where('fs.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('fs.activo = true')
      .andWhere(
        '(fs.fecha_inicio <= :fechaFin AND fs.fecha_fin >= :fechaInicio)',
        { fechaInicio: dto.fechaInicio, fechaFin: dto.fechaFin }
      )
      .getCount();

    if (overlapping > 0) {
      throw new Error('Ya existe un fuel surcharge activo en el periodo especificado');
    }

    const fuelSurcharge = this.fuelSurchargeRepository.create({
      ...dto,
      tenantId: ctx.tenantId,
      activo: true,
      createdById: ctx.userId,
    });

    return this.fuelSurchargeRepository.save(fuelSurcharge);
  }

  async updateFuelSurcharge(
    id: string,
    dto: UpdateFuelSurchargeDto,
    ctx: ServiceContext
  ): Promise<FuelSurcharge | null> {
    const fuelSurcharge = await this.findFuelSurcharge(id, ctx.tenantId);
    if (!fuelSurcharge) return null;

    // Check for overlapping periods if dates changed
    if (dto.fechaInicio || dto.fechaFin) {
      const fechaInicio = dto.fechaInicio ?? fuelSurcharge.fechaInicio;
      const fechaFin = dto.fechaFin ?? fuelSurcharge.fechaFin;

      const overlapping = await this.fuelSurchargeRepository
        .createQueryBuilder('fs')
        .where('fs.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('fs.id != :id', { id })
        .andWhere('fs.activo = true')
        .andWhere(
          '(fs.fecha_inicio <= :fechaFin AND fs.fecha_fin >= :fechaInicio)',
          { fechaInicio, fechaFin }
        )
        .getCount();

      if (overlapping > 0) {
        throw new Error('Ya existe un fuel surcharge activo en el periodo especificado');
      }
    }

    Object.assign(fuelSurcharge, dto);
    return this.fuelSurchargeRepository.save(fuelSurcharge);
  }

  async desactivarFuelSurcharge(id: string, ctx: ServiceContext): Promise<FuelSurcharge | null> {
    const fuelSurcharge = await this.findFuelSurcharge(id, ctx.tenantId);
    if (!fuelSurcharge) return null;

    fuelSurcharge.activo = false;
    return this.fuelSurchargeRepository.save(fuelSurcharge);
  }

  async deleteFuelSurcharge(id: string, ctx: ServiceContext): Promise<boolean> {
    const fuelSurcharge = await this.findFuelSurcharge(id, ctx.tenantId);
    if (!fuelSurcharge) return false;

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

  // Calculate total surcharges for a given base amount
  async calcularRecargos(
    montoBase: number,
    ctx: ServiceContext,
    options?: {
      valorDeclarado?: number;
      incluirFuelSurcharge?: boolean;
    }
  ): Promise<{
    recargos: Array<{
      tipo: TipoRecargo;
      nombre: string;
      monto: number;
      esPorcentaje: boolean;
      montoCalculado: number;
    }>;
    totalRecargos: number;
  }> {
    const { valorDeclarado, incluirFuelSurcharge = true } = options ?? {};
    const recargos: Array<{
      tipo: TipoRecargo;
      nombre: string;
      monto: number;
      esPorcentaje: boolean;
      montoCalculado: number;
    }> = [];
    let totalRecargos = 0;

    // Get automatic surcharges
    const recargosAutomaticos = await this.getRecargosAutomaticos(ctx);

    for (const recargo of recargosAutomaticos) {
      let montoCalculado = 0;

      if (recargo.tipo === TipoRecargo.SEGURO_ADICIONAL && valorDeclarado) {
        // Insurance is calculated on declared value
        montoCalculado = recargo.esPorcentaje
          ? valorDeclarado * (Number(recargo.monto) / 100)
          : Number(recargo.monto);
      } else if (recargo.tipo !== TipoRecargo.FUEL_SURCHARGE) {
        // Other surcharges calculated on base amount
        montoCalculado = recargo.esPorcentaje
          ? montoBase * (Number(recargo.monto) / 100)
          : Number(recargo.monto);
      }

      if (montoCalculado > 0) {
        recargos.push({
          tipo: recargo.tipo,
          nombre: recargo.nombre,
          monto: Number(recargo.monto),
          esPorcentaje: recargo.esPorcentaje,
          montoCalculado,
        });
        totalRecargos += montoCalculado;
      }
    }

    // Add fuel surcharge if requested
    if (incluirFuelSurcharge) {
      const fuelSurcharge = await this.getFuelSurchargeVigente(ctx.tenantId);
      if (fuelSurcharge) {
        const montoFuel = montoBase * (Number(fuelSurcharge.porcentajeSurcharge) / 100);
        recargos.push({
          tipo: TipoRecargo.FUEL_SURCHARGE,
          nombre: `Fuel Surcharge (${fuelSurcharge.porcentajeSurcharge}%)`,
          monto: Number(fuelSurcharge.porcentajeSurcharge),
          esPorcentaje: true,
          montoCalculado: montoFuel,
        });
        totalRecargos += montoFuel;
      }
    }

    return { recargos, totalRecargos };
  }

  // ============================================
  // FUEL SURCHARGE CALCULATIONS
  // ============================================

  /**
   * Calculate fuel surcharge based on distance and current fuel price
   */
  async calculateFuelSurcharge(
    tenantId: string,
    distanciaKm: number,
    precioLitro: number,
    rendimientoKmPorLitro: number = DEFAULT_RENDIMIENTO_KM_LITRO
  ): Promise<FuelSurchargeCalculation> {
    // Get current fuel surcharge configuration
    const fuelSurcharge = await this.getFuelSurchargeVigente(tenantId);

    // Calculate estimated fuel cost
    const litrosEstimados = distanciaKm / rendimientoKmPorLitro;
    const costoBaseEstimado = litrosEstimados * precioLitro;

    // Apply surcharge percentage
    const porcentajeSurcharge = fuelSurcharge
      ? Number(fuelSurcharge.porcentajeSurcharge)
      : 0;
    const montoSurcharge = costoBaseEstimado * (porcentajeSurcharge / 100);

    return {
      distanciaKm,
      precioLitro,
      rendimientoKmPorLitro,
      litrosEstimados: Math.round(litrosEstimados * 100) / 100,
      costoBaseEstimado: Math.round(costoBaseEstimado * 100) / 100,
      porcentajeSurcharge,
      montoSurcharge: Math.round(montoSurcharge * 100) / 100,
      total: Math.round((costoBaseEstimado + montoSurcharge) * 100) / 100,
    };
  }

  /**
   * Calculate detention charge
   */
  async calculateDetention(
    tenantId: string,
    horasEspera: number,
    tarifaBase?: number
  ): Promise<DetentionCalculation> {
    // Get detention recargo configuration
    const detentionRecargo = await this.recargoRepository.findOne({
      where: {
        tenantId,
        tipo: TipoRecargo.DETENTION,
        activo: true,
      },
      order: { monto: 'DESC' },
    });

    const horasLibres = DEFAULT_HORAS_LIBRES_DETENTION;
    const tarifaPorHora = detentionRecargo
      ? Number(detentionRecargo.monto)
      : DEFAULT_TARIFA_HORA_DETENTION;

    // Calculate chargeable hours
    const horasCobrables = Math.max(0, horasEspera - horasLibres);

    // Calculate detention amount
    let montoDetention = 0;
    if (detentionRecargo?.esPorcentaje && tarifaBase) {
      // If percentage-based, apply to tarifa base
      montoDetention = tarifaBase * (tarifaPorHora / 100) * horasCobrables;
    } else {
      // Fixed rate per hour
      montoDetention = tarifaPorHora * horasCobrables;
    }

    return {
      horasEspera,
      tarifaBase: tarifaBase ?? 0,
      horasLibres,
      horasCobrables,
      tarifaPorHora,
      montoDetention: Math.round(montoDetention * 100) / 100,
    };
  }

  // ============================================
  // APPLY TO FACTURA
  // ============================================

  /**
   * Apply surcharge to invoice line
   */
  async applyToFactura(
    tenantId: string,
    facturaId: string,
    recargoId: string,
    monto: number,
    descripcion?: string
  ): Promise<LineaFactura | null> {
    if (!this.lineaFacturaRepository) {
      throw new Error('LineaFactura repository not provided');
    }

    const recargo = await this.findOne(recargoId, tenantId);
    if (!recargo) {
      throw new Error('Recargo no encontrado');
    }

    // Get next line number
    const maxLinea = await this.lineaFacturaRepository
      .createQueryBuilder('l')
      .select('MAX(l.linea)', 'max')
      .where('l.factura_id = :facturaId', { facturaId })
      .getRawOne();

    const lineaNumero = (maxLinea?.max ?? 0) + 1;

    // Calculate IVA
    const ivaTasa = IVA_TASA_DEFAULT;
    const ivaMonto = monto * (ivaTasa / 100);

    const linea = this.lineaFacturaRepository.create({
      tenantId,
      facturaId,
      linea: lineaNumero,
      descripcion: descripcion ?? recargo.nombre,
      claveProductoSat: '78101800', // SAT code for transport services
      unidadSat: 'E48', // Unit of service
      cantidad: 1,
      precioUnitario: monto,
      descuento: 0,
      importe: monto,
      ivaTasa,
      ivaMonto,
      recargoId: recargo.id,
    });

    return this.lineaFacturaRepository.save(linea);
  }

  // ============================================
  // HISTORIAL AND REPORTING
  // ============================================

  /**
   * Get surcharge price history
   */
  async getHistorialPrecios(
    tenantId: string,
    tipo: TipoRecargo | 'FUEL_SURCHARGE',
    fechaDesde: Date,
    fechaHasta: Date
  ): Promise<RecargoHistorialItem[]> {
    const historial: RecargoHistorialItem[] = [];

    if (tipo === 'FUEL_SURCHARGE') {
      // Get fuel surcharge periods in date range
      const fuelPeriods = await this.fuelSurchargeRepository.find({
        where: {
          tenantId,
          fechaInicio: LessThanOrEqual(fechaHasta),
          fechaFin: MoreThanOrEqual(fechaDesde),
        },
        order: { fechaInicio: 'ASC' },
      });

      for (const period of fuelPeriods) {
        historial.push({
          id: period.id,
          fecha: period.fechaInicio,
          tipo: 'FUEL_SURCHARGE_PERIODO',
          descripcion: `Fuel Surcharge: ${period.porcentajeSurcharge}% (${period.descripcionPeriodo})`,
          valor: Number(period.porcentajeSurcharge),
          esPorcentaje: true,
        });
      }
    } else {
      // Get recargo catalog entries
      const recargos = await this.recargoRepository.find({
        where: { tenantId, tipo },
        order: { createdAt: 'ASC' },
      });

      for (const recargo of recargos) {
        if (recargo.createdAt >= fechaDesde && recargo.createdAt <= fechaHasta) {
          historial.push({
            id: recargo.id,
            fecha: recargo.createdAt,
            tipo: recargo.tipo,
            descripcion: `${recargo.nombre}: ${recargo.descripcionMonto}`,
            valor: Number(recargo.monto),
            esPorcentaje: recargo.esPorcentaje,
          });
        }
      }
    }

    return historial.sort((a, b) => a.fecha.getTime() - b.fecha.getTime());
  }

  /**
   * Calculate all applicable surcharges for a shipment
   */
  async calculateAllSurcharges(
    tenantId: string,
    params: {
      tarifaBase: number;
      distanciaKm?: number;
      precioLitro?: number;
      horasEspera?: number;
      tiposAdicionales?: TipoRecargo[];
    }
  ): Promise<{
    fuelSurcharge: FuelSurchargeCalculation | null;
    detention: DetentionCalculation | null;
    otrosRecargos: Array<{ recargo: RecargoCatalogo; monto: number }>;
    totalRecargos: number;
  }> {
    let fuelSurcharge: FuelSurchargeCalculation | null = null;
    let detention: DetentionCalculation | null = null;
    const otrosRecargos: Array<{ recargo: RecargoCatalogo; monto: number }> = [];
    let totalRecargos = 0;

    // Calculate fuel surcharge if applicable
    if (params.distanciaKm && params.precioLitro) {
      fuelSurcharge = await this.calculateFuelSurcharge(
        tenantId,
        params.distanciaKm,
        params.precioLitro
      );
      totalRecargos += fuelSurcharge.montoSurcharge;
    }

    // Calculate detention if applicable
    if (params.horasEspera && params.horasEspera > DEFAULT_HORAS_LIBRES_DETENTION) {
      detention = await this.calculateDetention(
        tenantId,
        params.horasEspera,
        params.tarifaBase
      );
      totalRecargos += detention.montoDetention;
    }

    // Calculate additional surcharges
    if (params.tiposAdicionales && params.tiposAdicionales.length > 0) {
      for (const tipo of params.tiposAdicionales) {
        const recargos = await this.findByTipo(tipo, tenantId);
        for (const recargo of recargos) {
          const monto = recargo.calcularRecargo(params.tarifaBase);
          otrosRecargos.push({ recargo, monto });
          totalRecargos += monto;
        }
      }
    }

    // Get automatic surcharges (excluding fuel and detention already calculated)
    const ctx = { tenantId, userId: '' };
    const automaticos = await this.getRecargosAutomaticos(ctx);
    for (const recargo of automaticos) {
      // Skip if already calculated
      if (
        recargo.tipo === TipoRecargo.FUEL_SURCHARGE ||
        recargo.tipo === TipoRecargo.DETENTION ||
        params.tiposAdicionales?.includes(recargo.tipo)
      ) {
        continue;
      }

      const monto = recargo.calcularRecargo(params.tarifaBase);
      otrosRecargos.push({ recargo, monto });
      totalRecargos += monto;
    }

    return {
      fuelSurcharge,
      detention,
      otrosRecargos,
      totalRecargos: Math.round(totalRecargos * 100) / 100,
    };
  }
}
