/**
 * Tests for VideoTool.getJobsSnapshot + cancelJob — the surface the
 * web-UI's `/api/videos/jobs` routes consume. We construct a tool
 * instance, hand-populate its queue/active/completed maps with the
 * same shape the execute() path builds, and verify the snapshot.
 */

import { jest, describe, test, expect, beforeEach } from '@jest/globals';

// Mock the heavy helpers VideoTool pulls in so the import is cheap.
jest.unstable_mockModule('fs', () => ({
  default: { promises: {} },
  promises: {},
}));
jest.unstable_mockModule('fs/promises', () => ({
  default: {},
}));
jest.unstable_mockModule('axios', () => ({
  default: { post: jest.fn(), get: jest.fn() },
}));

const { default: VideoTool } = await import('../videoTool.js');

function makeTool() {
  const tool = new VideoTool({});
  tool.logger = { info() {}, warn() {}, error() {}, debug() {} };
  return tool;
}

function queuedJob(overrides = {}) {
  return {
    jobId: overrides.jobId || `q-${Math.random().toString(36).slice(2, 8)}`,
    agentId: 'agent-A',
    sessionId: 'sess-A',
    prompt: 'a cat playing piano',
    model: 'sora-2',
    width: 1080, height: 1080,
    duration: 5, variants: 1,
    projectDir: '/tmp/x',
    status: 'queued',
    soraJobId: null,
    createdAt: new Date().toISOString(),
    ...overrides,
  };
}

describe('VideoTool.getJobsSnapshot', () => {
  test('returns empty arrays when nothing queued', () => {
    const tool = makeTool();
    const snap = tool.getJobsSnapshot();
    expect(snap.queued).toEqual([]);
    expect(snap.active).toEqual([]);
    expect(snap.completed).toEqual([]);
    expect(snap.limits).toBeDefined();
  });

  test('serialises a queued job to summary shape', () => {
    const tool = makeTool();
    const j = queuedJob({ jobId: 'j1', prompt: 'a fox' });
    tool.queue.push(j);
    const snap = tool.getJobsSnapshot();
    expect(snap.queued).toHaveLength(1);
    const s = snap.queued[0];
    expect(s.jobId).toBe('j1');
    expect(s.agentId).toBe('agent-A');
    expect(s.prompt).toBe('a fox');
    expect(s.status).toBe('queued');
  });

  test('builds a videoUrl for completed jobs with an outputPath', () => {
    const tool = makeTool();
    const j = queuedJob({
      jobId: 'done-1',
      status: 'completed',
      sessionId: 'sess-X',
      outputPath: 'videos/out-42.mp4',
    });
    tool.completedJobs.set(j.jobId, j);
    const snap = tool.getJobsSnapshot();
    expect(snap.completed[0].videoUrl).toBe('/api/videos/sess-X/out-42.mp4');
  });

  test('trims very long prompts to a preview', () => {
    const tool = makeTool();
    const big = 'x'.repeat(500);
    tool.queue.push(queuedJob({ prompt: big }));
    const snap = tool.getJobsSnapshot();
    expect(snap.queued[0].prompt.length).toBe(300);
  });

  test('separates queued / active / completed correctly', () => {
    const tool = makeTool();
    tool.queue.push(queuedJob({ jobId: 'q1' }));
    tool.activeJobs.set('a1', queuedJob({ jobId: 'a1', status: 'active' }));
    tool.completedJobs.set('c1', queuedJob({ jobId: 'c1', status: 'completed' }));
    const snap = tool.getJobsSnapshot();
    expect(snap.queued.map(j => j.jobId)).toEqual(['q1']);
    expect(snap.active.map(j => j.jobId)).toEqual(['a1']);
    expect(snap.completed.map(j => j.jobId)).toEqual(['c1']);
  });
});

describe('VideoTool.cancelJob', () => {
  test('cancels a queued job (removes from queue, adds to completed)', () => {
    const tool = makeTool();
    tool.queue.push(queuedJob({ jobId: 'j1' }));
    const r = tool.cancelJob('j1');
    expect(r.success).toBe(true);
    expect(r.state).toBe('queued');
    expect(tool.queue).toHaveLength(0);
    expect(tool.completedJobs.get('j1').status).toBe('cancelled');
  });

  test('cancels an active job', () => {
    const tool = makeTool();
    tool.activeJobs.set('a1', queuedJob({ jobId: 'a1', status: 'active' }));
    const r = tool.cancelJob('a1');
    expect(r.success).toBe(true);
    expect(r.state).toBe('active');
    expect(tool.activeJobs.has('a1')).toBe(false);
    expect(tool.completedJobs.get('a1').status).toBe('cancelled');
  });

  test('returns not found for unknown jobId', () => {
    const tool = makeTool();
    const r = tool.cancelJob('never-existed');
    expect(r.success).toBe(false);
  });

  test('returns not found for already completed job (terminal)', () => {
    const tool = makeTool();
    tool.completedJobs.set('done', queuedJob({ jobId: 'done', status: 'completed' }));
    const r = tool.cancelJob('done');
    expect(r.success).toBe(false);
  });

  test('clears any poll timer for active jobs on cancel', () => {
    const tool = makeTool();
    tool.activeJobs.set('a1', queuedJob({ jobId: 'a1', status: 'active' }));
    // Simulate a live timer handle.
    const handle = setTimeout(() => {}, 10_000);
    tool.pollTimers.set('a1', handle);
    tool.cancelJob('a1');
    expect(tool.pollTimers.has('a1')).toBe(false);
    clearTimeout(handle);
  });
});
