import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import type { Message } from '../../llm/openai-compat.js';
import type { EventLogger, EmitOptions } from '../../progress/event-log.js';
import { createMovementWatchdogs } from './watchdogs.js';

function captureLogger(): EventLogger & { kinds: () => string[]; payloads: unknown[] } {
  const events: Array<{ kind: string; payload: unknown }> = [];
  return {
    payloads: events.map((e) => e.payload),
    kinds: () => events.map((e) => e.kind),
    emit(kind: string, payload: unknown, _opts?: EmitOptions): string {
      events.push({ kind, payload });
      return 'evt';
    },
  };
}

describe('createMovementWatchdogs', () => {
  let ws: string;
  beforeEach(() => { ws = mkdtempSync(join(tmpdir(), 'watchdog-')); });
  afterEach(() => { rmSync(ws, { recursive: true, force: true }); });

  const opts = (over: Partial<{ missionWatchdogEnabled: boolean; missionAware: boolean }> = {}) => ({
    workspacePath: ws,
    missionWatchdogEnabled: false,
    missionAware: true,
    ...over,
  });

  it('does not fire before the iteration threshold', () => {
    const wd = createMovementWatchdogs(opts());
    const messages: Message[] = [];
    wd.maybeFire(4, 'm', messages, captureLogger());
    expect(messages).toHaveLength(0);
  });

  it('fires the checklist nudge once at the threshold', () => {
    const wd = createMovementWatchdogs(opts());
    const messages: Message[] = [];
    const ev = captureLogger();
    wd.maybeFire(5, 'm', messages, ev);
    expect(messages).toHaveLength(1);
    expect(String(messages[0].content)).toContain('checklist watchdog');
    expect(ev.kinds()).toContain('watchdog_fire');
    // second call must not push again
    wd.maybeFire(6, 'm', messages, ev);
    expect(messages).toHaveLength(1);
  });

  it('suppresses the checklist nudge when a checklist file already exists', () => {
    mkdirSync(join(ws, 'logs', 'checklists'), { recursive: true });
    writeFileSync(join(ws, 'logs', 'checklists', 'plan.json'), '{}');
    const wd = createMovementWatchdogs(opts());
    const messages: Message[] = [];
    wd.maybeFire(8, 'm', messages, captureLogger());
    expect(messages).toHaveLength(0);
  });

  it('suppresses the checklist nudge after a checklist tool is used', () => {
    const wd = createMovementWatchdogs(opts());
    wd.markToolUse('CreateChecklist');
    const messages: Message[] = [];
    wd.maybeFire(9, 'm', messages, captureLogger());
    expect(messages).toHaveLength(0);
  });

  it('fires the mission nudge only when enabled and mission is not yet set', () => {
    // Seed a checklist file so the checklist nudge is suppressed and only the
    // mission nudge can fire.
    mkdirSync(join(ws, 'logs', 'checklists'), { recursive: true });
    writeFileSync(join(ws, 'logs', 'checklists', 'p.json'), '{}');
    const wd = createMovementWatchdogs({ workspacePath: ws, missionWatchdogEnabled: true, missionAware: false });
    const messages: Message[] = [];
    wd.maybeFire(5, 'm', messages, captureLogger());
    expect(messages).toHaveLength(1);
    expect(String(messages[0].content)).toContain('mission watchdog');
  });

  it('does not fire the mission nudge when the watchdog is disabled', () => {
    mkdirSync(join(ws, 'logs', 'checklists'), { recursive: true });
    writeFileSync(join(ws, 'logs', 'checklists', 'p.json'), '{}');
    const wd = createMovementWatchdogs({ workspacePath: ws, missionWatchdogEnabled: false, missionAware: false });
    const messages: Message[] = [];
    wd.maybeFire(7, 'm', messages, captureLogger());
    expect(messages).toHaveLength(0);
  });

  it('suppresses the mission nudge after MissionUpdate is used', () => {
    mkdirSync(join(ws, 'logs', 'checklists'), { recursive: true });
    writeFileSync(join(ws, 'logs', 'checklists', 'p.json'), '{}');
    const wd = createMovementWatchdogs({ workspacePath: ws, missionWatchdogEnabled: true, missionAware: false });
    wd.markToolUse('MissionUpdate');
    const messages: Message[] = [];
    wd.maybeFire(6, 'm', messages, captureLogger());
    expect(messages).toHaveLength(0);
  });
});
