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

/**
 * Un evento SIFEN (ECAN / EINU / EINO) a la vez por documento: mientras el
 * anterior está Pendiente no se puede mandar otro, ni el mismo reintentado.
 */
describe('MiddlewareSifenService.enviarEvento - eventos duplicados', () => {
  const facturaAprobada = {
    id: 'fac-1',
    empresa_id: 'emp-1',
    estado_sifen: 'Aprobado',
    cdc: 'CDC-1',
    dest: '001',
    dpunexp: '001',
    dnumdoc: '0000001',
    evento_aplicado: null,
    estado_evento: null,
    mensaje_evento: null,
    fecha_evento: null,
  };

  const crearService = (documento: Record<string, unknown>, reservados = 1) => {
    const prisma: any = {
      factura_cab: {
        findFirst: jest.fn().mockResolvedValue(documento),
        updateMany: jest.fn().mockResolvedValue({ count: reservados }),
        update: jest.fn().mockResolvedValue({}),
      },
    };
    const audit = { log: jest.fn().mockResolvedValue(undefined) };
    const service = new MiddlewareSifenService({} as any, prisma, audit as any, {} as any, {} as any, {} as any, {} as any, {} as any, {} as any);
    jest.spyOn(service, 'empresaUsaSifen').mockResolvedValue(true);
    const enviar = jest.spyOn(service as any, 'sendEventoToMiddleware').mockResolvedValue({ ok: true });
    return { service, prisma, enviar };
  };

  it('con un evento Pendiente no manda nada al middleware', async () => {
    const { service, prisma, enviar } = crearService({
      ...facturaAprobada,
      evento_aplicado: 'EINO',
      estado_evento: 'Pendiente',
    });

    const res = await service.enviarEvento('emp-1', 'factura', 'nominacion', 'fac-1', 'motivo', { clienteId: 'cli-1' });

    expect(res.success).toBe(false);
    expect(res.message).toContain('nominación pendiente');
    expect(enviar).not.toHaveBeenCalled();
    expect(prisma.factura_cab.updateMany).not.toHaveBeenCalled();
  });

  it('si otro envío simultáneo ya reservó el documento, éste no llega al middleware', async () => {
    // El findFirst todavía ve el documento libre, pero la reserva atómica no afecta filas.
    const { service, enviar } = crearService(facturaAprobada, 0);

    const res = await service.enviarEvento('emp-1', 'factura', 'nominacion', 'fac-1', 'motivo');

    expect(res.success).toBe(false);
    expect(res.message).toContain('pendiente');
    expect(enviar).not.toHaveBeenCalled();
  });

  it('reserva el documento como Pendiente antes de llamar al middleware', async () => {
    const { service, prisma, enviar } = crearService(facturaAprobada);

    const res = await service.enviarEvento('emp-1', 'factura', 'nominacion', 'fac-1', 'motivo');

    expect(res.success).toBe(true);
    const reserva = prisma.factura_cab.updateMany.mock.calls[0][0];
    expect(reserva.where.OR).toEqual([{ estado_evento: null }, { estado_evento: { not: 'Pendiente' } }]);
    expect(reserva.data).toMatchObject({ evento_aplicado: 'EINO', estado_evento: 'Pendiente' });
    expect(prisma.factura_cab.updateMany.mock.invocationCallOrder[0]).toBeLessThan(enviar.mock.invocationCallOrder[0]);
  });

  it('si el middleware falla, libera la reserva para poder reintentar', async () => {
    const { service, prisma, enviar } = crearService(facturaAprobada);
    enviar.mockRejectedValue(new Error('middleware caído'));

    const res = await service.enviarEvento('emp-1', 'factura', 'nominacion', 'fac-1', 'motivo');

    expect(res.success).toBe(false);
    expect(prisma.factura_cab.update).toHaveBeenCalledWith({
      where: { id: 'fac-1' },
      data: { evento_aplicado: null, estado_evento: null, mensaje_evento: null, fecha_evento: null },
    });
  });

  it('un evento anterior Rechazado no bloquea reintentar', async () => {
    const { service, enviar } = crearService({ ...facturaAprobada, evento_aplicado: 'EINO', estado_evento: 'Rechazado' });

    const res = await service.enviarEvento('emp-1', 'factura', 'nominacion', 'fac-1', 'motivo');

    expect(res.success).toBe(true);
    expect(enviar).toHaveBeenCalledTimes(1);
  });
});
