/**
 * Tests for /api/videos/jobs (list), /api/videos/jobs/:id/cancel,
 * /api/videos (list files), and DELETE /api/videos (delete file).
 * Mini-app pattern — routes only, no full webServer boot.
 */

import { describe, it, expect, beforeAll, afterAll, beforeEach } from '@jest/globals';
import express from 'express';
import { createServer } from 'http';
import path from 'path';
import fs from 'fs/promises';
import os from 'os';

const HTTP_STATUS = {
  OK: 200, BAD_REQUEST: 400, NOT_FOUND: 404, SERVICE_UNAVAILABLE: 503, INTERNAL_SERVER_ERROR: 500,
};

function buildApp({ toolsRegistry, agentPool, sessions }) {
  const app = express();

  app.get('/api/videos/jobs', async (_req, res) => {
    try {
      const tool = toolsRegistry?.getTool?.('video-gen');
      if (!tool || typeof tool.getJobsSnapshot !== 'function') {
        return res.json({ success: true, queued: [], active: [], completed: [], limits: {} });
      }
      res.json({ success: true, ...tool.getJobsSnapshot() });
    } catch (error) {
      res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ success: false, error: error.message });
    }
  });

  app.post('/api/videos/jobs/:jobId/cancel', async (req, res) => {
    try {
      const tool = toolsRegistry?.getTool?.('video-gen');
      if (!tool || typeof tool.cancelJob !== 'function') {
        return res.status(HTTP_STATUS.SERVICE_UNAVAILABLE).json({ success: false, error: 'Video tool not available' });
      }
      const result = tool.cancelJob(req.params.jobId);
      if (!result.success) return res.status(HTTP_STATUS.NOT_FOUND).json(result);
      res.json(result);
    } catch (error) {
      res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ success: false, error: error.message });
    }
  });

  app.get('/api/videos', async (req, res) => {
    try {
      if (!agentPool) return res.json({ success: true, videos: [] });
      const requestedAgentId = req.query.agentId || null;
      const agents = await agentPool.getAllAgents();
      const results = [];
      const seen = new Set();
      const VIDEO_EXTS = new Set(['.mp4', '.webm', '.mov', '.gif']);
      for (const agent of (agents instanceof Map ? Array.from(agents.values()) : agents)) {
        if (requestedAgentId && agent.id !== requestedAgentId) continue;
        const workDir = agent.directoryAccess?.workingDirectory || agent.projectDir;
        if (!workDir) continue;
        const videosDir = path.join(workDir, 'videos');
        let entries;
        try { entries = await fs.readdir(videosDir); } catch { continue; }
        for (const name of entries) {
          const ext = path.extname(name).toLowerCase();
          if (!VIDEO_EXTS.has(ext)) continue;
          const abs = path.join(videosDir, name);
          if (seen.has(abs)) continue;
          seen.add(abs);
          let stat;
          try { stat = await fs.stat(abs); } catch { continue; }
          if (!stat.isFile()) continue;
          const anySession = Array.from(sessions.values())[0];
          const sessionId = agent.sessionId || anySession?.sessionId || 'web-session';
          results.push({
            filename: name, agentId: agent.id, agentName: agent.name,
            projectDir: workDir, mtime: stat.mtime.toISOString(), size: stat.size,
            url: `/api/videos/${encodeURIComponent(sessionId)}/${encodeURIComponent(name)}`,
          });
        }
      }
      results.sort((a, b) => b.mtime.localeCompare(a.mtime));
      res.json({ success: true, videos: results });
    } catch (error) {
      res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ success: false, error: error.message });
    }
  });

  app.delete('/api/videos', async (req, res) => {
    try {
      const { agentId, filename } = req.query;
      if (!agentId || !filename) return res.status(HTTP_STATUS.BAD_REQUEST).json({ success: false, error: 'agentId and filename are required' });
      const normalized = path.basename(filename);
      if (normalized !== filename || filename.includes('..')) return res.status(HTTP_STATUS.BAD_REQUEST).json({ success: false, error: 'Invalid filename' });
      const ext = path.extname(normalized).toLowerCase();
      if (!['.mp4', '.webm', '.mov', '.gif'].includes(ext)) return res.status(HTTP_STATUS.BAD_REQUEST).json({ success: false, error: 'Unsupported file extension for video delete' });
      if (!agentPool) return res.status(HTTP_STATUS.SERVICE_UNAVAILABLE).json({ success: false, error: 'Agent pool not available' });
      const agent = await agentPool.getAgent(agentId);
      if (!agent) return res.status(HTTP_STATUS.NOT_FOUND).json({ success: false, error: 'Agent not found' });
      const workDir = agent.directoryAccess?.workingDirectory || agent.projectDir;
      if (!workDir) return res.status(HTTP_STATUS.BAD_REQUEST).json({ success: false, error: 'Agent has no working directory' });
      const target = path.join(workDir, 'videos', normalized);
      await fs.unlink(target);
      res.json({ success: true });
    } catch (error) {
      if (error.code === 'ENOENT') return res.status(HTTP_STATUS.NOT_FOUND).json({ success: false, error: 'Video not found' });
      res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ success: false, error: error.message });
    }
  });

  return app;
}

async function http(url, options = {}) {
  const resp = await globalThis.fetch(url, options);
  const body = resp.headers.get('content-type')?.includes('json') ? await resp.json() : await resp.text();
  return { status: resp.status, body };
}

describe('video jobs routes', () => {
  let server, baseUrl;
  let tool; // fake video tool

  beforeAll(() => {
    tool = {
      _q: [],
      _a: [],
      _c: [],
      getJobsSnapshot() {
        return { queued: this._q, active: this._a, completed: this._c, limits: { queue: 5, concurrency: 2 } };
      },
      cancelJob(jobId) {
        if (this._q.find(j => j.jobId === jobId)) { this._q = this._q.filter(j => j.jobId !== jobId); return { success: true, state: 'queued' }; }
        if (this._a.find(j => j.jobId === jobId)) { this._a = this._a.filter(j => j.jobId !== jobId); return { success: true, state: 'active' }; }
        return { success: false, error: 'Job not found or already terminal' };
      },
    };
    const toolsRegistry = { getTool: (id) => id === 'video-gen' ? tool : null };
    const app = buildApp({ toolsRegistry, agentPool: null, sessions: new Map() });
    server = createServer(app);
    return new Promise(r => server.listen(0, () => { baseUrl = `http://localhost:${server.address().port}`; r(); }));
  });

  afterAll(() => { server?.close(); });

  beforeEach(() => {
    tool._q = []; tool._a = []; tool._c = [];
  });

  it('GET /api/videos/jobs returns snapshot', async () => {
    tool._q = [{ jobId: 'q1', prompt: 'fox' }];
    tool._a = [{ jobId: 'a1', prompt: 'bear' }];
    tool._c = [{ jobId: 'c1', prompt: 'eagle' }];
    const { status, body } = await http(`${baseUrl}/api/videos/jobs`);
    expect(status).toBe(200);
    expect(body.queued).toHaveLength(1);
    expect(body.active).toHaveLength(1);
    expect(body.completed).toHaveLength(1);
    expect(body.limits).toMatchObject({ queue: 5 });
  });

  it('POST cancel removes a queued job', async () => {
    tool._q = [{ jobId: 'q1' }];
    const { status, body } = await http(`${baseUrl}/api/videos/jobs/q1/cancel`, { method: 'POST' });
    expect(status).toBe(200);
    expect(body.success).toBe(true);
    expect(body.state).toBe('queued');
    expect(tool._q).toHaveLength(0);
  });

  it('POST cancel returns 404 for unknown job', async () => {
    const { status, body } = await http(`${baseUrl}/api/videos/jobs/nope/cancel`, { method: 'POST' });
    expect(status).toBe(404);
    expect(body.success).toBe(false);
  });
});

// Library routes are now GalleryService-backed — see galleryService.test.js
// and galleryRoutes.test.js for the thorough coverage. The only difference
// for videos is the file extensions and paths, which galleryService handles
// by delegation; a duplicate integration test here would be redundant.
