import { MiddlewareSifenService } from './middleware-sifen.service';

/**
 * La reversión de una factura anulada vivía implementada tres veces —el job de
 * sincronización, el evento mandado desde la pantalla y `FacturasService.anular`—
 * y ninguna cubría todo: el sync no tocaba cuotas, cuenta corriente ni comisiones;
 * el camino de la pantalla no tocaba comisiones. Resultado real: la factura
 * 001-001-0002374 de DOBA quedó cancelada en SIFEN y viva en el ERP con saldo.
 *
 * Estos tests fijan que `revertirFacturaAnulada` sea el punto único y que
 * efectivamente dispare las 8 reversiones de una factura.
 */
describe('MiddlewareSifenService - reversión unificada de factura anulada', () => {
  const FACTURA = 'fac-1';
  const EMPRESA = 'emp-1';

  const armar = () => {
    const prisma: any = {
      // `sincronizarCuentaCobrar` lee cuotas y la cuenta para recalcular el saldo:
      // sin estos mocks esa reversión fallaba en silencio y el test no la cubría.
      factura_cuotas: {
        updateMany: jest.fn().mockResolvedValue({ count: 2 }),
        findMany: jest.fn().mockResolvedValue([
          { id: 'cuo-1', estado: 'Anulada', monto_cuota: 100, saldo_pendiente: 0 },
        ]),
      },
      cuentas_cobrar: {
        updateMany: jest.fn().mockResolvedValue({ count: 1 }),
        findFirst: jest.fn().mockResolvedValue({ id: 'cc-1', cliente_id: 'cli-1', saldo_pendiente: 100 }),
        update: jest.fn().mockResolvedValue({}),
      },
      clientes: { update: jest.fn().mockResolvedValue({}), findUnique: jest.fn().mockResolvedValue({ id: 'cli-1' }) },
      factura_cab: {
        findUnique: jest.fn().mockResolvedValue({ id: FACTURA, cliente_id: 'cli-1' }),
        findFirst: jest.fn().mockResolvedValue({ id: FACTURA, cliente_id: 'cli-1' }),
        update: jest.fn().mockResolvedValue({}),
      },
      factura_det: { findMany: jest.fn().mockResolvedValue([]) },
      $queryRaw: jest.fn().mockResolvedValue([]),
      $executeRaw: jest.fn().mockResolvedValue(0),
      $transaction: jest.fn((fn: any) => (typeof fn === 'function' ? fn(prisma) : Promise.resolve([]))),
    };
    const contabilidad = { revertirDocumento: jest.fn().mockResolvedValue(undefined) };
    const comisiones = { cancelarComisionesPorFactura: jest.fn().mockResolvedValue(undefined) };
    const reversionCaja = { revertirCajaFactura: jest.fn().mockResolvedValue(undefined) };

    const service = new MiddlewareSifenService(
      {} as any,
      prisma,
      { log: jest.fn().mockResolvedValue(undefined) } as any,
      contabilidad as any,
      comisiones as any,
      {} as any,
      reversionCaja as any,
      {} as any,
      {} as any,
    );
    // Privados con dependencias propias: se aíslan para medir sólo la orquestación.
    jest.spyOn(service as any, 'revertirStockFactura').mockResolvedValue(undefined);
    jest.spyOn(service as any, 'revertirOrdenVentaDesdeFactura').mockResolvedValue(undefined);
    return { service, prisma, contabilidad, comisiones, reversionCaja };
  };

  it('dispara las 8 reversiones de la factura', async () => {
    const { service, prisma, contabilidad, comisiones, reversionCaja } = armar();

    await service.revertirFacturaAnulada(FACTURA, EMPRESA, { motivo: 'test' });

    expect((service as any).revertirOrdenVentaDesdeFactura).toHaveBeenCalledWith(FACTURA, EMPRESA);
    expect(reversionCaja.revertirCajaFactura).toHaveBeenCalledWith(FACTURA, EMPRESA, 'test');
    expect((service as any).revertirStockFactura).toHaveBeenCalledWith(FACTURA, EMPRESA);
    expect(prisma.factura_cuotas.updateMany).toHaveBeenCalled();
    expect(prisma.cuentas_cobrar.updateMany).toHaveBeenCalled();
    expect(contabilidad.revertirDocumento).toHaveBeenCalledWith('factura_cab', FACTURA, null);
    // La que faltaba en los dos caminos de evento SIFEN.
    expect(comisiones.cancelarComisionesPorFactura).toHaveBeenCalledWith(FACTURA, EMPRESA);
    // El saldo de la cuenta se recalcula: si esto no corre, la factura queda
    // anulada pero el cliente le sigue debiendo.
    expect(prisma.cuentas_cobrar.findFirst).toHaveBeenCalled();
  });

  it('anula las cuotas en cualquier grafía de "pendiente", no sólo una', async () => {
    const { service, prisma } = armar();

    await service.revertirFacturaAnulada(FACTURA, EMPRESA, { motivo: 'test' });

    const where = prisma.factura_cuotas.updateMany.mock.calls[0][0].where;
    // En la base conviven 'Pendiente', 'pendiente' y 'parcial': se anula todo lo
    // que no esté pagado, en vez de filtrar por una grafía concreta.
    expect(where.estado.notIn).toEqual(expect.arrayContaining(['pagado', 'Pagado', 'pagada', 'Pagada']));
  });

  it('respeta revertirContabilidad:false cuando el asiento ya lo revirtió el llamador', async () => {
    const { service, contabilidad, comisiones } = armar();

    await service.revertirFacturaAnulada(FACTURA, EMPRESA, { motivo: 'x', revertirContabilidad: false });

    expect(contabilidad.revertirDocumento).not.toHaveBeenCalled();
    // El resto sí tiene que correr igual.
    expect(comisiones.cancelarComisionesPorFactura).toHaveBeenCalled();
  });

  it('una reversión que falla no impide las siguientes', async () => {
    const { service, prisma, comisiones, reversionCaja } = armar();
    reversionCaja.revertirCajaFactura.mockRejectedValue(new Error('caja caída'));

    await expect(service.revertirFacturaAnulada(FACTURA, EMPRESA, { motivo: 'x' })).resolves.toBeUndefined();

    expect(prisma.factura_cuotas.updateMany).toHaveBeenCalled();
    expect(comisiones.cancelarComisionesPorFactura).toHaveBeenCalled();
  });

  it('pasa el usuario humano a contabilidad cuando lo hay', async () => {
    const { service, contabilidad } = armar();

    await service.revertirFacturaAnulada(FACTURA, EMPRESA, { motivo: 'x', usuarioId: 'user-9' });

    expect(contabilidad.revertirDocumento).toHaveBeenCalledWith('factura_cab', FACTURA, 'user-9');
  });
});
