import { PlanesCuotasService } from './planes-cuotas.service';

describe('PlanesCuotasService - alcance por categoría (Fase 6)', () => {
  const createService = (prismaOverrides: Record<string, any> = {}) => {
    const prisma = {
      planes_cuotas: {
        findFirst: jest.fn().mockResolvedValue(null),
        updateMany: jest.fn().mockResolvedValue({ count: 0 }),
        create: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'plan-1', ...data })),
        update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'plan-1', ...data })),
      },
      ...prismaOverrides,
    };
    const auditService = { log: jest.fn().mockResolvedValue(undefined) };
    return new PlanesCuotasService(prisma as any, auditService as any);
  };

  it('crearPlan persiste categoriaId y el rango de precio de producto', async () => {
    const service = createService();

    await service.crearPlan(
      {
        empresaId: 'emp-1',
        codigo: 'PLAN-CAT',
        nombre: 'Plan Celulares 12 cuotas',
        tipoCalculo: 'automatico',
        cantidadCuotas: 12,
        intervaloDias: 30,
        tasaInteres: 0,
        tipoInteres: 'frances',
        cuotaInicialRequerida: false,
        activo: true,
        esDefault: false,
        categoriaId: 'cat-celulares',
        precioProductoDesde: 500000,
        precioProductoHasta: 3000000,
      } as any,
      'user-1',
    );

    const dataEnviada = (service as any).prisma.planes_cuotas.create.mock.calls[0][0].data;
    expect(dataEnviada.categoria_id).toBe('cat-celulares');
    expect(dataEnviada.precio_producto_desde).toBe(500000);
    expect(dataEnviada.precio_producto_hasta).toBe(3000000);
  });

  it('crearPlan sin categoría ni rango deja los campos undefined (retrocompatible)', async () => {
    const service = createService();

    await service.crearPlan(
      {
        empresaId: 'emp-1',
        codigo: 'PLAN-GLOBAL',
        nombre: 'Plan 3 cuotas',
        tipoCalculo: 'automatico',
        cantidadCuotas: 3,
        intervaloDias: 30,
        tasaInteres: 0,
        tipoInteres: 'frances',
        cuotaInicialRequerida: false,
        activo: true,
        esDefault: false,
      } as any,
      'user-1',
    );

    const dataEnviada = (service as any).prisma.planes_cuotas.create.mock.calls[0][0].data;
    expect(dataEnviada.categoria_id).toBeUndefined();
    expect(dataEnviada.precio_producto_desde).toBeUndefined();
    expect(dataEnviada.precio_producto_hasta).toBeUndefined();
  });

  it('actualizarPlan solo toca los campos de alcance cuando vienen en el dto', async () => {
    const prisma = {
      planes_cuotas: {
        findFirst: jest.fn().mockResolvedValue({ id: 'plan-1', empresa_id: 'emp-1' }),
        updateMany: jest.fn().mockResolvedValue({ count: 0 }),
        update: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'plan-1', ...data })),
      },
    };
    const service = createService(prisma);

    await service.actualizarPlan('plan-1', 'emp-1', { categoriaId: 'cat-x' } as any, 'user-1');

    const dataEnviada = (service as any).prisma.planes_cuotas.update.mock.calls[0][0].data;
    expect(dataEnviada.categoria_id).toBe('cat-x');
    expect(dataEnviada.precio_producto_desde).toBeUndefined();
    expect(dataEnviada.precio_producto_hasta).toBeUndefined();
  });
});

describe('PlanesCuotasService.obtenerPlanesAplicables - alcance por categoría (Fase 7)', () => {
  const planGlobal = {
    id: 'plan-global',
    nombre: '12 cuotas',
    activo: true,
    categoria_id: null,
    precio_producto_desde: null,
    precio_producto_hasta: null,
  };
  const planCategoria = {
    id: 'plan-cat',
    nombre: '6 cuotas Televisores',
    activo: true,
    categoria_id: 'cat-televisores',
    precio_producto_desde: null,
    precio_producto_hasta: null,
  };

  const createService = (planes: any[], productos: any[]) => {
    const prisma = {
      planes_cuotas: { findMany: jest.fn().mockResolvedValue(planes) },
      productos: { findMany: jest.fn().mockResolvedValue(productos) },
    };
    const auditService = { log: jest.fn().mockResolvedValue(undefined) };
    const service = new PlanesCuotasService(prisma as any, auditService as any);
    return { service, prisma };
  };

  it('un carrito de una sola categoría ve el plan global y el de esa categoría', async () => {
    const producto = { id: 'prod-tv', categoria_id: 'cat-televisores', categoria: { padre_id: null } };
    const { service } = createService([planGlobal, planCategoria], [producto]);

    const resultado = await service.obtenerPlanesAplicables('emp-1', [
      { productoId: 'prod-tv', precioContado: 500000 },
    ]);

    expect(resultado.map((p) => p.id).sort()).toEqual(['plan-cat', 'plan-global']);
  });

  it('un carrito mixto (categoría cubierta + no cubierta) excluye el plan acotado', async () => {
    const productoTv = { id: 'prod-tv', categoria_id: 'cat-televisores', categoria: { padre_id: null } };
    const productoZapatero = { id: 'prod-zap', categoria_id: 'cat-zapateros', categoria: { padre_id: 'cat-muebles' } };
    const { service } = createService([planGlobal, planCategoria], [productoTv, productoZapatero]);

    const resultado = await service.obtenerPlanesAplicables('emp-1', [
      { productoId: 'prod-tv', precioContado: 500000 },
      { productoId: 'prod-zap', precioContado: 100000 },
    ]);

    expect(resultado.map((p) => p.id)).toEqual(['plan-global']);
  });

  it('carrito vacío devuelve todos los planes activos (mismo comportamiento que obtenerPlanes)', async () => {
    const { service, prisma } = createService([planGlobal, planCategoria], []);

    const resultado = await service.obtenerPlanesAplicables('emp-1', []);

    expect(resultado.map((p) => p.id).sort()).toEqual(['plan-cat', 'plan-global']);
    expect(prisma.productos.findMany).not.toHaveBeenCalled();
  });

  it('no resuelve un producto de otra empresa (aislamiento multi-tenant)', async () => {
    // El findMany real (filtrado por empresa_id) no devolvería un producto de otra empresa;
    // acá lo simulamos devolviendo [] para verificar el comportamiento resultante.
    const { service, prisma } = createService([planGlobal, planCategoria], []);

    const resultado = await service.obtenerPlanesAplicables('emp-1', [
      { productoId: 'prod-de-otra-empresa', precioContado: 500000 },
    ]);

    // Producto no resuelto para la empresa => ningún plan puede confirmar que cubre ese ítem.
    expect(resultado).toEqual([]);
    expect(prisma.productos.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          id: { in: ['prod-de-otra-empresa'] },
          empresa_id: 'emp-1',
        }),
      }),
    );
  });

  it('un plan con rango de precio que no cubre el carrito actual queda excluido (filtro de precio a nivel de obtenerPlanesAplicables, no solo del util)', async () => {
    const planRangoAlto = {
      id: 'plan-rango-alto',
      nombre: 'Financiado electro caro',
      activo: true,
      categoria_id: null,
      precio_producto_desde: 5000000,
      precio_producto_hasta: 10000000,
    };
    const producto = { id: 'prod-tv', categoria_id: 'cat-televisores', categoria: { padre_id: null } };
    const { service } = createService([planGlobal, planRangoAlto], [producto]);

    // El precio del carrito (500.000) queda por debajo del rango del plan (5M-10M).
    const resultado = await service.obtenerPlanesAplicables('emp-1', [
      { productoId: 'prod-tv', precioContado: 500000 },
    ]);

    expect(resultado.map((p) => p.id)).toEqual(['plan-global']);
  });

  it('un plan inactivo no aparece en el resultado (activo:true se filtra en obtenerPlanes, llamado internamente)', async () => {
    const planInactivo = {
      id: 'plan-inactivo',
      nombre: 'Plan viejo dado de baja',
      activo: false,
      categoria_id: null,
      precio_producto_desde: null,
      precio_producto_hasta: null,
    };
    const producto = { id: 'prod-tv', categoria_id: 'cat-televisores', categoria: { padre_id: null } };
    // El mock de `planes_cuotas.findMany` no aplica el `where` de verdad — para
    // representar fielmente lo que Prisma real devolvería con `where: { activo:
    // true }`, la lista mockeada NO incluye `planInactivo`. Se verifica además,
    // con el `toHaveBeenCalledWith` de abajo, que `obtenerPlanesAplicables`
    // efectivamente pide `activo: true` (delegado en `obtenerPlanes`) — así la
    // ausencia del plan inactivo en el resultado no es un accidente del mock.
    const { service, prisma } = createService([planGlobal, planCategoria], [producto]);

    const resultado = await service.obtenerPlanesAplicables('emp-1', [
      { productoId: 'prod-tv', precioContado: 500000 },
    ]);

    expect(prisma.planes_cuotas.findMany).toHaveBeenCalledWith(
      expect.objectContaining({ where: expect.objectContaining({ activo: true }) }),
    );
    expect(resultado.map((p) => p.id)).not.toContain(planInactivo.id);
    expect(resultado.map((p) => p.id).sort()).toEqual(['plan-cat', 'plan-global']);
  });

  // ── Filtro por monto del carrito (pendiente §12.9.1) ──────────────────────
  const planConMinimo = {
    ...planGlobal,
    id: 'plan-min-500k',
    nombre: '12 cuotas (mínimo 500.000)',
    monto_minimo: 500000,
    monto_maximo: 5000000,
  };

  it('excluye un plan cuyo monto mínimo no alcanza el carrito', async () => {
    const producto = { id: 'prod-tv', categoria_id: 'cat-televisores', categoria: { padre_id: null } };
    const { service } = createService([planGlobal, planConMinimo], [producto]);

    const resultado = await service.obtenerPlanesAplicables(
      'emp-1',
      [{ productoId: 'prod-tv', precioContado: 487000 }],
      487000, // por debajo del mínimo de 500.000
    );

    expect(resultado.map((p) => p.id)).not.toContain('plan-min-500k');
    expect(resultado.map((p) => p.id)).toEqual(['plan-global']);
  });

  it('excluye un plan cuyo monto máximo supera el carrito', async () => {
    const producto = { id: 'prod-tv', categoria_id: 'cat-televisores', categoria: { padre_id: null } };
    const { service } = createService([planGlobal, planConMinimo], [producto]);

    const resultado = await service.obtenerPlanesAplicables(
      'emp-1',
      [{ productoId: 'prod-tv', precioContado: 9000000 }],
      9000000, // por encima del máximo de 5.000.000
    );

    expect(resultado.map((p) => p.id)).not.toContain('plan-min-500k');
  });

  it('incluye el plan cuando el monto cae dentro del rango', async () => {
    const producto = { id: 'prod-tv', categoria_id: 'cat-televisores', categoria: { padre_id: null } };
    const { service } = createService([planGlobal, planConMinimo], [producto]);

    const resultado = await service.obtenerPlanesAplicables(
      'emp-1',
      [{ productoId: 'prod-tv', precioContado: 1000000 }],
      1000000,
    );

    expect(resultado.map((p) => p.id).sort()).toEqual(['plan-global', 'plan-min-500k']);
  });

  it('sin montoTotal no filtra por monto (retrocompatible con quien no lo manda)', async () => {
    const producto = { id: 'prod-tv', categoria_id: 'cat-televisores', categoria: { padre_id: null } };
    const { service } = createService([planGlobal, planConMinimo], [producto]);

    const resultado = await service.obtenerPlanesAplicables('emp-1', [
      { productoId: 'prod-tv', precioContado: 100 },
    ]);

    expect(resultado.map((p) => p.id).sort()).toEqual(['plan-global', 'plan-min-500k']);
  });

  it('un monto_minimo/monto_maximo en 0 significa "sin límite"', async () => {
    const planSinLimites = { ...planGlobal, id: 'plan-sin-lim', monto_minimo: 0, monto_maximo: 0 };
    const producto = { id: 'prod-tv', categoria_id: 'cat-televisores', categoria: { padre_id: null } };
    const { service } = createService([planSinLimites], [producto]);

    const resultado = await service.obtenerPlanesAplicables(
      'emp-1',
      [{ productoId: 'prod-tv', precioContado: 1 }],
      1,
    );

    expect(resultado.map((p) => p.id)).toEqual(['plan-sin-lim']);
  });

  it('carrito vacío también respeta el filtro por monto', async () => {
    const { service } = createService([planGlobal, planConMinimo], []);

    const resultado = await service.obtenerPlanesAplicables('emp-1', [], 100000);

    expect(resultado.map((p) => p.id)).toEqual(['plan-global']);
  });
});
