const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const { spawn } = require('child_process');
const rateLimit = require('express-rate-limit');
const log = require('../lib/logger');
const { UPLOADS_DIR, RESULTS_DIR } = require('../lib/storage');
const queue = require('../lib/queue');
const ws = require('../lib/ws');

const router = express.Router();

const MAX_FILE_SIZE = 200 * 1024 * 1024;
const MAX_FILE_SIZE_ADMIN = 1024 * 1024 * 1024;

function videoFileFilter(req, file, cb) {
  const valid = ['video/mp4', 'video/webm', 'video/quicktime', 'video/x-msvideo', 'video/x-matroska', 'video/mpeg', 'video/3gpp', 'video/ogg'];
  if (valid.includes(file.mimetype) || file.originalname.match(/\.(mp4|webm|mov|avi|mkv|mpeg|mpg|3gp|ogg|flv|wmv)$/i)) {
    cb(null, true);
  } else {
    cb(new Error('Неподдерживаемый формат видео'));
  }
}

const uploadUser = multer({ dest: UPLOADS_DIR, limits: { fileSize: MAX_FILE_SIZE }, fileFilter: videoFileFilter });
const uploadAdmin = multer({ dest: UPLOADS_DIR, limits: { fileSize: MAX_FILE_SIZE_ADMIN }, fileFilter: videoFileFilter });

function uploadMiddleware(req, res, next) {
  const isAdmin = req.session && req.session.user && req.session.user.role === 'admin';
  const handler = isAdmin ? uploadAdmin.single('video') : uploadUser.single('video');
  handler(req, res, next);
}

const limiter = rateLimit({
  keyGenerator: (req) => (req.session && req.session.user && req.session.user.id) ? "user_" + req.session.user.id : req.ip,
  windowMs: 60000, max: 10, message: { error: 'Слишком много запросов' },
});

// In-memory progress tracking for active FFmpeg processes
const liveProgress = new Map();

function runFFmpeg(args, jobId, duration) {
  return new Promise((resolve, reject) => {
    const proc = spawn('ffmpeg', args, { timeout: 600000 });
    let stderrBuf = '';

    proc.stderr.on('data', (d) => {
      const chunk = d.toString();
      stderrBuf += chunk;
      const timeMatch = chunk.match(/(?:out_time|time)=\s*(\d{2}):(\d{2}):(\d{2})[\.\d]*/);
      if (timeMatch && duration > 0) {
        const secs = parseInt(timeMatch[1]) * 3600 + parseInt(timeMatch[2]) * 60 + parseInt(timeMatch[3]);
        const pct = Math.min(99, Math.round((secs / duration) * 100));
        const live = liveProgress.get(jobId);
        if (live && pct > live.progress) {
          live.progress = pct;
          queue.updateProgress(jobId, pct);
          ws.notify(jobId, { type: "progress", progress: pct });
        }
      }
    });

    proc.on('close', (code) => {
      if (code === 0) resolve();
      else reject(new Error(`ffmpeg exited with code ${code}: ${stderrBuf.slice(-500)}`));
    });

    proc.on('error', reject);
  });
}

function getVideoDuration(filePath) {
  return new Promise((resolve) => {
    const proc = spawn('ffprobe', ['-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', filePath]);
    let out = '';
    proc.stdout.on('data', (d) => { out += d.toString(); });
    proc.on('close', () => {
      try {
        const info = JSON.parse(out);
        const duration = parseFloat(info.format?.duration || '0');
        const streams = info.streams || [];
        const video = streams.find(s => s.codec_type === 'video');
        const audio = streams.find(s => s.codec_type === 'audio');
        resolve({
          duration,
          width: video ? parseInt(video.width) : 0,
          height: video ? parseInt(video.height) : 0,
          codec: video ? video.codec_name : '',
          audioCodec: audio ? audio.codec_name : '',
          bitrate: info.format?.bit_rate ? Math.round(parseInt(info.format.bit_rate) / 1024) : 0,
        });
      } catch {
        resolve({ duration: 0, width: 0, height: 0, codec: '', audioCodec: '', bitrate: 0 });
      }
    });
  });
}

// Page
router.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, '..', 'public', 'video.html'));
});

// Upload and get info
router.post('/upload', limiter, uploadMiddleware, async (req, res) => {
  if (!req.file) return res.status(400).json({ error: 'Файл не загружен' });

  try {
    const info = await getVideoDuration(req.file.path);

    // Create job in SQLite queue
    const jobId = queue.addJob('video', {
      inputPath: req.file.path,
      originalName: req.file.originalname,
      size: req.file.size,
      duration: info.duration,
      info,
    }, req.session.user && req.session.user.id);

    // Schedule cleanup of input file after 30 min
    setTimeout(() => {
      try { fs.existsSync(req.file.path) && fs.unlinkSync(req.file.path); } catch {}
    }, 30 * 60 * 1000);

    log.info(`Video upload: ${req.file.originalname} (${(req.file.size / 1024 / 1024).toFixed(1)}MB, ${info.duration.toFixed(1)}s)`);

    res.json({ jobId, info, originalName: req.file.originalname, size: req.file.size });
  } catch (err) {
    try { fs.unlinkSync(req.file.path); } catch {}
    res.status(500).json({ error: err.message });
  }
});

// Convert
router.post('/convert', limiter, express.json(), async (req, res) => {
  const { jobId, mode, format, quality, startTime, endTime } = req.body;
  const job = queue.getJob(jobId);
  // 404 (not 403) on ownership mismatch — do not leak existence of others' jobs
  if (!job || String(job.user_id) !== String(req.session.user && req.session.user.id)) return res.status(404).json({ error: 'Задача не найдена' });
  if (job.status === 'processing') return res.status(409).json({ error: 'Уже обрабатывается' });

  queue.startJob(jobId);

  const payload = job.payload;
  const ext = { mp4: '.mp4', webm: '.webm', avi: '.avi', mkv: '.mkv', mp3: '.mp3', aac: '.aac', gif: '.gif' };
  const outExt = ext[format] || '.mp4';
  const outFile = path.join(RESULTS_DIR, `${jobId}${outExt}`);

  // Track live progress in memory
  liveProgress.set(jobId, { progress: 0 });

  try {
    let args = ['-y', '-progress', 'pipe:2', '-i', payload.inputPath];

    if (startTime !== undefined && startTime > 0) args.push('-ss', String(startTime));
    if (endTime !== undefined && endTime > 0) args.push('-to', String(endTime));

    switch (mode) {
      case 'convert': {
        if (format === 'mp4') args.push('-c:v', 'libx264', '-preset', 'fast', '-crf', '23', '-c:a', 'aac', '-movflags', '+faststart');
        else if (format === 'webm') args.push('-c:v', 'libvpx', '-b:v', '1M', '-c:a', 'libvorbis');
        else if (format === 'avi') args.push('-c:v', 'libx264', '-preset', 'fast', '-crf', '23', '-c:a', 'aac');
        else if (format === 'mkv') args.push('-c:v', 'libx264', '-preset', 'fast', '-crf', '23', '-c:a', 'aac');
        break;
      }
      case 'compress': {
        const crf = quality === 'high' ? '18' : quality === 'low' ? '28' : '23';
        args.push('-c:v', 'libx264', '-preset', 'fast', '-crf', crf, '-c:a', 'aac', '-b:a', '128k');
        if (format === 'mp4' || !format) args.push('-movflags', '+faststart');
        break;
      }
      case 'audio': {
        args.push('-vn');
        if (format === 'mp3') args.push('-c:a', 'libmp3lame', '-b:a', '192k');
        else args.push('-c:a', 'aac', '-b:a', '192k');
        break;
      }
      case 'gif': {
        const w = Math.min(payload.info.width || 480, 480);
        args.push('-vf', `scale=${w}:-1:flags=lanczos,fps=12`, '-loop', '0');
        break;
      }
      default:
        queue.failJob(jobId, 'Неизвестный режим');
        return res.status(400).json({ error: 'Неизвестный режим' });
    }

    args.push(outFile);

    // Respond immediately, process in background
    res.json({ status: 'processing' });

    runFFmpeg(args, jobId, payload.duration).then(() => {
      const outStat = fs.statSync(outFile);
      const outputSize = outStat.size;
      const savings = payload.size > 0 ? Math.round((1 - outputSize / payload.size) * 100) : 0;
      const downloadUrl = `/video/download/${jobId}${outExt}`;

      queue.finishJob(jobId, { downloadUrl, outputSize, savings });
      ws.notify(jobId, { type: "done", downloadUrl, outputSize, savings });
      liveProgress.delete(jobId);

      // Cleanup output after 30 min
      setTimeout(() => { try { fs.existsSync(outFile) && fs.unlinkSync(outFile); } catch {} }, 30 * 60 * 1000);

      log.info(`Video ${mode}: ${payload.originalName} → ${format} (${(outputSize / 1024 / 1024).toFixed(1)}MB, ${savings}% saved)`);
    }).catch(err => {
      queue.failJob(jobId, err.message.slice(0, 200));
      ws.notify(jobId, { type: 'error', error: err.message.slice(0, 200) });
      liveProgress.delete(jobId);
      log.error('Video convert error', { error: err.message });
    });
  } catch (err) {
    queue.failJob(jobId, err.message);
    liveProgress.delete(jobId);
    log.error('Video convert error', { error: err.message });
    res.status(500).json({ error: 'Ошибка конвертации: ' + err.message.slice(0, 200) });
  }
});

// Progress
router.get('/progress/:jobId', (req, res) => {
  const job = queue.getJob(req.params.jobId);
  if (!job || String(job.user_id) !== String(req.session.user && req.session.user.id)) return res.status(404).json({ error: 'Not found' });

  // Use live progress if available (more up-to-date)
  const live = liveProgress.get(req.params.jobId);
  const progress = live ? live.progress : job.progress;

  const result = { status: job.status, progress };
  if (job.status === 'done' && job.result) {
    result.downloadUrl = job.result.downloadUrl;
    result.outputSize = job.result.outputSize;
    result.savings = job.result.savings;
  }
  if (job.status === 'error') {
    result.error = job.error || 'Ошибка обработки';
  }
  res.json(result);
});

// Download result
router.get('/download/:filename', (req, res) => {
  const filename = path.basename(req.params.filename);
  const filePath = path.join(RESULTS_DIR, filename);
  if (!fs.existsSync(filePath)) return res.status(404).json({ error: 'Файл не найден' });
  res.download(filePath);
});

// Multer error handler
router.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    if (err.code === 'LIMIT_FILE_SIZE') {
      const isAdmin = req.session && req.session.user && req.session.user.role === 'admin';
      const limit = isAdmin ? '1 GB' : '200 MB';
      return res.status(413).json({ error: 'Файл слишком большой. Максимум ' + limit + '.' });
    }
    return res.status(400).json({ error: err.message });
  }
  if (err) return res.status(400).json({ error: err.message });
  next();
});

module.exports = router;
