import fs from 'fs';
import { promises as fsp } from 'fs';
import path from 'path';
import { Router, Request, Response } from 'express';
import { config } from '../config';
import { probeVideo, getVideoCodec, getVideoDuration, runFFmpeg } from '../services/ffmpeg';
import type { HlsConversionStatus } from '@abcvideo/shared';

const router = Router();

interface ConversionJob {
  status: HlsConversionStatus;
  percent: number;
  error?: string;
}

const jobs = new Map<string, ConversionJob>();
const sseClients = new Map<string, Set<Response>>();

function emitProgress(videoId: string, job: ConversionJob): void {
  const clients = sseClients.get(videoId);
  if (!clients) return;
  const data = JSON.stringify({ videoId, percent: job.percent, status: job.status });
  for (const res of clients) {
    res.write(`data: ${data}\n\n`);
  }
}

// GET /api/hls/:videoId/progress  (SSE)
router.get('/:videoId/progress', (req: Request, res: Response) => {
  const { videoId } = req.params;
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*',
  });
  res.write('\n');

  if (!sseClients.has(videoId)) sseClients.set(videoId, new Set());
  sseClients.get(videoId)!.add(res);

  const job = jobs.get(videoId);
  if (job) {
    res.write(`data: ${JSON.stringify({ videoId, percent: job.percent, status: job.status })}\n\n`);
  }

  req.on('close', () => {
    sseClients.get(videoId)?.delete(res);
  });
});

// POST /api/hls/:videoId/convert
router.post('/:videoId/convert', async (req: Request, res: Response) => {
  const { videoId } = req.params;
  const inputPath = path.resolve(config.videosDir, videoId);

  if (!fs.existsSync(inputPath)) {
    res.status(404).json({ error: 'Video not found' });
    return;
  }

  const existing = jobs.get(videoId);
  if (existing?.status === 'converting') {
    res.json({ status: 'converting', message: 'Already converting' });
    return;
  }
  if (existing?.status === 'done') {
    res.json({ status: 'done', message: 'Already converted' });
    return;
  }

  const outputDir = path.join(config.hlsDir, videoId.replace(/\.[^.]+$/, ''));

  // 이미 디스크에 HLS가 생성돼 있으면 재인코딩하지 않고 즉시 완료 처리 (멱등)
  // jobs는 메모리라 서버 재시작 후 idle이 되지만, 산출물은 디스크에 남아있음
  if (fs.existsSync(path.join(outputDir, 'index.m3u8'))) {
    jobs.set(videoId, { status: 'done', percent: 100 });
    res.json({ status: 'done', message: 'Already converted (on disk)' });
    return;
  }

  await fsp.mkdir(outputDir, { recursive: true });

  const job: ConversionJob = { status: 'converting', percent: 0 };
  jobs.set(videoId, job);

  res.json({ status: 'converting', message: 'Conversion started' });

  // Run conversion asynchronously
  (async () => {
    try {
      const probe = await probeVideo(inputPath);
      const duration = getVideoDuration(probe);
      const codec = getVideoCodec(probe);
      const isCopyable = codec === 'h264';

      const ffmpegArgs = isCopyable
        ? [
            '-i', inputPath,
            '-c', 'copy',
            '-f', 'hls',
            '-hls_time', String(config.hlsSegmentTime),
            '-hls_list_size', '0',
            '-hls_playlist_type', 'vod',
            '-hls_segment_filename', path.join(outputDir, 'segment%03d.ts'),
            path.join(outputDir, 'index.m3u8'),
          ]
        : [
            '-i', inputPath,
            '-c:v', 'libx264', '-preset', 'medium', '-crf', '23',
            '-profile:v', 'high', '-level', '4.1',
            '-c:a', 'aac', '-b:a', '128k', '-ar', '48000',
            '-f', 'hls',
            '-hls_time', String(config.hlsSegmentTime),
            '-hls_list_size', '0',
            '-hls_playlist_type', 'vod',
            '-force_key_frames', `expr:gte(t,n_forced*2)`,
            '-hls_segment_filename', path.join(outputDir, 'segment%03d.ts'),
            path.join(outputDir, 'index.m3u8'),
          ];

      await runFFmpeg(ffmpegArgs, duration, (progress) => {
        job.percent = progress.percent;
        emitProgress(videoId, job);
      });

      job.status = 'done';
      job.percent = 100;
      emitProgress(videoId, job);
      console.log(`[hls] conversion complete: ${videoId}`);
    } catch (err) {
      job.status = 'error';
      job.error = String(err);
      emitProgress(videoId, job);
      console.error(`[hls] conversion error for ${videoId}:`, err);
    }
  })();
});

// GET /api/hls/:videoId/status
router.get('/:videoId/status', (req: Request, res: Response) => {
  const { videoId } = req.params;
  const job = jobs.get(videoId);
  if (job) {
    res.json(job);
    return;
  }
  // 메모리에 작업 기록이 없어도 디스크에 산출물이 있으면 done
  const m3u8Path = path.join(config.hlsDir, videoId.replace(/\.[^.]+$/, ''), 'index.m3u8');
  if (fs.existsSync(m3u8Path)) {
    res.json({ status: 'done' as HlsConversionStatus, percent: 100 });
    return;
  }
  res.json({ status: 'idle' as HlsConversionStatus, percent: 0 });
});

// GET /api/hls/:videoId/index.m3u8
router.get('/:videoId/index.m3u8', async (req: Request, res: Response) => {
  const hlsId = req.params.videoId.replace(/\.[^.]+$/, '');
  const filePath = path.join(config.hlsDir, hlsId, 'index.m3u8');
  if (!fs.existsSync(filePath)) {
    res.status(404).json({ error: 'HLS not ready' });
    return;
  }
  res.setHeader('Content-Type', 'application/vnd.apple.mpegurl');
  res.sendFile(filePath);
});

// GET /api/hls/:videoId/:segment  (.ts segments)
router.get('/:videoId/:segment', async (req: Request, res: Response) => {
  const hlsId = req.params.videoId.replace(/\.[^.]+$/, '');
  const filePath = path.join(config.hlsDir, hlsId, req.params.segment);
  if (!fs.existsSync(filePath)) {
    res.status(404).json({ error: 'Segment not found' });
    return;
  }
  res.setHeader('Content-Type', 'video/mp2t');
  res.sendFile(filePath);
});

export default router;
