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

/**
 * Documentos que SIFEN aprobó pero el middleware nunca confirmó: el envío del
 * lote dio timeout, el reenvío volvió 0301 y el lote no se consultó más. La
 * sincronización los confirma por CDC (DOBA 001-001-0002502 y 0002503).
 */
describe('MiddlewareSifenService.sincronizarSifen - rescate por CDC', () => {
  const urlOriginal = envs.middlewareSifenUrl;
  beforeAll(() => {
    (envs as any).middlewareSifenUrl = 'http://middleware.test';
  });
  afterAll(() => {
    (envs as any).middlewareSifenUrl = urlOriginal;
  });

  const hace = (min: number) => new Date(Date.now() - min * 60_000);
  const factura = (id: string, cdc: string, extra: Record<string, unknown> = {}) => ({
    id,
    empresa_id: 'emp-1',
    cdc,
    dest: '001',
    dpunexp: '001',
    dnumdoc: id.slice(-7),
    nro_lote: 'novasispy-1',
    estado_sifen: 'Documento firmado',
    created_at: hace(60),
    updated_at: hace(60),
    ...extra,
  });

  const crear = (trabados: Array<Record<string, unknown>>, respuestaCdc: Array<Record<string, unknown>>) => {
    const prisma: any = {
      factura_cab: {
        // 1ª consulta: pendientes de documento; 2ª: pendientes de evento; 3ª: trabados para el rescate.
        findMany: jest.fn(({ where }) => Promise.resolve(where.estado_evento ? [] : trabados)),
        findFirst: jest.fn().mockResolvedValue(null),
        update: jest.fn().mockResolvedValue({}),
      },
      empresas: { findUnique: jest.fn().mockResolvedValue({ ruc: '80144315' }) },
    };
    const integracion = { integrarFacturaVenta: jest.fn().mockResolvedValue(undefined) };
    const audit = { log: jest.fn() };
    const service = new MiddlewareSifenService(
      {} as any,
      prisma,
      audit as any,
      integracion as any,
      {} as any,
      {} as any,
      {} as any,
      {} as any,
      {} as any,
    );
    jest.spyOn(service, 'login').mockResolvedValue({ token: 't' } as any);
    const fetchSpy = jest.fn<Promise<{ json: () => Promise<unknown> }>, [string, { body?: string }?]>((url) =>
      Promise.resolve({
        json: () =>
          Promise.resolve(
            url.endsWith('/api/consultar-cdc')
              ? { status: 'success', response: respuestaCdc }
              : // El lote sigue diciendo "Documento firmado": no resuelve nada.
                { status: 'success', response: [] },
          ),
      }),
    );
    global.fetch = fetchSpy as any;
    return { service, prisma, integracion, fetchSpy };
  };

  it('aprueba lo que SIFEN tiene (0422), con la fecha de proceso, y genera el asiento', async () => {
    const { service, prisma, integracion, fetchSpy } = crear(
      [factura('fac-0002502', 'CDC-502')],
      [{ cdc: 'CDC-502', cod_respuesta: '0422', fecha_proceso: '2026-10-06 10:54:09', protocolo: '3631792229' }],
    );

    const res = await service.sincronizarSifen('emp-1', 'factura', 'documento');

    const llamadaCdc = fetchSpy.mock.calls.find(([u]) => String(u).endsWith('/api/consultar-cdc'));
    expect(JSON.parse(String(llamadaCdc?.[1]?.body))).toEqual({ ruc: '80144315', documentos: [{ cdc: 'CDC-502' }] });
    expect(prisma.factura_cab.update).toHaveBeenCalledWith({
      where: { id: 'fac-0002502' },
      data: expect.objectContaining({
        estado: 'Aprobado',
        estado_sifen: 'Aprobado',
        fecha_registro_sifen: new Date('2026-10-06T13:54:09.000Z'),
      }),
    });
    expect(integracion.integrarFacturaVenta).toHaveBeenCalledWith('fac-0002502');
    expect(res.actualizados).toBe(1);
  });

  it('un 0420 no rechaza nada: sólo posterga la próxima consulta', async () => {
    const { service, prisma, integracion } = crear(
      [factura('fac-0000002', 'CDC-2')],
      [{ cdc: 'CDC-2', cod_respuesta: '0420' }],
    );

    await service.sincronizarSifen('emp-1', 'factura', 'documento');

    const updates = prisma.factura_cab.update.mock.calls.map(([a]: any[]) => a);
    expect(updates).toEqual([{ where: { id: 'fac-0000002' }, data: { updated_at: expect.any(Date) } }]);
    expect(integracion.integrarFacturaVenta).not.toHaveBeenCalled();
  });

  it('sólo busca trabados de más de 15 minutos y no consultados en ese lapso', async () => {
    // Un pendiente reciente: la sincronización corre y llega al rescate.
    const { service, prisma } = crear([factura('fac-0000009', 'CDC-9', { created_at: hace(5), updated_at: hace(5) })], []);

    await service.sincronizarSifen('emp-1', 'factura', 'documento');

    const where = prisma.factura_cab.findMany.mock.calls.map(([a]: any[]) => a.where).find((w: any) => w.created_at);
    const limite = Date.now() - 15 * 60_000;
    expect(where.created_at.lt.getTime()).toBeLessThanOrEqual(limite + 1000);
    expect(where.AND[1].OR[1].updated_at.lt.getTime()).toBeLessThanOrEqual(limite + 1000);
    // Además de los pendientes, los rechazados por "duplicado" (reenvío de un CDC ya aprobado).
    expect(where.AND[0].OR).toEqual(
      expect.arrayContaining([
        { estado_sifen: 'Rechazado', mensaje_sifen: { contains: 'duplicad', mode: 'insensitive' } },
      ]),
    );
  });

  it('una rechazada por "CDC duplicado" que SIFEN tiene aprobada vuelve a Aprobado, aunque no haya pendientes', async () => {
    const rechazada = factura('fac-0002184', 'CDC-2184', { estado_sifen: 'Rechazado', mensaje_sifen: 'CDC duplicado' });
    const { service, prisma, integracion } = crear([rechazada], [
      { cdc: 'CDC-2184', cod_respuesta: '0422', fecha_proceso: '2026-09-25 17:02:10' },
    ]);
    // Sin pendientes de lote ni de evento: sólo el rescate encuentra la rechazada.
    prisma.factura_cab.findMany.mockImplementation(({ where }: any) =>
      Promise.resolve(where.created_at ? [rechazada] : []),
    );

    const res = await service.sincronizarSifen('emp-1', 'factura', 'documento');

    expect(prisma.factura_cab.update).toHaveBeenCalledWith({
      where: { id: 'fac-0002184' },
      data: expect.objectContaining({ estado: 'Aprobado', estado_sifen: 'Aprobado' }),
    });
    expect(integracion.integrarFacturaVenta).toHaveBeenCalledWith('fac-0002184');
    expect(res.actualizados).toBe(1);
  });
});
