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

/**
 * Cuando el evento de cancelación (ECAN) o inutilización (EINU) se manda desde
 * la app, el estado lo resuelve `esperarYConsultarEstado`. Ese camino revertía
 * la orden de venta, el stock y la contabilidad, pero NO la caja: el efectivo de
 * la venta cancelada seguía en la sesión y el cajero terminaba rindiendo por
 * plata que ya no existía.
 *
 * Caso real (producción, 22/09/2026): factura 003-001-0001864 por Gs. 196.500,
 * cancelada a las 20:06 con la caja todavía abierta; el arqueo de cierre de las
 * 20:15 siguió contándola. En 60 días había 18 facturas así.
 */
describe('MiddlewareSifenService — ECAN/EINU revierte la caja', () => {
  const crear = (estadoEvento: string) => {
    const service = Object.create(MiddlewareSifenService.prototype) as any;
    service.logger = { log: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() };
    service.prisma = {
      factura_cab: { update: jest.fn().mockResolvedValue({}) },
      factura_det: { findMany: jest.fn().mockResolvedValue([]) },
      tipo_movimiento_inventario: { findFirst: jest.fn().mockResolvedValue(null) },
      stock_deposito: { upsert: jest.fn() },
      movimientos_inventario: { create: jest.fn() },
      cuentas_cobrar: { updateMany: jest.fn().mockResolvedValue({ count: 1 }) },
      factura_cuotas: { updateMany: jest.fn().mockResolvedValue({ count: 1 }) },
    };
    service.revertirStockFactura = jest.fn().mockResolvedValue(undefined);
    service.consultarEstadoEvento = jest.fn().mockResolvedValue({ estado: estadoEvento, mensaje: 'ok' });
    service.revertirOrdenVentaDesdeFactura = jest.fn().mockResolvedValue(undefined);
    service.contabilidadIntegracion = { revertirDocumento: jest.fn().mockResolvedValue(undefined) };
    service.reversionCaja = { revertirCajaFactura: jest.fn().mockResolvedValue({}) };
    // Desde la unificación este camino también cancela las comisiones: antes
    // una factura cancelada desde la pantalla dejaba viva la del vendedor.
    service.asignacionFacturas = { cancelarComisionesPorFactura: jest.fn().mockResolvedValue(undefined) };
    return service;
  };

  const factura = { cdc: 'CDC', dest: '003', dpunexp: '001', dnumdoc: '0001864' };

  const esperar = (service: any, tipoEvento: 'ECAN' | 'EINU' | 'EINO') =>
    service.esperarYConsultarEstado('emp-1', 'fac-1', factura, tipoEvento);

  it('ECAN aprobado revierte los movimientos de caja de la factura', async () => {
    const service = crear('Aprobado');

    await esperar(service, 'ECAN');

    expect(service.reversionCaja.revertirCajaFactura).toHaveBeenCalledWith(
      'fac-1',
      'emp-1',
      expect.stringContaining('ECAN'),
    );
  }, 15000);

  it('ECAN aprobado también cancela las comisiones del vendedor', async () => {
    const service = crear('Aprobado');

    await esperar(service, 'ECAN');

    expect(service.asignacionFacturas.cancelarComisionesPorFactura).toHaveBeenCalledWith('fac-1', 'emp-1');
  }, 15000);

  it('EINU aprobado también revierte la caja', async () => {
    const service = crear('Aprobado');

    await esperar(service, 'EINU');

    expect(service.reversionCaja.revertirCajaFactura).toHaveBeenCalledWith(
      'fac-1',
      'emp-1',
      expect.stringContaining('EINU'),
    );
  }, 15000);

  it('si SIFEN rechaza el evento no se toca la caja', async () => {
    const service = crear('Rechazado');

    await esperar(service, 'ECAN');

    expect(service.reversionCaja.revertirCajaFactura).not.toHaveBeenCalled();
  }, 15000);

  it('EINO (nominación) no es una cancelación: no revierte caja', async () => {
    const service = crear('Aprobado');

    await esperar(service, 'EINO');

    expect(service.reversionCaja.revertirCajaFactura).not.toHaveBeenCalled();
  }, 15000);

  it('un error al revertir la caja no rompe el evento', async () => {
    const service = crear('Aprobado');
    service.reversionCaja.revertirCajaFactura = jest.fn().mockRejectedValue(new Error('sin sesión'));

    const r = await esperar(service, 'ECAN');

    expect(r.estado).toBe('Aprobado');
    expect(service.logger.error).toHaveBeenCalledWith(
      expect.stringContaining('caja'),
      'MiddlewareSifenService',
    );
  }, 15000);

  it('ECAN aprobado anula las cuentas por cobrar y sus cuotas pendientes', async () => {
    // Sin esto la cartera queda inflada: en producción había 25 cuentas en
    // "pendiente" con la factura ya cancelada por SIFEN.
    const service = crear('Aprobado');

    await esperar(service, 'ECAN');

    expect(service.prisma.cuentas_cobrar.updateMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ factura_venta_id: 'fac-1', empresa_id: 'emp-1' }),
        data: expect.objectContaining({ estado: 'anulada' }),
      }),
    );
    // Mismo valor que usa la anulación normal ('Anulado'). Se filtra por lo que
    // NO se anula (lo pagado) en vez de enumerar los estados vivos: así caen las
    // dos grafías de "pendiente" que conviven en la base y también 'parcial',
    // que con la lista blanca anterior quedaba viva en una factura cancelada.
    const argsCuotas = (service.prisma.factura_cuotas.updateMany as jest.Mock).mock.calls[0][0];
    expect(argsCuotas.where.factura_cab_id).toBe('fac-1');
    expect(argsCuotas.data).toEqual({ estado: 'Anulado' });
    const noAnulados: string[] = argsCuotas.where.estado.notIn;
    for (const vivo of ['pendiente', 'Pendiente', 'parcial']) {
      expect(noAnulados).not.toContain(vivo);
    }
    for (const pagado of ['pagado', 'Pagado', 'pagada', 'Pagada']) {
      expect(noAnulados).toContain(pagado);
    }
  }, 15000);

  it('EINU aprobado también anula las cuentas por cobrar', async () => {
    const service = crear('Aprobado');

    await esperar(service, 'EINU');

    expect(service.prisma.cuentas_cobrar.updateMany).toHaveBeenCalled();
  }, 15000);

  it('evento rechazado no toca las cuentas por cobrar', async () => {
    const service = crear('Rechazado');

    await esperar(service, 'ECAN');

    expect(service.prisma.cuentas_cobrar.updateMany).not.toHaveBeenCalled();
  }, 15000);

  it('usa la reversión de stock compartida, que devuelve los lotes FIFO', async () => {
    // El camino de la app repetía la lógica de stock y se olvidaba de los lotes.
    const service = crear('Aprobado');

    await esperar(service, 'ECAN');

    expect(service.revertirStockFactura).toHaveBeenCalledWith('fac-1', 'emp-1');
    expect(service.prisma.stock_deposito.upsert).not.toHaveBeenCalled();
  }, 15000);
});
