import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { AuditService } from 'src/audit/audit.service';
import { formatDateResponse } from 'src/utils/utilidades';
import { PrismaService } from '../prisma/prisma.service';
import { CrearListaPreciosDto } from './dto/crear-lista-precios.dto';

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

  /**
   * Tope de descuento: % máximo que se puede aplicar sobre precioFinal sin
   * requerir autorización de supervisor. Ver docs/plan-motor-precios-rentabilidad.md §10.3.
   * piso = precio_minimo (si está cargado) o precio_costo; tope = % exacto para
   * llegar de precioFinal al piso (punto de equilibrio). Si no hay piso disponible,
   * se usa el campo manual descuento_maximo_pct como respaldo; si tampoco existe, tope = 0
   * (cualquier descuento requiere autorización).
   */
  private calcularTopeDescuento(params: {
    precioFinal: number;
    precioCosto: number;
    precioMinimo: number;
    descuentoMaximoPct: number;
  }): { topeDescuentoPct: number; piso: number } {
    const { precioFinal, precioCosto, precioMinimo, descuentoMaximoPct } = params;
    const piso = precioMinimo > 0 ? precioMinimo : precioCosto;

    if (piso > 0 && precioFinal > 0) {
      return { topeDescuentoPct: Math.max(0, (1 - piso / precioFinal) * 100), piso };
    }

    return { topeDescuentoPct: descuentoMaximoPct > 0 ? descuentoMaximoPct : 0, piso: 0 };
  }

  async obtenerPrecioProducto(params: {
    productoId: string;
    clienteId?: string;
    zonaGeografica?: string;
    canalVenta?: string;
    moneda?: string;
    empresaId: string;
  }) {
    const { productoId, empresaId, moneda, clienteId, zonaGeografica, canalVenta } = params;
    const ahora = new Date();

    // Recopilar IDs de listas candidatas con queries simples
    const listaIds = new Set<string>();

    // 1. Lista directa del cliente (FK lista_precios_id)
    if (clienteId) {
      const cliente = await this.prisma.clientes.findUnique({
        where: { id: clienteId },
        select: { lista_precios_id: true },
      });
      if (cliente?.lista_precios_id) {
        listaIds.add(cliente.lista_precios_id);
      }

      // 2. Listas asignadas al cliente vía lista_precios_asignaciones
      const asignaciones = await this.prisma.lista_precios_asignaciones.findMany({
        where: {
          activa: true,
          tipo_entidad: 'cliente',
          entidad_id: clienteId,
          fecha_inicio: { lte: ahora },
          OR: [{ fecha_fin: null }, { fecha_fin: { gte: ahora } }],
        },
        select: { lista_precios_id: true },
      });
      asignaciones.forEach((a) => listaIds.add(a.lista_precios_id));
    }

    if (zonaGeografica) {
      const asignaciones = await this.prisma.lista_precios_asignaciones.findMany({
        where: {
          activa: true,
          tipo_entidad: 'zona',
          zona_geografica: zonaGeografica,
          fecha_inicio: { lte: ahora },
          OR: [{ fecha_fin: null }, { fecha_fin: { gte: ahora } }],
        },
        select: { lista_precios_id: true },
      });
      asignaciones.forEach((a) => listaIds.add(a.lista_precios_id));
    }

    if (canalVenta) {
      const asignaciones = await this.prisma.lista_precios_asignaciones.findMany({
        where: {
          activa: true,
          tipo_entidad: 'canal',
          canal_venta: canalVenta,
          fecha_inicio: { lte: ahora },
          OR: [{ fecha_fin: null }, { fecha_fin: { gte: ahora } }],
        },
        select: { lista_precios_id: true },
      });
      asignaciones.forEach((a) => listaIds.add(a.lista_precios_id));
    }

    // 3. Buscar listas activas y vigentes
    // Primero las del cliente (prioridad absoluta), luego las generales
    const baseWhere: any = {
      empresa_id: empresaId,
      activa: true,
      fecha_inicio: { lte: ahora },
      OR: [{ fecha_fin: null }, { fecha_fin: { gte: ahora } }],
      ...(moneda && { moneda }),
    };

    // Listas asignadas al cliente van primero (tienen prioridad sobre las generales)
    const listasCliente = listaIds.size > 0
      ? await this.prisma.lista_precios.findMany({
          where: { ...baseWhere, id: { in: Array.from(listaIds) } },
          orderBy: { prioridad: 'asc' },
        })
      : [];

    // Luego las generales como fallback
    const listasGenerales = await this.prisma.lista_precios.findMany({
      where: { ...baseWhere, tipo_aplicacion: 'general' },
      orderBy: { prioridad: 'asc' },
    });

    // Cliente primero, generales después
    const listas = [...listasCliente, ...listasGenerales];

    // Obtener precio base del producto
    const producto = await this.prisma.productos.findUnique({
      where: { id: productoId },
      select: { precio: true, descripcion: true, precio_costo: true },
    });
    if (!producto) throw new Error('Producto no encontrado');
    const precioBaseProducto = Number(producto.precio || 0);
    const precioCosto = Number(producto.precio_costo || 0);

    let precioEncontrado: any = null;
    let listaAplicada: any = null;

    for (const lista of listas) {
      // Buscar precio específico del producto en la lista
      const precio = await this.prisma.lista_precios_productos.findFirst({
        where: { lista_precios_id: lista.id, producto_id: productoId },
      });

      if (precio) {
        precioEncontrado = precio;
        listaAplicada = lista;
        break;
      }

      // Si no hay precio específico pero la lista aplica descuento/recargo general, usarla
      if (lista.aplica_descuento_general || lista.aplica_recargo_general) {
        listaAplicada = lista;
        break;
      }
    }

    // Sin lista aplicable: devolver precio base del producto
    if (!listaAplicada) {
      const { topeDescuentoPct } = this.calcularTopeDescuento({
        precioFinal: precioBaseProducto,
        precioCosto,
        precioMinimo: 0,
        descuentoMaximoPct: 0,
      });
      return {
        productoId,
        precioBase: precioBaseProducto,
        precioFinal: precioBaseProducto,
        listaAplicada: null,
        descuentos: 0,
        recargos: 0,
        descuentoGeneral: 0,
        recargoGeneral: 0,
        permiteCuotas: true,
        cuotasMaximas: 12,
        precioCosto,
        precioMinimo: 0,
        descuentoMaximoPct: 0,
        topeDescuentoPct,
      };
    }

    // ── Paso 1: Resolver precio base ──
    // Si hay entrada del producto en la lista con precio_base > 0, usarlo
    // Si no, usar precio base del producto
    const precioBaseLista = precioEncontrado ? Number(precioEncontrado.precio_base) : 0;
    let precioBase = (precioBaseLista > 0) ? precioBaseLista : precioBaseProducto;

    // ── Paso 2: Aplicar descuento/recargo del PRODUCTO (si hay entrada específica) ──
    let precioFinal = precioBase;
    let descProducto = 0;
    let recProducto = 0;

    if (precioEncontrado) {
      descProducto = Number(precioEncontrado.descuento_porcentaje || 0);
      recProducto = Number(precioEncontrado.recargo_porcentaje || 0);

      // Aplicar ambos secuencialmente: descuento primero, luego recargo
      if (descProducto > 0) {
        precioFinal = precioFinal * (1 - descProducto / 100);
      }
      if (recProducto > 0) {
        precioFinal = precioFinal * (1 + recProducto / 100);
      }
    }

    // ── Paso 3: Aplicar descuento/recargo GENERAL de la lista ──
    const descGeneral = listaAplicada.aplica_descuento_general ? Number(listaAplicada.descuento_porcentaje || 0) : 0;
    const recGeneral = listaAplicada.aplica_recargo_general ? Number(listaAplicada.recargo_porcentaje || 0) : 0;

    if (descGeneral > 0) {
      precioFinal = precioFinal * (1 - descGeneral / 100);
    }
    if (recGeneral > 0) {
      precioFinal = precioFinal * (1 + recGeneral / 100);
    }

    // ── Paso 4: Aplicar precio mínimo/máximo del producto ──
    if (precioEncontrado) {
      const pMin = Number(precioEncontrado.precio_minimo || 0);
      const pMax = Number(precioEncontrado.precio_maximo || 0);
      if (pMin > 0 && precioFinal < pMin) precioFinal = pMin;
      if (pMax > 0 && precioFinal > pMax) precioFinal = pMax;
    }

    // ── Paso 5: Aplicar redondeo de la lista ──
    if (listaAplicada.redondeo_activo) {
      const decimales = Number(listaAplicada.redondeo_decimales || 0);
      const factor = Math.pow(10, decimales);
      switch (listaAplicada.redondeo_tipo) {
        case 'superior':
          precioFinal = Math.ceil(precioFinal * factor) / factor;
          break;
        case 'inferior':
          precioFinal = Math.floor(precioFinal * factor) / factor;
          break;
        default: // 'normal'
          precioFinal = Math.round(precioFinal * factor) / factor;
          break;
      }
    }

    // Asegurar no negativo
    precioFinal = Math.max(precioFinal, 0);

    const precioMinimo = Number(precioEncontrado?.precio_minimo || 0);
    const descuentoMaximoPct = Number(precioEncontrado?.descuento_maximo_pct || 0);
    const { topeDescuentoPct } = this.calcularTopeDescuento({
      precioFinal,
      precioCosto,
      precioMinimo,
      descuentoMaximoPct,
    });

    return {
      productoId,
      precioBase,
      precioFinal,
      listaAplicada: {
        id: listaAplicada.id,
        codigo: listaAplicada.codigo,
        nombre: listaAplicada.nombre,
        moneda: listaAplicada.moneda,
      },
      descuentos: descProducto,
      recargos: recProducto,
      descuentoGeneral: descGeneral,
      recargoGeneral: recGeneral,
      permiteCuotas: precioEncontrado?.permite_cuotas ?? true,
      cuotasMaximas: precioEncontrado?.cuotas_maximas ?? 12,
      moneda: listaAplicada.moneda,
      precioCosto,
      precioMinimo,
      descuentoMaximoPct,
      topeDescuentoPct,
    };
  }

  /**
   * Versión batch de `obtenerPrecioProducto`: resuelve el precio real de N
   * productos con una sola consulta a `lista_precios_productos`, en vez de una
   * por producto. Usada por pantallas de búsqueda/listado (ProductoBrowserModal,
   * Solicitud de Crédito) donde resolver 1 por 1 sería una consulta por cada
   * resultado. Mismas reglas que la versión individual (lista del cliente
   * primero, luego generales por prioridad), sin zona/canal — no aplican en un
   * contexto de búsqueda genérica.
   */
  async obtenerPreciosProductosBatch(params: { productoIds: string[]; empresaId: string; clienteId?: string }) {
    const { productoIds, empresaId, clienteId } = params;
    if (productoIds.length === 0) return {};
    const ahora = new Date();

    const listaIds = new Set<string>();
    if (clienteId) {
      const cliente = await this.prisma.clientes.findUnique({
        where: { id: clienteId },
        select: { lista_precios_id: true },
      });
      if (cliente?.lista_precios_id) listaIds.add(cliente.lista_precios_id);

      const asignaciones = await this.prisma.lista_precios_asignaciones.findMany({
        where: {
          activa: true,
          tipo_entidad: 'cliente',
          entidad_id: clienteId,
          fecha_inicio: { lte: ahora },
          OR: [{ fecha_fin: null }, { fecha_fin: { gte: ahora } }],
        },
        select: { lista_precios_id: true },
      });
      asignaciones.forEach((a) => listaIds.add(a.lista_precios_id));
    }

    const baseWhere: any = {
      empresa_id: empresaId,
      activa: true,
      fecha_inicio: { lte: ahora },
      OR: [{ fecha_fin: null }, { fecha_fin: { gte: ahora } }],
    };

    const [listasCliente, listasGenerales, productos, preciosLista] = await Promise.all([
      listaIds.size > 0
        ? this.prisma.lista_precios.findMany({ where: { ...baseWhere, id: { in: Array.from(listaIds) } }, orderBy: { prioridad: 'asc' } })
        : Promise.resolve([]),
      this.prisma.lista_precios.findMany({ where: { ...baseWhere, tipo_aplicacion: 'general' }, orderBy: { prioridad: 'asc' } }),
      this.prisma.productos.findMany({ where: { id: { in: productoIds } }, select: { id: true, precio: true, precio_costo: true } }),
      this.prisma.lista_precios_productos.findMany({
        where: { producto_id: { in: productoIds } },
      }),
    ]);

    const listas = [...listasCliente, ...listasGenerales];
    const listaIdsOrdenados = listas.map((l) => l.id);
    const productoPrecioBase = new Map(productos.map((p) => [p.id, Number(p.precio || 0)]));
    const productoPrecioCosto = new Map(productos.map((p) => [p.id, Number(p.precio_costo || 0)]));
    // Índice `${lista_precios_id}:${producto_id}` → fila, para lookup O(1) en el batch.
    const preciosPorListaYProducto = new Map(preciosLista.map((p) => [`${p.lista_precios_id}:${p.producto_id}`, p]));

    const resultado: Record<string, any> = {};

    for (const productoId of productoIds) {
      const precioBaseProducto = productoPrecioBase.get(productoId) ?? 0;
      const precioCosto = productoPrecioCosto.get(productoId) ?? 0;

      let precioEncontrado: any = null;
      let listaAplicada: any = null;

      for (const listaId of listaIdsOrdenados) {
        const lista = listas.find((l) => l.id === listaId);
        const precio = preciosPorListaYProducto.get(`${listaId}:${productoId}`);
        if (precio) {
          precioEncontrado = precio;
          listaAplicada = lista;
          break;
        }
        if (lista?.aplica_descuento_general || lista?.aplica_recargo_general) {
          listaAplicada = lista;
          break;
        }
      }

      if (!listaAplicada) {
        const { topeDescuentoPct } = this.calcularTopeDescuento({
          precioFinal: precioBaseProducto,
          precioCosto,
          precioMinimo: 0,
          descuentoMaximoPct: 0,
        });
        resultado[productoId] = {
          productoId,
          precioBase: precioBaseProducto,
          precioFinal: precioBaseProducto,
          listaAplicada: null,
          precioCosto,
          precioMinimo: 0,
          descuentoMaximoPct: 0,
          topeDescuentoPct,
          // Ninguna lista aplica: el precio base del producto no está gobernado por nada.
          precioBaseGobernado: false,
        };
        continue;
      }

      const precioBaseLista = precioEncontrado ? Number(precioEncontrado.precio_base) : 0;
      let precioFinal = precioBaseLista > 0 ? precioBaseLista : precioBaseProducto;

      if (precioEncontrado) {
        const descProducto = Number(precioEncontrado.descuento_porcentaje || 0);
        const recProducto = Number(precioEncontrado.recargo_porcentaje || 0);
        if (descProducto > 0) precioFinal = precioFinal * (1 - descProducto / 100);
        if (recProducto > 0) precioFinal = precioFinal * (1 + recProducto / 100);
      }

      const descGeneral = listaAplicada.aplica_descuento_general ? Number(listaAplicada.descuento_porcentaje || 0) : 0;
      const recGeneral = listaAplicada.aplica_recargo_general ? Number(listaAplicada.recargo_porcentaje || 0) : 0;
      if (descGeneral > 0) precioFinal = precioFinal * (1 - descGeneral / 100);
      if (recGeneral > 0) precioFinal = precioFinal * (1 + recGeneral / 100);

      if (precioEncontrado) {
        const pMin = Number(precioEncontrado.precio_minimo || 0);
        const pMax = Number(precioEncontrado.precio_maximo || 0);
        if (pMin > 0 && precioFinal < pMin) precioFinal = pMin;
        if (pMax > 0 && precioFinal > pMax) precioFinal = pMax;
      }

      if (listaAplicada.redondeo_activo) {
        const decimales = Number(listaAplicada.redondeo_decimales || 0);
        const factor = Math.pow(10, decimales);
        switch (listaAplicada.redondeo_tipo) {
          case 'superior':
            precioFinal = Math.ceil(precioFinal * factor) / factor;
            break;
          case 'inferior':
            precioFinal = Math.floor(precioFinal * factor) / factor;
            break;
          default:
            precioFinal = Math.round(precioFinal * factor) / factor;
            break;
        }
      }

      precioFinal = Math.max(precioFinal, 0);

      const precioMinimo = Number(precioEncontrado?.precio_minimo || 0);
      const descuentoMaximoPct = Number(precioEncontrado?.descuento_maximo_pct || 0);
      const { topeDescuentoPct } = this.calcularTopeDescuento({
        precioFinal,
        precioCosto,
        precioMinimo,
        descuentoMaximoPct,
      });

      resultado[productoId] = {
        productoId,
        precioBase: precioBaseLista > 0 ? precioBaseLista : precioBaseProducto,
        precioFinal,
        listaAplicada: { id: listaAplicada.id, codigo: listaAplicada.codigo, nombre: listaAplicada.nombre, moneda: listaAplicada.moneda },
        precioCosto,
        precioMinimo,
        descuentoMaximoPct,
        topeDescuentoPct,
        // true solo cuando la lista REEMPLAZA el precio base del producto (hay una
        // fila en lista_precios_productos con precio_base > 0 para este producto).
        // Un descuento/recargo general de la lista, que se aplica sobre el precio
        // del producto, NO pone esto en true: ahí editar productos.precio sí tiene
        // efecto (proporcional al precio base).
        precioBaseGobernado: precioBaseLista > 0,
      };
    }

    return resultado;
  }

  async crearListaPrecios(datosLista: CrearListaPreciosDto, empresa_id: string, user_id?: string) {
    const codigo = datosLista.codigo?.trim();
    if (!codigo) {
      throw new HttpException(
        {
          success: false,
          message: 'El código de la lista es obligatorio',
        },
        HttpStatus.BAD_REQUEST,
      );
    }

    const existente = await this.prisma.lista_precios.findFirst({
      where: {
        empresa_id,
        codigo,
      },
      select: { id: true, codigo: true },
    });

    if (existente) {
      throw new HttpException(
        {
          success: false,
          message: `Ya existe una lista con el código ${codigo}`,
          data: existente,
        },
        HttpStatus.CONFLICT,
      );
    }

    const nuevaLista = await this.prisma.lista_precios.create({
      data: {
        empresa_id,
        codigo,
        nombre: datosLista.nombre,
        descripcion: datosLista.descripcion,
        tipo_aplicacion: datosLista.tipoAplicacion || 'general',
        moneda: datosLista.moneda || 'PYG',
        prioridad: datosLista.prioridad || 1,
        fecha_inicio: datosLista.fechaInicio ? new Date(datosLista.fechaInicio) : new Date(),
        fecha_fin: datosLista.fechaFin ? new Date(datosLista.fechaFin) : null,
        aplica_descuento_general: datosLista.aplicaDescuentoGeneral || false,
        descuento_porcentaje: datosLista.descuentoPorcentaje || 0,
        aplica_recargo_general: datosLista.aplicaRecargoGeneral || false,
        recargo_porcentaje: datosLista.recargoPorcentaje || 0,
        redondeo_activo: datosLista.redondeoActivo || false,
        redondeo_tipo: datosLista.redondeoTipo || 'normal',
        redondeo_decimales: datosLista.redondeoDecimales || 0,
        activa: true,
        usuario_creacion: 'sistema',
        fecha_creacion: new Date(),
      },
    });

    this.logger.log(`Lista de precios creada: ${nuevaLista.nombre} (${nuevaLista.id})`, 'ListaPreciosService');
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'CREATE',
      entity_type: 'lista_precios',
      entity_id: nuevaLista.id,
      descripcion: `Lista de precios creada: ${nuevaLista.nombre} | Código: ${nuevaLista.codigo}`,
      new_value: {
        nombre: nuevaLista.nombre,
        codigo: nuevaLista.codigo,
        tipo_aplicacion: nuevaLista.tipo_aplicacion,
        moneda: nuevaLista.moneda,
      },
    });
    return nuevaLista;
  }

  async actualizarPreciosProductos(listaId: string, productos: any[], user_id?: string, empresa_id?: string) {
    const resultados: any[] = [];

    for (const producto of productos) {
      const existente = await this.prisma.lista_precios_productos.findFirst({
        where: {
          lista_precios_id: listaId,
          producto_id: producto.productoId,
        },
      });

      if (existente) {
        const actualizado = await this.prisma.lista_precios_productos.update({
          where: { id: existente.id },
          data: {
            precio_base: producto.precioBase,
            descuento_porcentaje: producto.descuentoPorcentaje || 0,
            recargo_porcentaje: producto.recargoPorcentaje || 0,
            precio_minimo: producto.precioMinimo,
            precio_maximo: producto.precioMaximo,
            descuento_maximo_pct: producto.descuentoMaximoPct,
            permite_cuotas: producto.permiteCuotas !== undefined ? producto.permiteCuotas : true,
            cuotas_maximas: producto.cuotasMaximas || 12,
            cuota_minima_monto: producto.cuotaMinimaMonto,
            // Edición manual (individual o masiva desde la UI) — deja de ser "de regla".
            origen_precio: 'manual',
            regla_precio_id: null,
            fecha_actualizacion: new Date(),
          },
        });
        resultados.push(actualizado);
      } else {
        const nuevo = await this.prisma.lista_precios_productos.create({
          data: {
            lista_precios_id: listaId,
            producto_id: producto.productoId,
            precio_base: producto.precioBase,
            descuento_porcentaje: producto.descuentoPorcentaje || 0,
            recargo_porcentaje: producto.recargoPorcentaje || 0,
            precio_minimo: producto.precioMinimo,
            precio_maximo: producto.precioMaximo,
            descuento_maximo_pct: producto.descuentoMaximoPct,
            permite_cuotas: producto.permiteCuotas !== undefined ? producto.permiteCuotas : true,
            cuotas_maximas: producto.cuotasMaximas || 12,
            cuota_minima_monto: producto.cuotaMinimaMonto,
            origen_precio: 'manual',
            fecha_creacion: new Date(),
          },
        });
        resultados.push(nuevo);
      }
    }

    if (resultados.length > 0) {
      this.logger.log(
        `Precios actualizados en lista ${listaId}: ${resultados.length} productos`,
        'ListaPreciosService',
      );
      await this.auditService.log({
        empresa_id,
        user_id,
        action: 'UPDATE',
        entity_type: 'lista_precios',
        entity_id: listaId,
        descripcion: `Precios actualizados: ${resultados.length} productos en lista ${listaId}`,
      });
    }
    return resultados;
  }

  async asignarListaPrecios(listaId: string, asignaciones: any[]) {
    const resultados: any[] = [];

    for (const asignacion of asignaciones) {
      const nueva = await this.prisma.lista_precios_asignaciones.create({
        data: {
          lista_precios_id: listaId,
          tipo_entidad: asignacion.tipoEntidad,
          entidad_id: asignacion.entidadId,
          zona_geografica: asignacion.zonaGeografica,
          canal_venta: asignacion.canalVenta,
          fecha_inicio: asignacion.fechaInicio ? new Date(asignacion.fechaInicio) : new Date(),
          fecha_fin: asignacion.fechaFin ? new Date(asignacion.fechaFin) : null,
          activa: true,
          fecha_creacion: new Date(),
        },
      });
      resultados.push(nueva);
    }

    return resultados;
  }

  async obtenerListasEmpresa(empresaId: string, filtros: any = {}, page: number = 1, limit: number = 20) {
    const where: any = { empresa_id: empresaId };

    if (filtros.activa !== undefined) {
      where.activa = filtros.activa;
    }

    if (filtros.tipoAplicacion) {
      where.tipo_aplicacion = filtros.tipoAplicacion;
    }

    if (filtros.moneda) {
      where.moneda = filtros.moneda;
    }

    if (filtros.id) {
      where.id = filtros.id;
    }

    const currentPage = Math.max(1, Number(page) || 1);
    const currentLimit = Math.max(1, Number(limit) || 20);
    const skip = (currentPage - 1) * currentLimit;

    const total = await this.prisma.lista_precios.count({ where });
    const listas = await this.prisma.lista_precios.findMany({
      where,
      include: {
        lista_precios_productos: {
          include: {
            productos: {
              select: {
                descripcion: true,
                cod_producto: true,
              },
            },
          },
        },
        lista_precios_asignaciones: true,
        _count: {
          select: {
            lista_precios_productos: true,
            lista_precios_asignaciones: true,
          },
        },
      },
      orderBy: [{ prioridad: 'asc' }, { fecha_creacion: 'desc' }],
      skip,
      take: currentLimit,
    });

    const data = listas.map((lista) => ({
      ...lista,
      asignaciones: lista.lista_precios_asignaciones,
      ultima_actualizacion: formatDateResponse(lista.fecha_actualizacion || lista.fecha_creacion),
      fecha_actualizacion: formatDateResponse(lista.fecha_actualizacion),
      fecha_inicio: formatDateResponse(lista.fecha_inicio),
      fecha_creacion: formatDateResponse(lista.fecha_creacion),
      fecha_fin: lista.fecha_fin ? formatDateResponse(lista.fecha_fin) : null,
    }));

    return {
      data,
      total,
      page: currentPage,
      limit: currentLimit,
      totalPages: currentLimit ? Math.ceil(total / currentLimit) : 1,
    };
  }

  async obtenerListaPorId(listaId: string) {
    const lista = await this.prisma.lista_precios.findUnique({
      where: { id: listaId },
      include: {
        lista_precios_productos: {
          include: {
            productos: {
              select: {
                descripcion: true,
                cod_producto: true,
              },
            },
          },
        },
        lista_precios_asignaciones: true,
      },
    });

    return lista;
  }

  async duplicarListaPrecios(listaOrigenId: string, nuevosDatos: any, user_id?: string) {
    const listaOrigen = await this.prisma.lista_precios.findUnique({
      where: { id: listaOrigenId },
      include: {
        lista_precios_productos: true,
        lista_precios_asignaciones: true,
      },
    });

    if (!listaOrigen) {
      throw new Error('Lista origen no encontrada');
    }

    const nuevaLista = await this.prisma.lista_precios.create({
      data: {
        empresa_id: listaOrigen.empresa_id,
        codigo: nuevosDatos.codigo,
        nombre: nuevosDatos.nombre,
        descripcion: nuevosDatos.descripcion || listaOrigen.descripcion,
        tipo_aplicacion: listaOrigen.tipo_aplicacion,
        moneda: nuevosDatos.moneda || listaOrigen.moneda,
        prioridad: listaOrigen.prioridad,
        fecha_inicio: new Date(),
        fecha_fin: listaOrigen.fecha_fin,
        aplica_descuento_general: listaOrigen.aplica_descuento_general,
        descuento_porcentaje: listaOrigen.descuento_porcentaje,
        aplica_recargo_general: listaOrigen.aplica_recargo_general,
        recargo_porcentaje: listaOrigen.recargo_porcentaje,
        redondeo_activo: listaOrigen.redondeo_activo,
        redondeo_tipo: listaOrigen.redondeo_tipo,
        redondeo_decimales: listaOrigen.redondeo_decimales,
        activa: true,
        usuario_creacion: 'sistema',
        fecha_creacion: new Date(),
      },
    });

    for (const producto of listaOrigen.lista_precios_productos) {
      await this.prisma.lista_precios_productos.create({
        data: {
          lista_precios_id: nuevaLista.id,
          producto_id: producto.producto_id,
          precio_base: producto.precio_base,
          descuento_porcentaje: producto.descuento_porcentaje,
          recargo_porcentaje: producto.recargo_porcentaje,
          precio_minimo: producto.precio_minimo,
          precio_maximo: producto.precio_maximo,
          permite_cuotas: producto.permite_cuotas,
          cuotas_maximas: producto.cuotas_maximas,
          cuota_minima_monto: producto.cuota_minima_monto,
          fecha_creacion: new Date(),
        },
      });
    }

    this.logger.log(`Lista duplicada: ${nuevaLista.nombre} desde ${listaOrigenId}`, 'ListaPreciosService');
    await this.auditService.log({
      empresa_id: nuevaLista.empresa_id,
      user_id,
      action: 'CREATE',
      entity_type: 'lista_precios',
      entity_id: nuevaLista.id,
      descripcion: `Lista de precios duplicada desde: ${listaOrigenId} | Nueva: ${nuevaLista.nombre}`,
      new_value: { nombre: nuevaLista.nombre, codigo: nuevaLista.codigo, origen_id: listaOrigenId },
    });
    return nuevaLista;
  }

  async aplicarDescuentoMasivo(listaId: string, descuentoPorcentaje: number, filtros: any = {}) {
    const where: any = { lista_precios_id: listaId };

    if (filtros.productoIds && filtros.productoIds.length > 0) {
      where.producto_id = { in: filtros.productoIds };
    }

    const productosActualizados = await this.prisma.lista_precios_productos.updateMany({
      where,
      data: {
        descuento_porcentaje: {
          increment: descuentoPorcentaje,
        },
        fecha_actualizacion: new Date(),
      },
    });

    return {
      productos_afectados: productosActualizados.count,
      descuento_aplicado: descuentoPorcentaje,
    };
  }

  async obtenerMonedasDisponibles(empresaId: string) {
    const monedas = await this.prisma.lista_precios.findMany({
      where: {
        empresa_id: empresaId,
        activa: true,
      },
      select: {
        moneda: true,
      },
      distinct: ['moneda'],
    });

    return monedas.map((item) => item.moneda);
  }

  async obtenerListasPorMoneda(empresaId: string, moneda: string) {
    return await this.prisma.lista_precios.findMany({
      where: {
        empresa_id: empresaId,
        moneda: moneda,
        activa: true,
      },
      include: {
        _count: {
          select: {
            lista_precios_productos: true,
            lista_precios_asignaciones: true,
          },
        },
      },
      orderBy: [{ prioridad: 'asc' }, { fecha_creacion: 'desc' }],
    });
  }

  async cambiarMonedaLista(listaId: string, nuevaMoneda: string) {
    const listaActualizada = await this.prisma.lista_precios.update({
      where: { id: listaId },
      data: {
        moneda: nuevaMoneda,
        fecha_actualizacion: new Date(),
      },
    });

    return listaActualizada;
  }

  async obtenerAsignacionesLista(listaId: string) {
    // 1. Asignaciones explícitas de la tabla lista_precios_asignaciones
    const asignaciones = await this.prisma.lista_precios_asignaciones.findMany({
      where: {
        lista_precios_id: listaId,
        activa: true,
      },
      orderBy: { fecha_creacion: 'desc' },
    });

    const resultado = asignaciones.map((asignacion) => ({
      ...asignacion,
      fecha_inicio: formatDateResponse(asignacion.fecha_inicio),
      fecha_fin: formatDateResponse(asignacion.fecha_fin),
      fecha_creacion: formatDateResponse(asignacion.fecha_creacion),
      origen: 'asignacion', // para distinguir el origen
    }));

    // 2. Clientes con FK directo (lista_precios_id) que NO tienen asignación explícita
    const asignacionEntidadIds = asignaciones
      .filter((a) => a.tipo_entidad === 'cliente' && a.entidad_id)
      .map((a) => a.entidad_id);

    const clientesDirectos = await this.prisma.clientes.findMany({
      where: {
        lista_precios_id: listaId,
        deleted: false,
        id: asignacionEntidadIds.length > 0
          ? { notIn: asignacionEntidadIds }
          : undefined,
      },
      select: {
        id: true,
        cod_cliente: true,
        tipo_cliente: true,
        created_at: true,
        personas: {
          select: {
            razon_social: true,
            ruc: true,
          },
        },
      },
    });

    // Mapear clientes directos al mismo formato de asignación
    for (const cli of clientesDirectos) {
      resultado.push({
        id: `fk_${cli.id}`, // prefijo para distinguir que no es de la tabla asignaciones
        lista_precios_id: listaId,
        tipo_entidad: 'cliente',
        entidad_id: cli.id,
        zona_geografica: null,
        canal_venta: null,
        fecha_inicio: formatDateResponse(cli.created_at),
        fecha_fin: null,
        activa: true,
        fecha_creacion: formatDateResponse(cli.created_at),
        origen: 'cliente_directo',
        cliente_nombre: cli.personas?.razon_social || cli.cod_cliente || '-',
        cliente_ruc: cli.personas?.ruc || null,
      } as any);
    }

    return resultado;
  }

  async eliminarAsignacionLista(listaId: string, asignacionId: string) {
    const resultado = await this.prisma.lista_precios_asignaciones.updateMany({
      where: {
        id: asignacionId,
        lista_precios_id: listaId,
        activa: true,
      },
      data: { activa: false },
    });

    if (!resultado.count) {
      throw new HttpException(
        {
          success: false,
          message: 'Asignación no encontrada o ya desactivada',
        },
        HttpStatus.NOT_FOUND,
      );
    }

    return resultado;
  }

  async obtenerProductosLista(listaId: string, page: number = 1, limit: number = 50) {
    const skip = (page - 1) * limit;

    const [productos, total] = await Promise.all([
      this.prisma.lista_precios_productos.findMany({
        where: {
          lista_precios_id: listaId,
        },
        include: {
          productos: {
            select: {
              id: true,
              cod_producto: true,
              descripcion: true,
              unidad_medida_id: true,
            },
          },
        },
        skip,
        take: limit,
        orderBy: { fecha_creacion: 'desc' },
      }),
      this.prisma.lista_precios_productos.count({
        where: { lista_precios_id: listaId },
      }),
    ]);

    return {
      data: productos,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  async eliminarProductoLista(listaId: string, productoListaId: string, user_id?: string, empresa_id?: string) {
    const resultado = await this.prisma.lista_precios_productos.deleteMany({
      where: {
        id: productoListaId,
        lista_precios_id: listaId,
      },
    });

    if (!resultado.count) {
      throw new HttpException(
        {
          success: false,
          message: 'Producto no encontrado en la lista',
        },
        HttpStatus.NOT_FOUND,
      );
    }

    this.logger.log(`Producto ${productoListaId} eliminado de lista ${listaId}`, 'ListaPreciosService');
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'DELETE',
      entity_type: 'lista_precios_producto',
      entity_id: productoListaId,
      descripcion: `Producto eliminado de lista de precios ${listaId}`,
      old_value: { lista_precios_id: listaId, producto_lista_id: productoListaId },
    });
    return resultado;
  }

  // Actualizar precio de un producto dentro de una lista
  async actualizarProductoLista(
    listaId: string,
    productoListaId: string,
    datos: {
      precio_base?: number;
      descuento_porcentaje?: number;
      recargo_porcentaje?: number;
      precio_minimo?: number;
      precio_maximo?: number;
      descuento_maximo_pct?: number;
      permite_cuotas?: boolean;
      cuotas_maximas?: number;
    },
  ) {
    const registro = await this.prisma.lista_precios_productos.findFirst({
      where: {
        id: productoListaId,
        lista_precios_id: listaId,
      },
    });

    if (!registro) {
      throw new HttpException(
        {
          success: false,
          message: 'Producto no encontrado en la lista',
        },
        HttpStatus.NOT_FOUND,
      );
    }

    // Edición manual desde la UI: si se toca precio_base, el registro deja de
    // ser "de regla" y pasa a "manual" — así un recálculo masivo con
    // preservarOverride=true no lo pisa. Ver docs/plan-motor-precios-rentabilidad.md.
    const tocaPrecio = datos.precio_base !== undefined;

    const actualizado = await this.prisma.lista_precios_productos.update({
      where: { id: productoListaId },
      data: {
        ...datos,
        ...(tocaPrecio && { origen_precio: 'manual', regla_precio_id: null }),
        fecha_actualizacion: new Date(),
      },
      include: {
        productos: {
          select: {
            id: true,
            cod_producto: true,
            descripcion: true,
          },
        },
      },
    });

    return actualizado;
  }

  async actualizarListaPrecios(listaId: string, datosActualizacion: any, user_id?: string, empresa_id?: string) {
    // Mapear camelCase del DTO a snake_case de Prisma
    const {
      tipoAplicacion,
      fechaInicio,
      fechaFin,
      aplicaDescuentoGeneral,
      descuentoPorcentaje,
      aplicaRecargoGeneral,
      recargoPorcentaje,
      redondeoActivo,
      redondeoTipo,
      redondeoDecimales,
      ...rest
    } = datosActualizacion;

    const data: any = { ...rest, fecha_actualizacion: new Date() };
    if (tipoAplicacion !== undefined) data.tipo_aplicacion = tipoAplicacion;
    if (fechaInicio !== undefined) data.fecha_inicio = new Date(fechaInicio);
    if (fechaFin !== undefined) data.fecha_fin = fechaFin ? new Date(fechaFin) : null;
    if (aplicaDescuentoGeneral !== undefined) data.aplica_descuento_general = aplicaDescuentoGeneral;
    if (descuentoPorcentaje !== undefined) data.descuento_porcentaje = descuentoPorcentaje;
    if (aplicaRecargoGeneral !== undefined) data.aplica_recargo_general = aplicaRecargoGeneral;
    if (recargoPorcentaje !== undefined) data.recargo_porcentaje = recargoPorcentaje;
    if (redondeoActivo !== undefined) data.redondeo_activo = redondeoActivo;
    if (redondeoTipo !== undefined) data.redondeo_tipo = redondeoTipo;
    if (redondeoDecimales !== undefined) data.redondeo_decimales = redondeoDecimales;

    const listaActualizada = await this.prisma.lista_precios.update({
      where: { id: listaId },
      data,
    });

    this.logger.log(`Lista de precios actualizada: ${listaActualizada.nombre} (${listaId})`, 'ListaPreciosService');
    await this.auditService.log({
      empresa_id: empresa_id ?? listaActualizada.empresa_id,
      user_id,
      action: 'UPDATE',
      entity_type: 'lista_precios',
      entity_id: listaId,
      descripcion: `Lista de precios actualizada: ${listaActualizada.nombre}`,
      new_value: datosActualizacion,
    });
    return listaActualizada;
  }

  // Obtener todas las listas de precios donde aparece un producto específico
  async obtenerPreciosProducto(productoId: string, empresaId: string) {
    const registros = await this.prisma.lista_precios_productos.findMany({
      where: {
        producto_id: productoId,
        lista_precios: {
          empresa_id: empresaId,
        },
      },
      include: {
        lista_precios: {
          select: {
            id: true,
            codigo: true,
            nombre: true,
            tipo_aplicacion: true,
            moneda: true,
            prioridad: true,
            activa: true,
            fecha_inicio: true,
            fecha_fin: true,
          },
        },
        regla_precio: {
          select: { id: true, nombre: true },
        },
      },
      orderBy: {
        lista_precios: {
          prioridad: 'asc',
        },
      },
    });

    return registros.map((reg) => {
      const precioBase = Number(reg.precio_base);
      const descuento = Number(reg.descuento_porcentaje) || 0;
      const recargo = Number(reg.recargo_porcentaje) || 0;
      const precioFinal = precioBase * (1 - descuento / 100) * (1 + recargo / 100);

      return {
        id: reg.id,
        lista: reg.lista_precios,
        precio_base: precioBase,
        descuento_porcentaje: descuento,
        recargo_porcentaje: recargo,
        precio_final: Math.round(precioFinal * 100) / 100,
        precio_minimo: reg.precio_minimo ? Number(reg.precio_minimo) : null,
        precio_maximo: reg.precio_maximo ? Number(reg.precio_maximo) : null,
        descuento_maximo_pct: reg.descuento_maximo_pct ? Number(reg.descuento_maximo_pct) : null,
        permite_cuotas: reg.permite_cuotas,
        cuotas_maximas: reg.cuotas_maximas,
        fecha_creacion: reg.fecha_creacion,
        // Motor de reglas de precio: de dónde salió este precio_base.
        origen_precio: reg.origen_precio,
        regla_precio: reg.regla_precio,
      };
    });
  }

  async cambiarEstadoLista(listaId: string, activa: boolean, user_id?: string, empresa_id?: string) {
    const listaActualizada = await this.prisma.lista_precios.update({
      where: { id: listaId },
      data: {
        activa,
        fecha_actualizacion: new Date(),
      },
    });

    const action = activa ? 'ACTIVATE' : 'DEACTIVATE';
    this.logger.log(`Lista de precios (${listaId}) ${activa ? 'activada' : 'desactivada'}`, 'ListaPreciosService');
    await this.auditService.log({
      empresa_id: empresa_id ?? listaActualizada.empresa_id,
      user_id,
      action,
      entity_type: 'lista_precios',
      entity_id: listaId,
      descripcion: `Lista de precios ${activa ? 'activada' : 'desactivada'}: ${listaActualizada.nombre}`,
      old_value: { activa: !activa },
      new_value: { activa },
    });
    return listaActualizada;
  }
}
