import { Logger } from '@nestjs/common';
import { JobsService } from './jobs.service';
import type { SyncService } from '../sync/sync.service';
import type { LembretesService } from '../lembretes/lembretes.service';

/**
 * Execução dos casos do plano `docs/testes/PP-10-plano.md` que são sobre o
 * **agendador**, não sobre a sincronização em si.
 */
describe('Plano PP-10 — agendador', () => {
  const original = process.env.JOBS_HABILITADOS;

  afterEach(() => {
    if (original === undefined) delete process.env.JOBS_HABILITADOS;
    else process.env.JOBS_HABILITADOS = original;
    jest.restoreAllMocks();
  });

  const montar = () => {
    const sync = { sincronizar: jest.fn().mockResolvedValue({}) };
    const lembretes = { dispararPendentes: jest.fn().mockResolvedValue({}) };
    const service = new JobsService(
      sync as unknown as SyncService,
      lembretes as unknown as LembretesService,
    );
    // O logger do Nest escreve no stdout do teste; silenciar mantém a saída
    // legível sem esconder falha de asserção.
    jest.spyOn(Logger.prototype, 'log').mockImplementation(() => {});
    jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => {});
    jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
    return { service, sync, lembretes };
  };

  it('PP-10-10 com JOBS_HABILITADOS=false nada é disparado', async () => {
    process.env.JOBS_HABILITADOS = 'false';
    const { service, sync, lembretes } = montar();

    await service.sincronizar();
    await service.lembrar();

    // É o que permite rodar e2e e ambiente local sem tráfego para a fonte, e
    // sem o cron competir pelo mesmo banco.
    expect(sync.sincronizar).not.toHaveBeenCalled();
    expect(lembretes.dispararPendentes).not.toHaveBeenCalled();
  });

  it('PP-10-10b habilitado por padrão — ausência da variável não desliga', async () => {
    delete process.env.JOBS_HABILITADOS;
    const { service, sync, lembretes } = montar();

    await service.sincronizar();
    await service.lembrar();

    // Padrão ligado importa: um deploy que esqueça a variável não pode ficar
    // com o prazo fechando na hora errada, em silêncio.
    expect(sync.sincronizar).toHaveBeenCalledTimes(1);
    expect(lembretes.dispararPendentes).toHaveBeenCalledTimes(1);
  });

  it('falha da sincronização não derruba o processo do agendador', async () => {
    delete process.env.JOBS_HABILITADOS;
    const { service, sync } = montar();
    sync.sincronizar.mockRejectedValue(new Error('fonte fora do ar'));

    // Exceção não tratada num `@Cron` sobe como rejeição não capturada e pode
    // derrubar o processo — e aí **todos** os jobs param, não só este.
    await expect(service.sincronizar()).resolves.toBeUndefined();
  });

  it('PP-10-11b ciclo ignorado é registrado, não confundido com sucesso', async () => {
    // A sincronização passou a recusar execução concorrente (07/08). Se isto
    // acontecer sempre, o intervalo do cron está curto demais para o tempo da
    // sincronização — e só o log conta essa história.
    const { service, sync } = montar();
    sync.sincronizar.mockResolvedValue({ ignorada: true, falhou: false });
    const aviso = jest
      .spyOn(Logger.prototype, 'warn')
      .mockImplementation(() => {});

    await service.sincronizar();

    expect(aviso).toHaveBeenCalledWith(
      expect.stringContaining('ainda em andamento'),
    );
  });
});
