import { BadRequestException, Injectable, NotFoundException, Logger } from '@nestjs/common';
import { AuditService } from 'src/audit/audit.service';
import { formatDateResponse } from 'src/utils/utilidades';
import { generarTablaAmortizacion, TipoInteres } from 'src/common/utils/calculo-interes.util';
import { planAplicaAProducto } from '../common/utils/plan-cuotas-matching.util';
import { PrismaService } from '../prisma/prisma.service';
import { ActualizarPlanCuotasDto } from './dto/actualizar-plan-cuotas.dto';
import { CalcularCuotasDto } from './dto/calcular-cuotas.dto';
import { CrearPlanCuotasDto } from './dto/crear-plan-cuotas.dto';
import { ActualizarCuotaIndividualDto, CrearCuotaIndividualDto } from './dto/cuota-individual.dto';

@Injectable()
export class PlanesCuotasService {
  private readonly logger = new Logger(PlanesCuotasService.name);
  constructor(
    private prisma: PrismaService,
    private readonly auditService: AuditService,
  ) {}

  async crearPlan(dto: CrearPlanCuotasDto, usuarioCreacion: string) {
    // Verificar que no exista un plan con el mismo código para la empresa
    const planExistente = await this.prisma.planes_cuotas.findFirst({
      where: {
        empresa_id: dto.empresaId,
        codigo: dto.codigo,
      },
    });

    if (planExistente) {
      throw new BadRequestException(`Ya existe un plan con el código ${dto.codigo} para esta empresa`);
    }

    this.validarRangoPrecioProducto(dto.precioProductoDesde, dto.precioProductoHasta);

    // Si es plan por defecto, desactivar otros planes por defecto
    if (dto.esDefault) {
      await this.prisma.planes_cuotas.updateMany({
        where: {
          empresa_id: dto.empresaId,
          es_default: true,
        },
        data: {
          es_default: false,
        },
      });
    }

    const plan = await this.prisma.planes_cuotas.create({
      data: {
        empresa_id: dto.empresaId,
        codigo: dto.codigo,
        nombre: dto.nombre,
        descripcion: dto.descripcion,
        tipo_calculo: dto.tipoCalculo,
        cantidad_cuotas: dto.cantidadCuotas,
        intervalo_dias: dto.intervaloDias,
        tasa_interes: dto.tasaInteres,
        tipo_interes: dto.tipoInteres,
        cuota_inicial_requerida: dto.cuotaInicialRequerida,
        cuota_inicial_minima: dto.cuotaInicialMinima,
        monto_minimo: dto.montoMinimo,
        monto_maximo: dto.montoMaximo,
        categoria_id: dto.categoriaId,
        precio_producto_desde: dto.precioProductoDesde,
        precio_producto_hasta: dto.precioProductoHasta,
        activo: dto.activo,
        es_default: dto.esDefault,
        usuario_creacion: usuarioCreacion,
      },
      include: {
        empresas: {
          select: {
            razon_social: true,
          },
        },
      },
    });

    this.logger.log(`Plan de cuotas creado: ${plan.nombre} (${plan.id})`, 'PlanesCuotasService');
    await this.auditService.log({
      empresa_id: dto.empresaId,
      user_id: usuarioCreacion,
      action: 'CREATE',
      entity_type: 'plan_cuotas',
      entity_id: plan.id,
      descripcion: `Plan de cuotas creado: ${plan.nombre} | Código: ${plan.codigo} | Tipo: ${plan.tipo_calculo}`,
      new_value: {
        codigo: plan.codigo,
        nombre: plan.nombre,
        tipo_calculo: plan.tipo_calculo,
        cantidad_cuotas: plan.cantidad_cuotas,
      },
    });
    return plan;
  }

  async obtenerPlanes(empresaId: string, activo?: boolean) {
    const where: any = { empresa_id: empresaId };

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

    return this.prisma.planes_cuotas.findMany({
      where,
      include: {
        empresas: {
          select: {
            razon_social: true,
          },
        },
        _count: {
          select: {
            cuotas_calculadas: true,
          },
        },
      },
      orderBy: [{ es_default: 'desc' }, { activo: 'desc' }, { nombre: 'asc' }],
    });
  }

  /**
   * Como obtenerPlanes(), pero filtra por los ítems del carrito actual: un
   * plan solo aparece si su alcance (categoría/precio, Fase 6) cubre TODOS
   * los ítems — un plan automático financia el monto total del carrito, no
   * un ítem individual, así que no alcanza con que matchee alguno. Un plan
   * sin alcance seteado siempre aplica. Ver docs/plan-motor-precios-rentabilidad.md §12.
   */
  async obtenerPlanesAplicables(
    empresaId: string,
    items: { productoId: string; precioContado: number }[],
    montoTotal?: number,
  ) {
    const todos = await this.obtenerPlanes(empresaId, true);

    // Filtro por monto del carrito: mismo criterio que la validación de `calcularCuotas`
    // (un `monto_minimo`/`monto_maximo` en 0 o null significa "sin límite"). Sin esto el
    // selector ofrecía planes que después fallaban con "El monto total debe ser mayor a X".
    const porMonto = (lista: typeof todos) =>
      montoTotal === undefined || montoTotal === null
        ? lista
        : lista.filter((plan) => {
            const min = Number(plan.monto_minimo) || 0;
            const max = Number(plan.monto_maximo) || 0;
            if (min && montoTotal < min) return false;
            if (max && montoTotal > max) return false;
            return true;
          });

    if (items.length === 0) return porMonto(todos);

    const productoIds = items.map((i) => i.productoId);
    const productos = await this.prisma.productos.findMany({
      where: { id: { in: productoIds }, empresa_id: empresaId },
      select: { id: true, categoria_id: true, categoria: { select: { padre_id: true } } },
    });
    const productoPorId = new Map(productos.map((p) => [p.id, p]));

    return porMonto(todos).filter((plan) =>
      items.every(({ productoId, precioContado }) => {
        const producto = productoPorId.get(productoId);
        if (!producto) return false;
        return planAplicaAProducto(
          plan,
          { categoriaId: producto.categoria_id, categoriaPadreId: producto.categoria?.padre_id ?? null },
          precioContado,
        );
      }),
    );
  }

  async obtenerPlanPorId(id: string, empresaId: string) {
    const plan = await this.prisma.planes_cuotas.findFirst({
      where: {
        id,
        empresa_id: empresaId,
      },
      include: {
        empresas: {
          select: {
            razon_social: true,
          },
        },
        planes_cuotas_detalles: {
          where: { activo: true },
          orderBy: { numero_cuota: 'asc' },
        },
        _count: {
          select: {
            cuotas_calculadas: true,
          },
        },
      },
    });

    if (!plan) {
      throw new NotFoundException('Plan de cuotas no encontrado');
    }

    return plan;
  }

  async actualizarPlan(id: string, empresaId: string, dto: ActualizarPlanCuotasDto, usuarioActualizacion: string) {
    const planExistente = await this.prisma.planes_cuotas.findFirst({
      where: {
        id,
        empresa_id: empresaId,
      },
    });

    if (!planExistente) {
      throw new NotFoundException('Plan de cuotas no encontrado');
    }

    const desdeEfectivo =
      dto.precioProductoDesde !== undefined
        ? dto.precioProductoDesde
        : planExistente.precio_producto_desde !== null
          ? Number(planExistente.precio_producto_desde)
          : undefined;
    const hastaEfectivo =
      dto.precioProductoHasta !== undefined
        ? dto.precioProductoHasta
        : planExistente.precio_producto_hasta !== null
          ? Number(planExistente.precio_producto_hasta)
          : undefined;
    this.validarRangoPrecioProducto(desdeEfectivo, hastaEfectivo);

    // Si se está marcando como plan por defecto, desactivar otros planes por defecto
    if (dto.esDefault) {
      await this.prisma.planes_cuotas.updateMany({
        where: {
          empresa_id: empresaId,
          es_default: true,
          id: { not: id },
        },
        data: {
          es_default: false,
        },
      });
    }

    const updated = await this.prisma.planes_cuotas.update({
      where: { id },
      data: {
        ...(dto.nombre !== undefined && { nombre: dto.nombre }),
        ...(dto.descripcion !== undefined && { descripcion: dto.descripcion }),
        ...(dto.tipoCalculo !== undefined && { tipo_calculo: dto.tipoCalculo }),
        ...(dto.cantidadCuotas !== undefined && {
          cantidad_cuotas: dto.cantidadCuotas,
        }),
        ...(dto.intervaloDias !== undefined && {
          intervalo_dias: dto.intervaloDias,
        }),
        ...(dto.tasaInteres !== undefined && { tasa_interes: dto.tasaInteres }),
        ...(dto.tipoInteres !== undefined && { tipo_interes: dto.tipoInteres }),
        ...(dto.cuotaInicialRequerida !== undefined && {
          cuota_inicial_requerida: dto.cuotaInicialRequerida,
        }),
        ...(dto.cuotaInicialMinima !== undefined && {
          cuota_inicial_minima: dto.cuotaInicialMinima,
        }),
        ...(dto.montoMinimo !== undefined && { monto_minimo: dto.montoMinimo }),
        ...(dto.montoMaximo !== undefined && { monto_maximo: dto.montoMaximo }),
        ...(dto.categoriaId !== undefined && { categoria_id: dto.categoriaId }),
        ...(dto.precioProductoDesde !== undefined && { precio_producto_desde: dto.precioProductoDesde }),
        ...(dto.precioProductoHasta !== undefined && { precio_producto_hasta: dto.precioProductoHasta }),
        ...(dto.activo !== undefined && { activo: dto.activo }),
        ...(dto.esDefault !== undefined && { es_default: dto.esDefault }),
        usuario_actualizacion: usuarioActualizacion,
        fecha_actualizacion: new Date(),
      },
      include: {
        empresas: {
          select: {
            razon_social: true,
          },
        },
      },
    });
    this.logger.log(`Plan de cuotas actualizado: ${updated.nombre} (${id})`, 'PlanesCuotasService');
    await this.auditService.log({
      empresa_id: empresaId,
      user_id: usuarioActualizacion,
      action: 'UPDATE',
      entity_type: 'plan_cuotas',
      entity_id: id,
      descripcion: `Plan de cuotas actualizado: ${updated.nombre}`,
      old_value: {
        nombre: planExistente.nombre,
        tipo_calculo: planExistente.tipo_calculo,
        activo: planExistente.activo,
      },
      new_value: dto as Record<string, unknown>,
    });
    return updated;
  }

  async eliminarPlan(id: string, empresaId: string, user_id?: string) {
    const plan = await this.prisma.planes_cuotas.findFirst({
      where: {
        id,
        empresa_id: empresaId,
      },
      include: {
        _count: {
          select: {
            cuotas_calculadas: true,
          },
        },
      },
    });

    if (!plan) {
      throw new NotFoundException('Plan de cuotas no encontrado');
    }

    if (plan._count.cuotas_calculadas > 0) {
      throw new BadRequestException('No se puede eliminar un plan que tiene cuotas calculadas asociadas');
    }

    await this.prisma.planes_cuotas.delete({
      where: { id },
    });
    this.logger.log(`Plan de cuotas eliminado: ${plan.nombre} (${id})`, 'PlanesCuotasService');
    await this.auditService.log({
      empresa_id: empresaId,
      user_id,
      action: 'DELETE',
      entity_type: 'plan_cuotas',
      entity_id: id,
      descripcion: `Plan de cuotas eliminado: ${plan.nombre} | Código: ${plan.codigo}`,
      old_value: { nombre: plan.nombre, codigo: plan.codigo, tipo_calculo: plan.tipo_calculo },
    });
    return { message: 'Plan de cuotas eliminado correctamente' };
  }

  async calcularCuotas(dto: CalcularCuotasDto, usuarioCalculo: string) {
    const plan = await this.prisma.planes_cuotas.findUnique({
      where: { id: dto.planCuotaId },
      include: {
        empresas: {
          select: { configuracion_precios: true },
        },
        planes_cuotas_detalles: {
          where: { activo: true },
          orderBy: { numero_cuota: 'asc' },
        },
      },
    });

    if (!plan) {
      throw new NotFoundException('Plan de cuotas no encontrado');
    }

    if (!plan.activo) {
      throw new BadRequestException('El plan de cuotas no está activo');
    }

    // Resolución 347/2014 SEDECO / config de la empresa — usado como fallback de moneda
    // y para el redondeo de los tipos 'manual'/'mixto' (sin interés, sin amortización).
    const configPrecios = (plan.empresas as any)?.configuracion_precios || {};
    const redondeoTipo: string = configPrecios.redondeo_precios || 'normal';
    const decimales: number = configPrecios.decimales_precio ?? 2;
    const moneda: string = dto.monedaIso || configPrecios.moneda_principal || 'PYG';

    // Validar monto mínimo y máximo
    if (plan.monto_minimo && dto.montoTotal < Number(plan.monto_minimo)) {
      throw new BadRequestException(
        `El monto total debe ser mayor a ${this.formatearMonto(Number(plan.monto_minimo), moneda)}`,
      );
    }

    if (plan.monto_maximo && dto.montoTotal > Number(plan.monto_maximo)) {
      throw new BadRequestException(
        `El monto total debe ser menor a ${this.formatearMonto(Number(plan.monto_maximo), moneda)}`,
      );
    }

    let cuotasCalculadas: any[] = [];
    let montoRestante = dto.montoTotal;
    let cantidadCuotas = 0;

    // Procesar cuota inicial si es requerida
    if (plan.cuota_inicial_requerida && dto.cuotaInicial) {
      const minimoRequerido = plan.cuota_inicial_minima
        ? (dto.montoTotal * Number(plan.cuota_inicial_minima)) / 100
        : 0;

      if (dto.cuotaInicial < minimoRequerido) {
        throw new BadRequestException(
          `La cuota inicial debe ser mayor a ${this.formatearMonto(minimoRequerido, moneda)}`,
        );
      }

      montoRestante -= dto.cuotaInicial;
    }

    // Calcular cuotas según el tipo de plan
    if (plan.tipo_calculo === 'manual' && dto.cuotasPersonalizadas) {
      // Cuotas manuales
      cuotasCalculadas = dto.cuotasPersonalizadas.map((cuota, index) => ({
        numero_cuota: index + 1,
        monto_cuota: cuota.monto,
        fecha_vencimiento: this.calcularFechaVencimiento(new Date(), cuota.diasVencimiento || plan.intervalo_dias),
      }));
      cantidadCuotas = cuotasCalculadas.length;
    } else if (plan.tipo_calculo === 'automatico') {
      // Cuotas automáticas: tabla de amortización completa (capital/interés/saldo),
      // con redondeo comercial ya aplicado — no pasa por el bloque de redondeo de abajo.
      // `plan.cantidad_cuotas` es el MÁXIMO permitido; el vendedor puede pedir menos
      // (ej. plan "hasta 12 cuotas" usado a 3, 6 o 12) pero nunca más.
      const maximoCuotas = plan.cantidad_cuotas || 1;
      if (dto.cantidadCuotas && dto.cantidadCuotas > maximoCuotas) {
        throw new BadRequestException(`Este plan admite hasta ${maximoCuotas} cuotas`);
      }
      cantidadCuotas = dto.cantidadCuotas || maximoCuotas;
      const tabla = generarTablaAmortizacion(
        montoRestante,
        cantidadCuotas,
        Number(plan.tasa_interes),
        plan.tipo_interes as TipoInteres,
        plan.intervalo_dias,
        moneda,
      );

      cuotasCalculadas = tabla.map((c) => ({
        numero_cuota: c.numero,
        monto_cuota: c.monto_cuota,
        capital: c.capital,
        interes: c.interes,
        fecha_vencimiento: this.calcularFechaVencimiento(new Date(), plan.intervalo_dias * c.numero),
      }));
    } else if (plan.tipo_calculo === 'mixto') {
      // Combinar detalles predefinidos con cálculo automático
      if (plan.planes_cuotas_detalles.length > 0) {
        for (const detalle of plan.planes_cuotas_detalles) {
          const montoCuota = (montoRestante * Number(detalle.porcentaje_monto)) / 100;
          cuotasCalculadas.push({
            numero_cuota: detalle.numero_cuota,
            monto_cuota: montoCuota,
            fecha_vencimiento: this.calcularFechaVencimiento(new Date(), detalle.dias_vencimiento),
          });
        }
        cantidadCuotas = plan.planes_cuotas_detalles.length;
      }
    }

    // Redondeo SEDECO para 'manual'/'mixto' (sin interés, no tienen tabla de amortización).
    if (plan.tipo_calculo !== 'automatico' && redondeoTipo !== 'ninguno' && cuotasCalculadas.length > 0) {
      // Calcular total esperado con interés
      const totalEsperado = cuotasCalculadas.reduce((sum, c) => sum + c.monto_cuota, 0);

      // Redondear cada cuota excepto la última
      for (let i = 0; i < cuotasCalculadas.length - 1; i++) {
        cuotasCalculadas[i].monto_cuota = this.redondearMonto(
          cuotasCalculadas[i].monto_cuota,
          redondeoTipo,
          decimales,
          moneda,
        );
      }

      // La última cuota absorbe la diferencia de redondeo
      const sumaRedondeadas = cuotasCalculadas.slice(0, -1).reduce((sum, c) => sum + c.monto_cuota, 0);
      const ultimaCuota = totalEsperado - sumaRedondeadas;
      cuotasCalculadas[cuotasCalculadas.length - 1].monto_cuota = this.redondearMonto(
        ultimaCuota,
        redondeoTipo,
        decimales,
        moneda,
      );
    }

    // Guardar el cálculo en la base de datos
    const cuotaCalculada = await this.prisma.cuotas_calculadas.create({
      data: {
        plan_cuota_id: dto.planCuotaId,
        cliente_id: dto.clienteId,
        factura_id: dto.facturaId,
        monto_total: dto.montoTotal,
        cantidad_cuotas: cantidadCuotas,
        tasa_interes_aplicada: plan.tasa_interes,
        cuota_inicial: dto.cuotaInicial,
        usuario_calculo: usuarioCalculo,
        observaciones: dto.observaciones,
        cuotas_calculadas_detalle: {
          create: cuotasCalculadas,
        },
      },
      include: {
        cuotas_calculadas_detalle: {
          orderBy: { numero_cuota: 'asc' },
        },
        planes_cuotas: {
          select: {
            nombre: true,
            codigo: true,
          },
        },
      },
    });

    this.logger.log(`Cuotas calculadas (${cuotaCalculada.id}) plan ${dto.planCuotaId}`, 'PlanesCuotasService');
    await this.auditService.log({
      empresa_id: plan.empresa_id,
      user_id: usuarioCalculo,
      action: 'CREATE',
      entity_type: 'cuota_calculada',
      entity_id: cuotaCalculada.id,
      descripcion: `Cuotas calculadas: ${cantidadCuotas} cuotas | Plan: ${plan.nombre} | Monto: ${dto.montoTotal}`,
      new_value: { plan_cuota_id: dto.planCuotaId, monto_total: dto.montoTotal, cantidad_cuotas: cantidadCuotas },
    });
    return {
      ...cuotaCalculada,
      fecha_calculo: formatDateResponse(cuotaCalculada.fecha_calculo),
      cuotas_calculadas_detalle: cuotaCalculada.cuotas_calculadas_detalle.map((detalle) => ({
        ...detalle,
        fecha_vencimiento: formatDateResponse(detalle.fecha_vencimiento),
        fecha_pago: formatDateResponse(detalle.fecha_pago),
      })),
    };
  }

  /** Formatea un monto para mensajes de error legibles (ej. "Gs. 5.000.000", "US$ 1,500.00"). */
  private formatearMonto(monto: number, moneda: string): string {
    const formateado = new Intl.NumberFormat('es-PY', {
      minimumFractionDigits: moneda === 'PYG' ? 0 : 2,
      maximumFractionDigits: moneda === 'PYG' ? 0 : 2,
    }).format(monto);
    const simbolo = moneda === 'PYG' ? 'Gs.' : moneda === 'USD' ? 'US$' : moneda === 'BRL' ? 'R$' : moneda;
    return `${simbolo} ${formateado}`;
  }

  /**
   * Redondea un monto según la moneda y tipo de redondeo.
   * - PYG: Resolución 347/2014 SEDECO → múltiplos de 50 Gs
   * - Monedas extranjeras: redondeo por decimales (50 céntimos tolerancia)
   */
  private redondearMonto(monto: number, tipo: string, decimales: number, moneda: string = 'PYG'): number {
    // Guaraníes: redondeo a múltiplos de 50 (Res. 347/2014 SEDECO)
    if (moneda === 'PYG') {
      switch (tipo) {
        case 'superior':
          return Math.ceil(monto / 50) * 50;
        case 'inferior':
          return Math.floor(monto / 50) * 50;
        case 'normal':
        default:
          return Math.round(monto / 50) * 50;
      }
    }

    // Monedas extranjeras: redondeo por decimales
    const factor = Math.pow(10, decimales);
    switch (tipo) {
      case 'superior':
        return Math.ceil(monto * factor) / factor;
      case 'inferior':
        return Math.floor(monto * factor) / factor;
      case 'normal':
      default:
        return Math.round(monto * factor) / factor;
    }
  }

  private calcularFechaVencimiento(fechaBase: Date, dias: number): Date {
    const fecha = new Date(fechaBase);
    fecha.setDate(fecha.getDate() + dias);
    return fecha;
  }

  async obtenerCuotasCalculadas(empresaId: string, clienteId?: string, estado?: string) {
    const where: any = {
      planes_cuotas: {
        empresa_id: empresaId,
      },
    };

    if (clienteId) {
      where.cliente_id = clienteId;
    }

    if (estado) {
      where.estado = estado;
    }

    return this.prisma.cuotas_calculadas.findMany({
      where,
      include: {
        planes_cuotas: {
          select: {
            nombre: true,
            codigo: true,
          },
        },
        clientes: {
          select: {
            personas: {
              select: {
                razon_social: true,
                nro_documento: true,
                ruc: true,
              },
            },
          },
        },
        cuotas_calculadas_detalle: {
          orderBy: { numero_cuota: 'asc' },
        },
      },
      orderBy: { fecha_calculo: 'desc' },
    });
  }

  async obtenerCuotaCalculadaPorId(id: string, empresaId: string) {
    const cuota = await this.prisma.cuotas_calculadas.findFirst({
      where: {
        id,
        planes_cuotas: {
          empresa_id: empresaId,
        },
      },
      include: {
        planes_cuotas: {
          select: {
            nombre: true,
            codigo: true,
            tipo_calculo: true,
          },
        },
        clientes: {
          select: {
            personas: {
              select: {
                razon_social: true,
                nro_documento: true,
                ruc: true,
              },
            },
          },
        },
        cuotas_calculadas_detalle: {
          orderBy: { numero_cuota: 'asc' },
        },
      },
    });

    if (!cuota) {
      throw new NotFoundException('Cuota calculada no encontrada');
    }

    return cuota;
  }

  // ===== CUOTAS INDIVIDUALES POR PRODUCTO =====

  async crearCuotaIndividual(dto: CrearCuotaIndividualDto, empresaId: string, usuarioCreacion: string) {
    // Verificar que el plan exista y sea de la empresa
    const plan = await this.prisma.planes_cuotas.findFirst({
      where: { id: dto.planCuotaId, empresa_id: empresaId },
    });
    if (!plan) {
      throw new NotFoundException('Plan de cuotas no encontrado');
    }

    // Verificar que el producto exista y sea de la empresa
    const producto = await this.prisma.productos.findFirst({
      where: { id: dto.productoId, empresa_id: empresaId },
    });
    if (!producto) {
      throw new NotFoundException('Producto no encontrado');
    }

    // Verificar que no exista ya una cuota individual para este producto + plan
    const existente = await this.prisma.cuotas_individuales.findFirst({
      where: {
        producto_id: dto.productoId,
        plan_cuota_id: dto.planCuotaId,
      },
    });
    if (existente) {
      throw new BadRequestException('Ya existe una configuración de cuotas para este producto con este plan');
    }

    const cuotaIndividual = await this.prisma.cuotas_individuales.create({
      data: {
        producto_id: dto.productoId,
        plan_cuota_id: dto.planCuotaId,
        empresa_id: empresaId,
        monto_cuota: dto.montoCuota,
        cantidad_cuotas: dto.cantidadCuotas,
        cuota_inicial: dto.cuotaInicial,
        activo: dto.activo ?? true,
        usuario_creacion: usuarioCreacion,
      },
      include: {
        planes_cuotas: {
          select: {
            id: true,
            nombre: true,
            codigo: true,
            tipo_calculo: true,
            intervalo_dias: true,
          },
        },
      },
    });
    this.logger.log(
      `Cuota individual creada (${cuotaIndividual.id}) producto ${dto.productoId}`,
      'PlanesCuotasService',
    );
    await this.auditService.log({
      empresa_id: empresaId,
      user_id: usuarioCreacion,
      action: 'CREATE',
      entity_type: 'cuota_individual',
      entity_id: cuotaIndividual.id,
      descripcion: `Cuota individual creada | Producto: ${dto.productoId} | Plan: ${dto.planCuotaId} | Cuotas: ${dto.cantidadCuotas}`,
      new_value: {
        producto_id: dto.productoId,
        plan_cuota_id: dto.planCuotaId,
        cantidad_cuotas: dto.cantidadCuotas,
        monto_cuota: dto.montoCuota,
      },
    });
    return cuotaIndividual;
  }

  async obtenerCuotasProducto(productoId: string, empresaId: string) {
    return this.prisma.cuotas_individuales.findMany({
      where: {
        producto_id: productoId,
        empresa_id: empresaId,
      },
      include: {
        planes_cuotas: {
          select: {
            id: true,
            nombre: true,
            codigo: true,
            tipo_calculo: true,
            intervalo_dias: true,
            cantidad_cuotas: true,
            tasa_interes: true,
            tipo_interes: true,
          },
        },
      },
      orderBy: { cantidad_cuotas: 'asc' },
    });
  }

  async actualizarCuotaIndividual(
    id: string,
    empresaId: string,
    dto: ActualizarCuotaIndividualDto,
    usuarioActualizacion: string,
  ) {
    const cuota = await this.prisma.cuotas_individuales.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!cuota) {
      throw new NotFoundException('Cuota individual no encontrada');
    }

    const updatedCuota = await this.prisma.cuotas_individuales.update({
      where: { id },
      data: {
        ...(dto.montoCuota !== undefined && { monto_cuota: dto.montoCuota }),
        ...(dto.cantidadCuotas !== undefined && {
          cantidad_cuotas: dto.cantidadCuotas,
        }),
        ...(dto.cuotaInicial !== undefined && {
          cuota_inicial: dto.cuotaInicial,
        }),
        ...(dto.activo !== undefined && { activo: dto.activo }),
        usuario_actualizacion: usuarioActualizacion,
        fecha_actualizacion: new Date(),
      },
      include: {
        planes_cuotas: {
          select: {
            id: true,
            nombre: true,
            codigo: true,
            tipo_calculo: true,
            intervalo_dias: true,
          },
        },
      },
    });
    this.logger.log(`Cuota individual actualizada (${id})`, 'PlanesCuotasService');
    await this.auditService.log({
      empresa_id: empresaId,
      user_id: usuarioActualizacion,
      action: 'UPDATE',
      entity_type: 'cuota_individual',
      entity_id: id,
      descripcion: `Cuota individual actualizada`,
      old_value: {
        monto_cuota: Number(cuota.monto_cuota),
        cantidad_cuotas: cuota.cantidad_cuotas,
        activo: cuota.activo,
      },
      new_value: dto as Record<string, unknown>,
    });
    return updatedCuota;
  }

  async eliminarCuotaIndividual(id: string, empresaId: string, user_id?: string) {
    const cuota = await this.prisma.cuotas_individuales.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!cuota) {
      throw new NotFoundException('Cuota individual no encontrada');
    }

    await this.prisma.cuotas_individuales.delete({
      where: { id },
    });
    this.logger.log(`Cuota individual eliminada (${id})`, 'PlanesCuotasService');
    await this.auditService.log({
      empresa_id: empresaId,
      user_id,
      action: 'DELETE',
      entity_type: 'cuota_individual',
      entity_id: id,
      descripcion: `Cuota individual eliminada | Producto: ${cuota.producto_id}`,
      old_value: {
        producto_id: cuota.producto_id,
        plan_cuota_id: cuota.plan_cuota_id,
        cantidad_cuotas: cuota.cantidad_cuotas,
      },
    });
    return { message: 'Cuota individual eliminada correctamente' };
  }

  private validarRangoPrecioProducto(desde?: number | null, hasta?: number | null) {
    if (desde !== undefined && desde !== null && hasta !== undefined && hasta !== null && Number(hasta) <= Number(desde)) {
      throw new BadRequestException('precioProductoHasta debe ser mayor a precioProductoDesde');
    }
  }
}
