import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { createStreamWatchdog } from './streamwatchdog';

describe('createStreamWatchdog', () => {
  beforeEach(() => vi.useFakeTimers());
  afterEach(() => vi.useRealTimers());

  it('fires onDead after the timeout with no activity', () => {
    const onDead = vi.fn();
    const w = createStreamWatchdog({ timeoutMs: 25000, onDead });
    w.reset();
    vi.advanceTimersByTime(24999);
    expect(onDead).not.toHaveBeenCalled();
    vi.advanceTimersByTime(1);
    expect(onDead).toHaveBeenCalledTimes(1);
  });

  it('reset before the timeout re-arms and prevents the fire', () => {
    const onDead = vi.fn();
    const w = createStreamWatchdog({ timeoutMs: 25000, onDead });
    w.reset();
    vi.advanceTimersByTime(20000);
    w.reset(); // a fresh event arrived; the deadline moves out
    vi.advanceTimersByTime(20000);
    expect(onDead).not.toHaveBeenCalled();
    vi.advanceTimersByTime(5000);
    expect(onDead).toHaveBeenCalledTimes(1);
  });

  it('a steady heartbeat keeps it from ever firing', () => {
    const onDead = vi.fn();
    const w = createStreamWatchdog({ timeoutMs: 25000, onDead });
    w.reset();
    // A 10 s server heartbeat resets it well inside the 25 s window, indefinitely.
    for (let i = 0; i < 20; i++) {
      vi.advanceTimersByTime(10000);
      w.reset();
    }
    expect(onDead).not.toHaveBeenCalled();
  });

  it('pause clears the pending timer so it cannot fire', () => {
    const onDead = vi.fn();
    const w = createStreamWatchdog({ timeoutMs: 25000, onDead });
    w.reset();
    w.pause();
    vi.advanceTimersByTime(60000);
    expect(onDead).not.toHaveBeenCalled();
  });

  it('resume re-arms after a pause and then fires on continued silence', () => {
    const onDead = vi.fn();
    const w = createStreamWatchdog({ timeoutMs: 25000, onDead });
    w.reset();
    w.pause();
    vi.advanceTimersByTime(60000); // stayed silent while paused: no fire
    expect(onDead).not.toHaveBeenCalled();
    w.resume();
    vi.advanceTimersByTime(24999);
    expect(onDead).not.toHaveBeenCalled();
    vi.advanceTimersByTime(1);
    expect(onDead).toHaveBeenCalledTimes(1);
  });

  it('clear stops a pending fire and does not re-arm', () => {
    const onDead = vi.fn();
    const w = createStreamWatchdog({ timeoutMs: 25000, onDead });
    w.reset();
    w.clear();
    vi.advanceTimersByTime(60000);
    expect(onDead).not.toHaveBeenCalled();
  });

  it('reset after a pause resumes watching (unpauses)', () => {
    const onDead = vi.fn();
    const w = createStreamWatchdog({ timeoutMs: 25000, onDead });
    w.pause();
    w.reset(); // a reopen after a suspend must start watching again
    vi.advanceTimersByTime(25000);
    expect(onDead).toHaveBeenCalledTimes(1);
  });
});
