import { PreciosEngineService } from './precios-engine.service';

const reglaBase = {
  id: 'r-global',
  nombre: 'Global +40%',
  prioridad: 1,
  moneda: 'PYG',
  costo_desde: 0,
  costo_hasta: null,
  metodo: 'margen_sobre_costo',
  valor: 40,
  redondeo_activo: false,
  redondeo_tipo: 'superior',
  redondeo_multiplo: 1,
  lista_precios_id: 'lista-1',
  alcance: 'global',
  categoria_id: null,
};

describe('PreciosEngineService.calcularPrecio - alcance por categoría (Fase 6)', () => {
  const createService = (producto: any, candidatas: any[]) => {
    const findManyMock = jest.fn().mockResolvedValue(candidatas);
    const prisma = {
      productos: { findFirst: jest.fn().mockResolvedValue(producto) },
      lista_precios: { findFirst: jest.fn().mockResolvedValue({ moneda: 'PYG' }) },
      reglas_precio: { findMany: findManyMock },
    };
    const auditService = { log: jest.fn().mockResolvedValue(undefined) };
    const service = new PreciosEngineService(prisma as any, auditService as any);
    return { service, findManyMock };
  };

  it('una regla de categoría específica gana sobre una regla global para el mismo costo', async () => {
    const producto = {
      precio_costo: 500000,
      categoria_id: 'cat-hijo',
      marca_id: null,
      empresa_id: 'emp-1',
      categoria: { padre_id: 'cat-padre' },
    };
    const reglaCategoriaEspecifica = {
      ...reglaBase,
      id: 'r-cat',
      nombre: 'Categoría específica +25%',
      valor: 25,
      alcance: 'categoria',
      categoria_id: 'cat-hijo',
    };
    const { service } = createService(producto, [reglaBase, reglaCategoriaEspecifica]);

    const resultado = await service.calcularPrecio('prod-1', 'lista-1', 'emp-1');

    expect(resultado.reglaAplicadaId).toBe('r-cat');
    expect(resultado.precioSugerido).toBe(625000); // 500000 * 1.25
  });

  it('una regla de categoría padre gana sobre una regla global para el mismo costo', async () => {
    const producto = {
      precio_costo: 500000,
      categoria_id: 'cat-hijo',
      marca_id: null,
      empresa_id: 'emp-1',
      categoria: { padre_id: 'cat-padre' },
    };
    const reglaPadre = {
      ...reglaBase,
      id: 'r-padre',
      nombre: 'Categoría padre +30%',
      valor: 30,
      alcance: 'categoria',
      categoria_id: 'cat-padre',
    };
    const { service } = createService(producto, [reglaBase, reglaPadre]);

    const resultado = await service.calcularPrecio('prod-1', 'lista-1', 'emp-1');

    expect(resultado.reglaAplicadaId).toBe('r-padre');
    expect(resultado.precioSugerido).toBe(650000); // 500000 * 1.30
  });

  it('la query a reglas_precio incluye alcance categoria (específica y padre) además de global', async () => {
    const producto = {
      precio_costo: 500000,
      categoria_id: 'cat-hijo',
      marca_id: null,
      empresa_id: 'emp-1',
      categoria: { padre_id: 'cat-padre' },
    };
    const { service, findManyMock } = createService(producto, [reglaBase]);

    await service.calcularPrecio('prod-1', 'lista-1', 'emp-1');

    const where = findManyMock.mock.calls[0][0].where;
    expect(where.OR).toEqual(
      expect.arrayContaining([
        { alcance: 'global' },
        { alcance: 'categoria', categoria_id: 'cat-hijo' },
        { alcance: 'categoria', categoria_id: 'cat-padre' },
      ]),
    );
  });

  it('producto sin categoría solo compite con reglas globales', async () => {
    const producto = { precio_costo: 500000, categoria_id: null, marca_id: null, empresa_id: 'emp-1', categoria: null };
    const { service, findManyMock } = createService(producto, [reglaBase]);

    const resultado = await service.calcularPrecio('prod-1', 'lista-1', 'emp-1');

    expect(resultado.reglaAplicadaId).toBe('r-global');
    const where = findManyMock.mock.calls[0][0].where;
    expect(where.OR).toEqual([{ alcance: 'global' }]);
  });

  it('no filtra un producto de otra empresa (aislamiento multi-tenant)', async () => {
    const prisma = {
      productos: {
        findFirst: jest.fn().mockResolvedValue(null), // el producto pertenece a otra empresa
      },
      lista_precios: { findFirst: jest.fn() },
      reglas_precio: { findMany: jest.fn() },
    };
    const auditService = { log: jest.fn().mockResolvedValue(undefined) };
    const service = new PreciosEngineService(prisma as any, auditService as any);

    await expect(service.calcularPrecio('prod-de-otra-empresa', 'lista-1', 'emp-1')).rejects.toThrow(
      'Producto no encontrado',
    );

    expect(prisma.productos.findFirst).toHaveBeenCalledWith(
      expect.objectContaining({ where: expect.objectContaining({ id: 'prod-de-otra-empresa', empresa_id: 'emp-1' }) }),
    );
  });

  it('no filtra una lista de precios de otra empresa (aislamiento multi-tenant)', async () => {
    const producto = { precio_costo: 500000, categoria_id: null, marca_id: null, empresa_id: 'emp-1', categoria: null };
    const prisma = {
      productos: { findFirst: jest.fn().mockResolvedValue(producto) },
      lista_precios: { findFirst: jest.fn().mockResolvedValue(null) }, // la lista pertenece a otra empresa
      reglas_precio: { findMany: jest.fn() },
    };
    const auditService = { log: jest.fn().mockResolvedValue(undefined) };
    const service = new PreciosEngineService(prisma as any, auditService as any);

    await expect(service.calcularPrecio('prod-1', 'lista-de-otra-empresa', 'emp-1')).rejects.toThrow(
      'Lista de precios no encontrada',
    );

    expect(prisma.lista_precios.findFirst).toHaveBeenCalledWith(
      expect.objectContaining({ where: expect.objectContaining({ id: 'lista-de-otra-empresa', empresa_id: 'emp-1' }) }),
    );
  });
});

describe('PreciosEngineService.generarGrillaCuotas - filtro de inclusión por categoría/precio (Fase 6)', () => {
  const createService = (producto: any, planes: any[], filasExistentes: any[] = []) => {
    const prisma = {
      lista_precios_productos: {
        findFirst: jest.fn().mockResolvedValue({ precio_base: 1000000 }),
      },
      lista_precios: { findUnique: jest.fn().mockResolvedValue({ moneda: 'PYG' }) },
      productos: { findUnique: jest.fn().mockResolvedValue(producto) },
      planes_cuotas: { findMany: jest.fn().mockResolvedValue(planes) },
      producto_precio_cuota: {
        findMany: jest.fn().mockResolvedValue(filasExistentes),
        findFirst: jest.fn().mockResolvedValue(null),
        create: jest.fn().mockResolvedValue({}),
        update: jest.fn().mockResolvedValue({}),
        updateMany: jest.fn().mockResolvedValue({ count: filasExistentes.length }),
      },
    };
    const auditService = { log: jest.fn().mockResolvedValue(undefined) };
    const service = new PreciosEngineService(prisma as any, auditService as any);
    return { service, prisma };
  };

  const planSinAlcance = {
    id: 'plan-global',
    nombre: '3 cuotas',
    cantidad_cuotas: 3,
    tasa_interes: 0,
    tipo_interes: 'frances',
    categoria_id: null,
    precio_producto_desde: null,
    precio_producto_hasta: null,
  };

  it('un plan sin categoría ni rango sigue aplicando a cualquier producto (retrocompatible)', async () => {
    const producto = { categoria_id: 'cat-otra', categoria: { padre_id: null } };
    const { service, prisma } = createService(producto, [planSinAlcance]);

    const resultado = await service.generarGrillaCuotas('prod-1', 'lista-1', 'emp-1');

    expect(resultado).toEqual({ generadas: 1, removidas: 0 });
    expect(prisma.producto_precio_cuota.create).toHaveBeenCalledTimes(1);
  });

  it('un plan con categoría específica no aplica a un producto de otra categoría', async () => {
    const planCategoria = { ...planSinAlcance, id: 'plan-cat', categoria_id: 'cat-celulares' };
    const producto = { categoria_id: 'cat-otra', categoria: { padre_id: null } };
    const { service, prisma } = createService(producto, [planCategoria]);

    const resultado = await service.generarGrillaCuotas('prod-1', 'lista-1', 'emp-1');

    expect(resultado.generadas).toBe(0);
    expect(prisma.producto_precio_cuota.create).not.toHaveBeenCalled();
  });

  it('un plan con categoría padre aplica a un producto cuya categoría es hija de esa categoría', async () => {
    const planPadre = { ...planSinAlcance, id: 'plan-padre', categoria_id: 'cat-electro' };
    const producto = { categoria_id: 'cat-heladeras', categoria: { padre_id: 'cat-electro' } };
    const { service, prisma } = createService(producto, [planPadre]);

    const resultado = await service.generarGrillaCuotas('prod-1', 'lista-1', 'emp-1');

    expect(resultado.generadas).toBe(1);
    expect(prisma.producto_precio_cuota.create).toHaveBeenCalledTimes(1);
  });

  it('un plan con rango de precio excluye productos fuera de rango', async () => {
    const planConRango = { ...planSinAlcance, id: 'plan-rango', precio_producto_desde: 2000000 };
    const producto = { categoria_id: null, categoria: null };
    // lista_precios_productos.precio_base mockeado en 1.000.000 (por debajo del desde)
    const { service, prisma } = createService(producto, [planConRango]);

    const resultado = await service.generarGrillaCuotas('prod-1', 'lista-1', 'emp-1');

    expect(resultado.generadas).toBe(0);
    expect(prisma.producto_precio_cuota.create).not.toHaveBeenCalled();
  });

  it('conviven un plan global y uno acotado por categoría para el mismo producto', async () => {
    const planGlobal = { ...planSinAlcance, id: 'plan-global' };
    const planCategoria = { ...planSinAlcance, id: 'plan-cat', nombre: '6 cuotas', cantidad_cuotas: 6, categoria_id: 'cat-celulares' };
    const producto = { categoria_id: 'cat-celulares', categoria: { padre_id: null } };
    const { service, prisma } = createService(producto, [planGlobal, planCategoria]);

    const resultado = await service.generarGrillaCuotas('prod-1', 'lista-1', 'emp-1');

    expect(resultado.generadas).toBe(2);
    expect(prisma.producto_precio_cuota.create).toHaveBeenCalledTimes(2);
  });

  it('desactiva (no borra) una fila existente cuyo plan dejó de matchear, y la reporta como removida', async () => {
    // Escenario: el producto ya tenía una fila de "6 cuotas" generada antes de que
    // alguien le asignara categoria_id a ese plan; ahora el producto no matchea.
    const planCategoria = { ...planSinAlcance, id: 'plan-cat', nombre: '6 cuotas', cantidad_cuotas: 6, categoria_id: 'cat-celulares' };
    const producto = { categoria_id: 'cat-otra', categoria: { padre_id: null } };
    const filaExistente = { id: 'fila-6-cuotas', nombre: '6 cuotas', activo: true };
    const { service, prisma } = createService(producto, [planCategoria], [filaExistente]);

    const resultado = await service.generarGrillaCuotas('prod-1', 'lista-1', 'emp-1');

    expect(resultado).toEqual({ generadas: 0, removidas: 1 });
    expect(prisma.producto_precio_cuota.updateMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ id: { in: ['fila-6-cuotas'] } }),
        data: expect.objectContaining({ activo: false }),
      }),
    );
  });

  it('el barrido NUNCA desactiva una fila con nombre libre (creada por el CRUD manual, no por el motor)', async () => {
    const producto = { categoria_id: null, categoria: null };
    const filaManual = { id: 'fila-manual', nombre: 'PRECIO CONTADO', activo: true };
    const { service, prisma } = createService(producto, [], [filaManual]);

    const resultado = await service.generarGrillaCuotas('prod-1', 'lista-1', 'emp-1');

    expect(resultado).toEqual({ generadas: 0, removidas: 0 });
    expect(prisma.producto_precio_cuota.updateMany).not.toHaveBeenCalled();
  });
});
