import { PromesasService } from './promesas.service';

describe('PromesasService.cerrarPorCobro', () => {
  const crearService = (promesas: any[], cuotas: any[]) => {
    const prisma: any = {
      promesas_pago: {
        findMany: jest.fn(async ({ where }: any) => {
          const estados: string[] = typeof where.estado === 'string' ? [where.estado] : where.estado.in;
          return promesas.filter((p) => estados.includes(p.estado));
        }),
        update: jest.fn().mockResolvedValue({}),
      },
      factura_cuotas: { findMany: jest.fn().mockResolvedValue(cuotas) },
    };
    const auditService: any = { log: jest.fn().mockResolvedValue(undefined) };
    const service = new PromesasService(prisma, auditService);
    return { service, prisma };
  };

  const params = {
    empresa_id: 'emp-1',
    cliente_id: 'cli-1',
    factura_cab_ids: ['fac-1'],
    cuota_ids: ['cuo-1'],
    recibo_cob_id: 'rec-1',
    monto_cobrado: 200000,
  };

  it('cierra como cumplida una promesa pendiente cuando la cuota queda saldada', async () => {
    const { service, prisma } = crearService(
      [{ id: 'p1', estado: 'pendiente', cuota_id: 'cuo-1', monto_prometido: 200000 }],
      [{ id: 'cuo-1', saldo_pendiente: 0, dmoncuota: 200000 }],
    );
    await service.cerrarPorCobro(params);
    expect(prisma.promesas_pago.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: 'p1' },
        data: expect.objectContaining({ estado: 'cumplida', cumplida_recibo_id: 'rec-1' }),
      }),
    );
  });

  it('cierra también una promesa ya marcada incumplida cuando el cliente paga tarde', async () => {
    const { service, prisma } = crearService(
      [{ id: 'p1', estado: 'incumplida', cuota_id: 'cuo-1', monto_prometido: 200000 }],
      [{ id: 'cuo-1', saldo_pendiente: 0, dmoncuota: 200000 }],
    );
    await service.cerrarPorCobro(params);
    expect(prisma.promesas_pago.update).toHaveBeenCalledWith(
      expect.objectContaining({ where: { id: 'p1' }, data: expect.objectContaining({ estado: 'cumplida' }) }),
    );
  });

  it('marca cumplida_parcial si la cuota recibió cobro pero conserva saldo', async () => {
    const { service, prisma } = crearService(
      [{ id: 'p1', estado: 'incumplida', cuota_id: 'cuo-1', monto_prometido: 200000 }],
      [{ id: 'cuo-1', saldo_pendiente: 50000, dmoncuota: 200000 }],
    );
    await service.cerrarPorCobro(params);
    expect(prisma.promesas_pago.update).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({ estado: 'cumplida_parcial', monto_cumplido: expect.anything() }),
      }),
    );
  });

  it('no toca promesas cuya cuota no recibió cobro en este recibo', async () => {
    const { service, prisma } = crearService(
      [{ id: 'p2', estado: 'incumplida', cuota_id: 'cuo-2', monto_prometido: 200000 }],
      [{ id: 'cuo-2', saldo_pendiente: 200000, dmoncuota: 200000 }],
    );
    await service.cerrarPorCobro(params);
    expect(prisma.promesas_pago.update).not.toHaveBeenCalled();
  });
});

describe('PromesasService.createMultiFactura', () => {
  const crearService = () => {
    const tx: any = {
      promesas_pago: { create: jest.fn(async ({ data }: any) => ({ id: 'p-nueva', ...data })) },
      cob_promesa_factura: { createMany: jest.fn().mockResolvedValue({ count: 0 }) },
    };
    const prisma: any = {
      factura_cab: { findMany: jest.fn().mockResolvedValue([{ id: 'fac-1' }]) },
      $transaction: jest.fn((fn: (tx: any) => Promise<unknown>) => fn(tx)),
    };
    const auditService: any = { log: jest.fn().mockResolvedValue(undefined) };
    return { service: new PromesasService(prisma, auditService), tx };
  };
  const base = { cliente_id: 'cli-1', fecha_prometida: '2026-09-10' };

  it('con varias cuotas crea UNA promesa por el total y un objetivo por cuota', async () => {
    const { service, tx } = crearService();
    await service.createMultiFactura(
      {
        ...base,
        facturas: [
          { factura_cab_id: 'fac-1', cuota_id: 'cuo-1', monto_asignado: 120000 },
          { factura_cab_id: 'fac-1', cuota_id: 'cuo-2', monto_asignado: 80000 },
        ],
      } as any,
      'emp-1',
      'user-1',
    );
    expect(tx.promesas_pago.create).toHaveBeenCalledTimes(1);
    const data = tx.promesas_pago.create.mock.calls[0][0].data;
    expect(Number(data.monto_prometido)).toBe(200000);
    expect(data.cuota_id).toBeNull();
    expect(tx.cob_promesa_factura.createMany.mock.calls[0][0].data).toEqual([
      expect.objectContaining({ promesa_pago_id: 'p-nueva', factura_cab_id: 'fac-1', cuota_id: 'cuo-1' }),
      expect.objectContaining({ promesa_pago_id: 'p-nueva', factura_cab_id: 'fac-1', cuota_id: 'cuo-2' }),
    ]);
  });

  it('con una sola cuota deja cuota_id en la promesa (compatibilidad con cobros por cuota)', async () => {
    const { service, tx } = crearService();
    await service.createMultiFactura(
      { ...base, facturas: [{ factura_cab_id: 'fac-1', cuota_id: 'cuo-1', monto_asignado: 120000 }] } as any,
      'emp-1',
      'user-1',
    );
    expect(tx.promesas_pago.create.mock.calls[0][0].data.cuota_id).toBe('cuo-1');
  });
});

describe('PromesasService.cerrarPorCobro — promesa multi-cuota (acumulado)', () => {
  const crearService = (promesa: any, cobrosAcumulados: number) => {
    const prisma: any = {
      promesas_pago: {
        findMany: jest.fn().mockResolvedValue([promesa]),
        update: jest.fn().mockResolvedValue({}),
      },
      factura_cuotas: { findMany: jest.fn().mockResolvedValue([]) },
      cob_promesa_factura: {
        findMany: jest.fn().mockResolvedValue([
          { promesa_pago_id: promesa.id, factura_cab_id: 'fac-1', cuota_id: 'cuo-1' },
          { promesa_pago_id: promesa.id, factura_cab_id: 'fac-1', cuota_id: 'cuo-2' },
        ]),
      },
      recibo_cobro_detalle: {
        aggregate: jest.fn().mockResolvedValue({ _sum: { monto_pagado: cobrosAcumulados } }),
      },
    };
    const auditService: any = { log: jest.fn().mockResolvedValue(undefined) };
    return { service: new PromesasService(prisma, auditService), prisma };
  };
  const promesa = {
    id: 'p1',
    estado: 'pendiente',
    cuota_id: null,
    monto_prometido: 200000,
    created_at: new Date('2026-09-03T10:00:00Z'),
  };
  const params = {
    empresa_id: 'emp-1',
    cliente_id: 'cli-1',
    factura_cab_ids: ['fac-1'],
    cuota_ids: ['cuo-1'],
    recibo_cob_id: 'rec-2',
    monto_cobrado: 100000,
  };

  it('suma lo cobrado sobre las cuotas objetivo desde la promesa: parcial si no llega al total', async () => {
    const { service, prisma } = crearService(promesa, 150000);
    await service.cerrarPorCobro(params);
    const where = prisma.recibo_cobro_detalle.aggregate.mock.calls[0][0].where;
    expect(where.factura_cuota_id.in).toEqual(['cuo-1', 'cuo-2']);
    expect(where.recibos_cobro.created_at.gte).toEqual(promesa.created_at);
    expect(prisma.promesas_pago.update).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({ estado: 'cumplida_parcial', monto_cumplido: expect.anything() }),
      }),
    );
    expect(Number(prisma.promesas_pago.update.mock.calls[0][0].data.monto_cumplido)).toBe(150000);
  });

  it('pasa de parcial a cumplida cuando el acumulado alcanza el monto prometido', async () => {
    const { service, prisma } = crearService({ ...promesa, estado: 'cumplida_parcial' }, 200000);
    await service.cerrarPorCobro(params);
    expect(prisma.promesas_pago.update.mock.calls[0][0].data.estado).toBe('cumplida');
  });
});
