import { PresentismoReconciliacionCron } from './presentismo-reconciliacion.cron';

const TZ = 'America/Asuncion';

function createCron() {
  const prisma = {
    rrhh_relojes_marcadores: { findMany: jest.fn() },
  };
  const relojes = { sincronizar: jest.fn() };
  const cron = new PresentismoReconciliacionCron(prisma as any, relojes as any);
  return { cron, prisma, relojes };
}

function fmtEnTz(d: Date) {
  return new Intl.DateTimeFormat('en-CA', {
    timeZone: TZ,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false,
  }).format(d);
}

describe('PresentismoReconciliacionCron', () => {
  it('sincroniza cada reloj Hikvision activo con la ventana del día anterior', async () => {
    const { cron, prisma, relojes } = createCron();
    prisma.rrhh_relojes_marcadores.findMany.mockResolvedValue([
      { id: 'r1', empresa_id: 'e1', nombre: 'Planta 1' },
      { id: 'r2', empresa_id: 'e1', nombre: 'Planta 2' },
    ]);
    relojes.sincronizar.mockResolvedValue({
      insertadas_raw: 3,
      dedup: { marcaciones_limpias_creadas: 2 },
    });

    await cron.reconciliarDiaAnterior();

    // Sólo relojes Hikvision activos por API.
    expect(prisma.rrhh_relojes_marcadores.findMany).toHaveBeenCalledWith({
      where: { tipo_conexion: 'API', protocolo_api: 'HIKVISION_ISAPI', activo: true },
      select: { id: true, empresa_id: true, nombre: true },
    });
    expect(relojes.sincronizar).toHaveBeenCalledTimes(2);

    const [, , opts] = relojes.sincronizar.mock.calls[0];
    // Ventana = día anterior completo en hora local Asunción.
    expect(fmtEnTz(opts.desde)).toMatch(/, 00:00:00$/);
    expect(fmtEnTz(opts.hasta)).toMatch(/, 23:59:59$/);
    // Mismo día local en desde y hasta, ~24h de duración.
    expect(fmtEnTz(opts.desde).split(',')[0]).toBe(fmtEnTz(opts.hasta).split(',')[0]);
    const horas = (opts.hasta.getTime() - opts.desde.getTime()) / 3_600_000;
    expect(horas).toBeGreaterThan(23.9);
    expect(horas).toBeLessThan(24);
  });

  it('una falla por reloj no detiene a los demás', async () => {
    const { cron, prisma, relojes } = createCron();
    prisma.rrhh_relojes_marcadores.findMany.mockResolvedValue([
      { id: 'r1', empresa_id: 'e1', nombre: 'Planta 1' },
      { id: 'r2', empresa_id: 'e1', nombre: 'Planta 2' },
    ]);
    relojes.sincronizar
      .mockRejectedValueOnce(new Error('reloj offline'))
      .mockResolvedValueOnce({ insertadas_raw: 1, dedup: { marcaciones_limpias_creadas: 1 } });

    await expect(cron.reconciliarDiaAnterior()).resolves.toBeUndefined();
    expect(relojes.sincronizar).toHaveBeenCalledTimes(2);
  });

  it('no hace nada si no hay relojes Hikvision', async () => {
    const { cron, prisma, relojes } = createCron();
    prisma.rrhh_relojes_marcadores.findMany.mockResolvedValue([]);

    await cron.reconciliarDiaAnterior();
    expect(relojes.sincronizar).not.toHaveBeenCalled();
  });
});
