import {
  alertaCancelacionRechazada,
  decidirCancelacionRechazada,
  esRechazoTransitorio,
  MAX_REINTENTOS_AUTO,
  motivoDeAuditoria,
} from './cancelacion-rechazada';
import { MiddlewareSifenService } from './middleware-sifen.service';

/**
 * Cancelaciones que SIFEN rechaza con un error propio ("0100 Error Inesperado").
 * DOBA, jun–ago 2026: 4 facturas pidieron cancelación a tiempo, SIFEN respondió
 * eso, nadie se enteró y siguieron como deuda del cliente.
 */
const H = 3600_000;
const aprobada = new Date('2026-07-09T19:34:51Z');

describe('esRechazoTransitorio', () => {
  it('reconoce los errores propios de SIFEN', () => {
    expect(esRechazoTransitorio('0100', 'Error Inesperado')).toBe(true);
    expect(esRechazoTransitorio('3590', 'Fallo al Guardar Eventos')).toBe(true);
    expect(esRechazoTransitorio(null, 'Error Inesperado')).toBe(true);
  });

  it('no reintenta un rechazo por plazo o por datos', () => {
    expect(esRechazoTransitorio('4009', 'Plazo de solicitud de cancelación de una FE extemporáneo')).toBe(false);
    expect(esRechazoTransitorio('4003', 'El CDC ya tiene ese evento')).toBe(false);
  });
});

describe('decidirCancelacionRechazada', () => {
  const base = { tipoDocumento: 'factura' as const, aprobadoEl: aprobada, intentosAuto: 0, transitorio: true };

  it('dentro del plazo y pasado el cuarto de hora, reintenta', () => {
    const d = decidirCancelacionRechazada({ ...base, ultimoEnvio: new Date(aprobada.getTime() + H), ahora: new Date(aprobada.getTime() + 2 * H) });
    expect(d.accion).toBe('reintentar');
    expect(d.venceEl?.toISOString()).toBe(new Date(aprobada.getTime() + 48 * H).toISOString());
  });

  it('recién enviado, espera', () => {
    const envio = new Date(aprobada.getTime() + H);
    const d = decidirCancelacionRechazada({ ...base, ultimoEnvio: envio, ahora: new Date(envio.getTime() + 5 * 60_000) });
    expect(d.accion).toBe('esperar');
  });

  it('agotados los reintentos, alerta', () => {
    const d = decidirCancelacionRechazada({ ...base, intentosAuto: MAX_REINTENTOS_AUTO, ultimoEnvio: aprobada, ahora: new Date(aprobada.getTime() + 3 * H) });
    expect(d).toMatchObject({ accion: 'alertar', motivo: 'reintentos_agotados' });
  });

  it('un rechazo que no es de SIFEN no se reintenta', () => {
    const d = decidirCancelacionRechazada({ ...base, transitorio: false, ultimoEnvio: aprobada, ahora: new Date(aprobada.getTime() + 3 * H) });
    expect(d).toMatchObject({ accion: 'alertar', motivo: 'no_transitorio' });
  });

  it('una factura vence a las 48 h; una NC tiene 168 h', () => {
    const ahora = new Date(aprobada.getTime() + 50 * H);
    expect(decidirCancelacionRechazada({ ...base, ultimoEnvio: aprobada, ahora })).toMatchObject({ accion: 'alertar', motivo: 'vencido' });
    expect(decidirCancelacionRechazada({ ...base, tipoDocumento: 'nota_credito', ultimoEnvio: aprobada, ahora }).accion).toBe('reintentar');
  });

  it('no reintenta pegado al vencimiento', () => {
    const d = decidirCancelacionRechazada({ ...base, ultimoEnvio: aprobada, ahora: new Date(aprobada.getTime() + 48 * H - 5 * 60_000) });
    expect(d).toMatchObject({ accion: 'alertar', motivo: 'vencido' });
  });

  it('sin fecha de aprobación no arriesga un reintento', () => {
    const d = decidirCancelacionRechazada({ ...base, aprobadoEl: null, ultimoEnvio: null, ahora: new Date() });
    expect(d).toMatchObject({ accion: 'alertar', motivo: 'vencido', venceEl: null });
  });
});

describe('alertaCancelacionRechazada', () => {
  const doc = { id: 'fac-1', dest: '001', dpunexp: '001', dnumdoc: '0002168' };
  const venceEl = new Date(aprobada.getTime() + 48 * H);
  const alerta = (decision: any, intentos = 0) =>
    alertaCancelacionRechazada({ empresaId: 'emp-1', tipoDocumento: 'factura', doc, decision, mensajeSifen: 'Error Inesperado', intentosAuto: intentos, sucursalId: null });

  it('mientras se reintenta es media y dice que no hace falta nada', () => {
    const a = alerta({ accion: 'esperar', venceEl }, 2);
    expect(a.criticidad).toBe('media');
    expect(a.titulo).toBe('La cancelación de la factura 001-001-0002168 no se registró en SIFEN');
    expect(a.descripcion).toContain('2 de 6');
    expect(a.datos).toMatchObject({ caso: 'cancelacion_rechazada', evento: 'ECAN', vencido: false });
  });

  it('en plazo, pide reenviar antes del vencimiento', () => {
    const a = alerta({ accion: 'alertar', motivo: 'reintentos_agotados', venceEl }, 6);
    expect(a.criticidad).toBe('alta');
    expect(a.accionSugerida).toContain('Reenviá la cancelación antes del');
  });

  it('vencido, manda a emitir la nota de crédito', () => {
    const a = alerta({ accion: 'alertar', motivo: 'vencido', venceEl });
    expect(a.criticidad).toBe('alta');
    expect(a.accionSugerida).toContain('nota de crédito');
    expect(a.datos).toMatchObject({ vencido: true });
  });
});

describe('motivoDeAuditoria', () => {
  it('prefiere el motivo guardado y si no lo lee de la descripción', () => {
    expect(motivoDeAuditoria([{ descripcion: 'x', new_value: { motivo: 'Error en el precio' } }])).toBe('Error en el precio');
    expect(
      motivoDeAuditoria([{ descripcion: 'Evento SIFEN cancelacion enviado | Código: ECAN | Motivo: Factura duplicada', new_value: {} }]),
    ).toBe('Factura duplicada');
    expect(motivoDeAuditoria([{ descripcion: 'Evento SIFEN cancelacion enviado | Código: ECAN', new_value: {} }])).toBeNull();
  });
});

describe('MiddlewareSifenService.gestionarCancelacionRechazada', () => {
  const doc = {
    id: 'fac-1',
    dest: '001',
    dpunexp: '001',
    dnumdoc: '0002168',
    fecha_evento: new Date(Date.now() - 2 * H),
    fecha_registro_sifen: new Date(Date.now() - 3 * H),
    fecha_firma_sifen: null,
  };

  const crear = (opciones: { totalFactura?: number; totalNc?: number; intentos?: number; reenvio?: boolean } = {}) => {
    const prisma: any = {
      factura_subtotales: { findFirst: jest.fn().mockResolvedValue({ dtotgralope: opciones.totalFactura ?? 540000 }) },
      nota_credito_subtotal: { aggregate: jest.fn().mockResolvedValue({ _sum: { dtotgralope: opciones.totalNc ?? 0 } }) },
      audit_logs: { count: jest.fn().mockResolvedValue(opciones.intentos ?? 0) },
    };
    const audit = { log: jest.fn().mockResolvedValue(undefined) };
    const alertas = { resolver: jest.fn().mockResolvedValue(true), registrar: jest.fn().mockResolvedValue({ id: 'a', creada: true }) };
    const service = new MiddlewareSifenService({} as any, prisma, audit as any, {} as any, {} as any, {} as any, {} as any, alertas as any, {} as any);
    const reenviar = jest
      .spyOn(service, 'reenviarEvento')
      .mockResolvedValue({ success: opciones.reenvio ?? false, numero: '001-001-0002168', message: opciones.reenvio ? 'Aprobado' : 'Error Inesperado' });
    const gestionar = (cod = '0100') =>
      (service as any).gestionarCancelacionRechazada('emp-1', 'factura', doc, cod, 'Error Inesperado', new Map(), []);
    return { service, prisma, audit, alertas, reenviar, gestionar };
  };

  it('error de SIFEN en plazo: la reenvía sola y, si queda, cierra la alerta', async () => {
    const { gestionar, reenviar, alertas, audit } = crear({ reenvio: true });
    await gestionar();
    expect(reenviar).toHaveBeenCalledWith('emp-1', 'factura', 'fac-1', null);
    expect(audit.log).toHaveBeenCalledWith(expect.objectContaining({ entity_type: 'factura_sifen_reintento_cancelacion', entity_id: 'fac-1' }));
    expect(alertas.resolver).toHaveBeenCalled();
    expect(alertas.registrar).not.toHaveBeenCalled();
  });

  it('si el reintento vuelve a fallar, avisa con la alerta', async () => {
    const { gestionar, alertas } = crear({ reenvio: false, intentos: 2 });
    await gestionar();
    expect(alertas.registrar).toHaveBeenCalledWith(expect.objectContaining({ criticidad: 'media', descripcion: expect.stringContaining('3 de 6') }));
  });

  it('agotados los reintentos, alerta alta sin reenviar', async () => {
    const { gestionar, reenviar, alertas } = crear({ intentos: MAX_REINTENTOS_AUTO });
    await gestionar();
    expect(reenviar).not.toHaveBeenCalled();
    expect(alertas.registrar).toHaveBeenCalledWith(expect.objectContaining({ criticidad: 'alta' }));
  });

  it('con notas de crédito aprobadas por el total no hay nada que avisar', async () => {
    const { gestionar, reenviar, alertas } = crear({ totalNc: 540000 });
    await gestionar();
    expect(reenviar).not.toHaveBeenCalled();
    expect(alertas.registrar).not.toHaveBeenCalled();
    expect(alertas.resolver).toHaveBeenCalledWith('emp-1', 'sifen_evento:factura:fac-1', 'auto');
  });
});

describe('MiddlewareSifenService.reenviarEvento — cancelación rechazada', () => {
  it('reenvía con el motivo original, no con la respuesta de SIFEN', async () => {
    const doc = {
      id: 'fac-1', dest: '001', dpunexp: '001', dnumdoc: '0002168',
      estado_sifen: 'Aprobado', evento_aplicado: 'ECAN', estado_evento: 'Rechazado', mensaje_evento: 'Error Inesperado',
    };
    const prisma: any = {
      factura_cab: { findFirst: jest.fn().mockResolvedValue(doc) },
      audit_logs: {
        findMany: jest.fn().mockResolvedValue([
          { descripcion: 'Evento SIFEN cancelacion enviado | Código: ECAN | Motivo: Mercadería devuelta', new_value: {} },
        ]),
      },
    };
    const service = new MiddlewareSifenService({} as any, prisma, {} as any, {} as any, {} as any, {} as any, {} as any, {} as any, {} as any);
    const enviar = jest.spyOn(service, 'enviarEvento').mockResolvedValue({ success: true, message: 'Aprobado' });

    const r = await service.reenviarEvento('emp-1', 'factura', 'fac-1', null);

    expect(r.success).toBe(true);
    expect(enviar).toHaveBeenCalledWith('emp-1', 'factura', 'cancelacion', 'fac-1', 'Mercadería devuelta');
  });
});
