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

/**
 * La inutilización (EINU) va por rango de numeración, y SIFEN identifica el
 * rango por timbrado: sin `dNumTim` el evento no se puede aplicar. El envío
 * genérico no lo mandaba, y las EINU quedaban pendientes para siempre.
 */
describe('MiddlewareSifenService.enviarEvento — EINU con dNumTim', () => {
  const facturaRechazada = {
    id: 'fac-1',
    empresa_id: 'emp-1',
    estado_sifen: 'Rechazado',
    cdc: 'CDC-1',
    dest: '001',
    dpunexp: '001',
    dnumdoc: '0000001',
    evento_aplicado: null,
    estado_evento: null,
  };

  const crear = ({ deTimbrado = '12558946', empresaTimbrado = '99999999' }: { deTimbrado?: string | null; empresaTimbrado?: string | null } = {}) => {
    const prisma: any = {
      factura_cab: {
        findFirst: jest.fn().mockResolvedValue(facturaRechazada),
        updateMany: jest.fn().mockResolvedValue({ count: 1 }),
        update: jest.fn().mockResolvedValue({}),
      },
      documentos_electronicos: { findFirst: jest.fn().mockResolvedValue(deTimbrado ? { num_timbrado: deTimbrado } : null) },
      empresas_timbrado: { findFirst: jest.fn().mockResolvedValue(empresaTimbrado ? { num_timbrado: empresaTimbrado } : null) },
    };
    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);
    jest.spyOn(service as any, 'esperarYConsultarEstado').mockResolvedValue({ estado: 'Pendiente', mensaje: 'Esperando' });
    const enviar = jest.spyOn(service as any, 'sendEventoToMiddleware').mockResolvedValue({ ok: true });
    return { service, prisma, enviar };
  };

  it('manda el timbrado con el que se emitió el documento', async () => {
    const { service, enviar } = crear();
    await service.enviarEvento('emp-1', 'factura', 'inutilizacion', 'fac-1', 'Documento rechazado');
    const payload = enviar.mock.calls[0][1] as any;
    expect(payload.tipoEve).toBe('EINU');
    expect(payload.comprobantes[0]).toMatchObject({ dNumTim: 12558946, dEst: '001', dPunExp: '001', dNumIn: '0000001', dNumFin: '0000001', cdc: null });
  });

  it('sin registro del DE usa el timbrado vigente de la empresa', async () => {
    const { service, enviar } = crear({ deTimbrado: null });
    await service.enviarEvento('emp-1', 'factura', 'inutilizacion', 'fac-1', 'motivo');
    expect((enviar.mock.calls[0][1] as any).comprobantes[0].dNumTim).toBe(99999999);
  });

  it('sin ningún timbrado no manda nada y lo explica', async () => {
    const { service, enviar, prisma } = crear({ deTimbrado: null, empresaTimbrado: null });
    const res = await service.enviarEvento('emp-1', 'factura', 'inutilizacion', 'fac-1', 'motivo');
    expect(res.success).toBe(false);
    expect(res.message).toMatch(/timbrado/i);
    expect(enviar).not.toHaveBeenCalled();
    expect(prisma.factura_cab.updateMany).not.toHaveBeenCalled();
  });

  it('ECAN no lleva timbrado (va por CDC)', async () => {
    const { service, enviar, prisma } = crear();
    prisma.factura_cab.findFirst.mockResolvedValue({ ...facturaRechazada, estado_sifen: 'Aprobado' });
    jest.spyOn(service as any, 'revertirStockFactura').mockResolvedValue(undefined);
    jest.spyOn(service as any, 'revertirOrdenVentaDesdeFactura').mockResolvedValue(undefined);
    await service.enviarEvento('emp-1', 'factura', 'cancelacion', 'fac-1', 'motivo');
    const c = (enviar.mock.calls[0][1] as any).comprobantes[0];
    expect(c.cdc).toBe('CDC-1');
    expect(c).not.toHaveProperty('dNumTim');
  });
});

describe('MiddlewareSifenService.reenviarInutilizacionesPendientes', () => {
  const doc = (id: string, estado_evento: string) => ({
    id,
    empresa_id: 'emp-1',
    estado_sifen: 'Rechazado',
    evento_aplicado: 'EINU',
    estado_evento,
    mensaje_evento: 'Documento rechazado por SIFEN',
    dest: '001',
    dpunexp: '001',
    dnumdoc: id.replace(/\D/g, '').padStart(7, '0'),
  });

  const crear = () => {
    const docs = [doc('fac-1', 'Pendiente'), doc('fac-2', 'Rechazado')];
    const prisma: any = {
      factura_cab: {
        findMany: jest.fn().mockResolvedValue(docs),
        findFirst: jest.fn(({ where }) => Promise.resolve(docs.find((d) => d.id === where.id) ?? null)),
      },
      nota_credito_cab: { findMany: jest.fn().mockResolvedValue([]) },
      nota_remision_cab: { findMany: jest.fn().mockResolvedValue([]) },
    };
    const service = new MiddlewareSifenService({} as any, prisma, {} as any, {} as any, {} as any, {} as any, {} as any, {} as any, {} as any);
    const liberar = jest.spyOn(service, 'liberarEvento').mockResolvedValue({ success: true, caso: 'no_registrado', message: 'ok' });
    const enviar = jest.spyOn(service, 'enviarEvento').mockResolvedValue({ success: true, message: 'enviado' });
    return { service, liberar, enviar };
  };

  it('libera las pendientes que SIFEN no tiene y reenvía todas con su motivo original', async () => {
    const { service, liberar, enviar } = crear();
    const r = await service.reenviarInutilizacionesPendientes('emp-1', 'usr-1');

    expect(liberar).toHaveBeenCalledTimes(1);
    expect(liberar.mock.calls[0].slice(0, 3)).toEqual(['emp-1', 'factura', 'fac-1']);
    expect(liberar.mock.calls[0][5]).toEqual(expect.arrayContaining(['sin_respuesta', 'en_espera']));
    expect(liberar.mock.calls[0][5]).not.toContain('respondido');
    const where = (service as any).prisma.factura_cab.findMany.mock.calls[0][0].where;
    expect(where.OR[0]).toEqual({ fecha_evento: null });
    expect(where.OR[1].fecha_evento.lt.getTime()).toBeLessThan(Date.now() - 59 * 60 * 1000);
    expect(enviar).toHaveBeenCalledTimes(2);
    expect(enviar).toHaveBeenCalledWith('emp-1', 'factura', 'inutilizacion', 'fac-1', 'Documento rechazado por SIFEN');
    expect(enviar).toHaveBeenCalledWith('emp-1', 'factura', 'inutilizacion', 'fac-2', 'Documento rechazado por SIFEN');
    expect(r.reenviados).toBe(2);
    expect(r.detalles.map((d) => d.numero)).toEqual(['001-001-0000001', '001-001-0000002']);
  });

  it('si SIFEN sí tiene el evento pendiente, no se libera ni se reenvía', async () => {
    const { service, liberar, enviar } = crear();
    liberar.mockResolvedValue({ success: false, caso: 'en_espera', message: 'No se liberó: SIFEN todavía no respondió' });
    const r = await service.reenviarInutilizacionesPendientes('emp-1', 'usr-1');
    expect(enviar).toHaveBeenCalledTimes(1); // sólo la Rechazada
    expect(r.reenviados).toBe(1);
    expect(r.detalles.find((d) => d.id === 'fac-1')?.resultado).toContain('No se liberó');
  });
});

describe('MiddlewareSifenService.reenviarEvento (acción "Reenviar evento" de la alerta)', () => {
  const crear = (doc: Record<string, unknown>) => {
    const prisma: any = { factura_cab: { findFirst: jest.fn().mockResolvedValue(doc) } };
    const service = new MiddlewareSifenService({} as any, prisma, {} as any, {} as any, {} as any, {} as any, {} as any, {} as any, {} as any);
    const liberar = jest.spyOn(service, 'liberarEvento').mockResolvedValue({ success: true, caso: 'incoherente', message: 'ok' });
    const enviar = jest.spyOn(service, 'enviarEvento').mockResolvedValue({ success: true, message: 'Evento enviado' });
    return { service, liberar, enviar };
  };
  const base = { id: 'fac-1', dest: '001', dpunexp: '002', dnumdoc: '0000637', estado_sifen: 'Rechazado', evento_aplicado: 'EINU', estado_evento: 'Pendiente', mensaje_evento: 'Rechazado por SIFEN' };

  it('EINU sobre un documento aprobado: libera lo trabado pero no reenvía, y dice que corresponde cancelar', async () => {
    const { service, liberar, enviar } = crear({ ...base, estado_sifen: 'Aprobado' });
    const r = await service.reenviarEvento('emp-1', 'factura', 'fac-1', 'usr-1');
    expect(liberar).toHaveBeenCalledTimes(1);
    expect(enviar).not.toHaveBeenCalled();
    expect(r.success).toBe(false);
    expect(r.message).toMatch(/001-002-0000637 está Aprobado en SIFEN/);
    expect(r.message).toMatch(/cancelalo \(ECAN\)/);
  });

  it('pendiente liberable: libera y reenvía la inutilización con el motivo original', async () => {
    const { service, liberar, enviar } = crear(base);
    const r = await service.reenviarEvento('emp-1', 'factura', 'fac-1', 'usr-1');
    expect(liberar).toHaveBeenCalledTimes(1);
    expect(enviar).toHaveBeenCalledWith('emp-1', 'factura', 'inutilizacion', 'fac-1', 'Rechazado por SIFEN');
    expect(r).toEqual({ success: true, numero: '001-002-0000637', message: 'Evento enviado' });
  });

  it('si SIFEN tiene el evento, no libera ni reenvía y devuelve el motivo', async () => {
    const { service, liberar, enviar } = crear(base);
    liberar.mockResolvedValue({ success: false, caso: 'en_espera', message: 'No se liberó: Esperando respuesta de SIFEN.' });
    const r = await service.reenviarEvento('emp-1', 'factura', 'fac-1', 'usr-1');
    expect(enviar).not.toHaveBeenCalled();
    expect(r.success).toBe(false);
    expect(r.message).toContain('No se liberó');
  });

  it('evento ya rechazado: reenvía directo sin liberar; ECAN se reenvía como cancelación', async () => {
    const { service, liberar, enviar } = crear({ ...base, estado_sifen: 'Aprobado', evento_aplicado: 'ECAN', estado_evento: 'Rechazado', mensaje_evento: 'Liberado por admin el ayer: Venta anulada' });
    await service.reenviarEvento('emp-1', 'factura', 'fac-1', 'usr-1');
    expect(liberar).not.toHaveBeenCalled();
    expect(enviar).toHaveBeenCalledWith('emp-1', 'factura', 'cancelacion', 'fac-1', 'Venta anulada');
  });

  it('nominación y eventos aprobados no se reenvían', async () => {
    const { service, enviar } = crear({ ...base, evento_aplicado: 'EINO' });
    expect((await service.reenviarEvento('emp-1', 'factura', 'fac-1', 'usr-1')).success).toBe(false);
    const { service: s2, enviar: e2 } = crear({ ...base, estado_evento: 'Aprobado' });
    expect((await s2.reenviarEvento('emp-1', 'factura', 'fac-1', 'usr-1')).message).toContain('aprobado');
    expect(enviar).not.toHaveBeenCalled();
    expect(e2).not.toHaveBeenCalled();
  });
});
