const express = require('express');
const multer = require('multer');
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const rateLimit = require('express-rate-limit');
const geoip = require('geoip-lite');
const log = require('../lib/logger');
const { UPLOADS_DIR, RESULTS_DIR } = require('../lib/storage');

const router = express.Router();



// Cleanup old files
function cleanupDir(dir, maxAgeMs) {
  try {
    const files = fs.readdirSync(dir);
    const now = Date.now();
    for (const file of files) {
      const filePath = path.join(dir, file);
      try {
        const stat = fs.statSync(filePath);
        if (now - stat.mtimeMs > maxAgeMs) fs.unlinkSync(filePath);
      } catch {}
    }
  } catch {}
}

setInterval(() => cleanupDir(UPLOADS_DIR, 10 * 60 * 1000), 2 * 60 * 1000);
setInterval(() => cleanupDir(RESULTS_DIR, 30 * 60 * 1000), 5 * 60 * 1000);

// Multer config
const maxFileSize = (parseInt(process.env.MAX_FILE_SIZE_MB) || 20) * 1024 * 1024;
const maxFiles = parseInt(process.env.MAX_FILES) || 50;

const imageFileFilter = (req, file, cb) => {
  // SVG intentionally excluded: sharp/librsvg would render it and honour external
  // <image href>/file:// references → SSRF / local-file read / SVG-bomb DoS.
  const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/avif', 'image/tiff', 'image/gif', 'image/bmp'];
  if (validTypes.includes(file.mimetype) || file.originalname.match(/\.(jpe?g|png|webp|avif|tiff?|gif|bmp|heic|heif)$/i)) cb(null, true);
  else cb(new Error('Неподдерживаемый формат изображения'));
};

// Админ — без лимитов (размер, кол-во файлов, rate-limit)
function isAdmin(req) {
  return !!(req.session && req.session.user && req.session.user.role === 'admin');
}

// User — лимит размера из .env; Admin — без лимита размера
const uploadUser = multer({ dest: UPLOADS_DIR, limits: { fileSize: maxFileSize }, fileFilter: imageFileFilter });
const uploadAdmin = multer({ dest: UPLOADS_DIR, fileFilter: imageFileFilter });

// Динамический выбор multer: админу — без лимита количества и размера
function uploadArray(req, res, next) {
  return (isAdmin(req) ? uploadAdmin.array('images') : uploadUser.array('images', maxFiles))(req, res, next);
}
function uploadSingle(req, res, next) {
  return (isAdmin(req) ? uploadAdmin : uploadUser).single('image')(req, res, next);
}

// Rate limiter (админ пропускается — безлимитная загрузка по одному файлу на запрос)
const limiter = rateLimit({
  keyGenerator: (req) => (req.session && req.session.user && req.session.user.id) ? "user_" + req.session.user.id : req.ip,
  windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 60000,
  max: parseInt(process.env.RATE_LIMIT_MAX) || 30,
  skip: isAdmin,
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Слишком много запросов. Попробуйте через минуту.' },
});

// Transliteration
const TRANSLIT_MAP = {
  'а':'a','б':'b','в':'v','г':'g','д':'d','е':'e','ё':'yo','ж':'zh','з':'z','и':'i',
  'й':'y','к':'k','л':'l','м':'m','н':'n','о':'o','п':'p','р':'r','с':'s','т':'t',
  'у':'u','ф':'f','х':'kh','ц':'ts','ч':'ch','ш':'sh','щ':'shch','ъ':'','ы':'y',
  'ь':'','э':'e','ю':'yu','я':'ya',
  'А':'A','Б':'B','В':'V','Г':'G','Д':'D','Е':'E','Ё':'Yo','Ж':'Zh','З':'Z','И':'I',
  'Й':'Y','К':'K','Л':'L','М':'M','Н':'N','О':'O','П':'P','Р':'R','С':'S','Т':'T',
  'У':'U','Ф':'F','Х':'Kh','Ц':'Ts','Ч':'Ch','Ш':'Sh','Щ':'Shch','Ъ':'','Ы':'Y',
  'Ь':'','Э':'E','Ю':'Yu','Я':'Ya',
};

function fixMulterFilename(str) {
  try {
    const buf = Buffer.from(str, 'latin1');
    const decoded = buf.toString('utf8');
    if (/[а-яёА-ЯЁ]/.test(decoded)) return decoded;
  } catch {}
  return str;
}

function transliterate(str) {
  const fixed = fixMulterFilename(str);
  return fixed.replace(/[а-яёА-ЯЁ]/g, ch => TRANSLIT_MAP[ch] !== undefined ? TRANSLIT_MAP[ch] : ch)
    .replace(/[^\w.\-]/g, '_').replace(/_+/g, '_').replace(/^_|_$/g, '');
}

function getOutputExtension(format, originalName) {
  const extMap = { webp: '.webp', jpeg: '.jpg', png: '.png', avif: '.avif', tiff: '.tiff', gif: '.gif' };
  return extMap[format] || path.extname(originalName);
}

function changeExtension(filename, newExt) {
  return path.basename(filename, path.extname(filename)) + newExt;
}

function getCountry(ip) {
  const clean = ip.replace(/^::ffff:/, '');
  if (clean === '127.0.0.1' || clean === '::1') return 'local';
  const geo = geoip.lookup(clean);
  return geo ? geo.country : '??';
}

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

// Compress endpoint
router.post('/', limiter, uploadArray, async (req, res) => {
  const resize = parseInt(req.body.resize || '0', 10);
  const format = (req.body.format || 'original').toLowerCase();
  const quality = Math.min(100, Math.max(1, parseInt(req.body.quality || process.env.COMPRESS_QUALITY || '80', 10)));
  const validFormats = ['original', 'webp', 'jpeg', 'png', 'avif', 'tiff', 'gif'];

  if (!validFormats.includes(format)) {
    return res.status(400).json({ error: 'Неверный формат' });
  }
  if (!req.files || !req.files.length) {
    return res.status(400).json({ error: 'Файлы не загружены' });
  }

  const archiveName = `archive_${Date.now()}.zip`;
  const archivePath = path.join(RESULTS_DIR, archiveName);
  const output = fs.createWriteStream(archivePath);
  const archive = archiver('zip', { zlib: { level: 9 } });
  const stats = [];
  const country = getCountry(req.ip);

  // Bind error/close listeners BEFORE piping — an unhandled 'error' on archive/output
  // would otherwise crash the whole process via uncaughtException.
  const archiveDone = new Promise((resolve, reject) => {
    output.on('close', resolve);
    output.on('error', reject);
    archive.on('error', reject);
  });

  try {
    archive.pipe(output);

    for (const file of req.files) {
      const inputPath = file.path;
      const originalSize = file.size;
      const image = sharp(inputPath);
      const metadata = await image.metadata();

      if (resize > 0 && metadata.width && metadata.height && Math.max(metadata.width, metadata.height) > resize) {
        const scale = resize / Math.max(metadata.width, metadata.height);
        const newWidth = Math.round(metadata.width * scale);
        const newHeight = Math.round(metadata.height * scale);
        image.resize({ width: newWidth, height: newHeight });

        const msg = `[${country}] ${req.ip} resized: ${file.originalname} ${metadata.width}x${metadata.height} → ${newWidth}x${newHeight} (${(originalSize / 1024).toFixed(0)}KB)`;
        log.info(msg);
        log.logToFile(msg);
      } else {
        const msg = `[${country}] ${req.ip} compress: ${file.originalname} ${metadata.width}x${metadata.height} format=${format} q=${quality} (${(originalSize / 1024).toFixed(0)}KB)`;
        log.info(msg);
        log.logToFile(msg);
      }

      let buffer;
      if (format === 'webp') buffer = await image.webp({ quality }).toBuffer();
      else if (format === 'jpeg') buffer = await image.jpeg({ quality }).toBuffer();
      else if (format === 'png') buffer = await image.png({ compressionLevel: 9 }).toBuffer();
      else if (format === 'avif') buffer = await image.avif({ quality }).toBuffer();
      else if (format === 'tiff') buffer = await image.tiff({ quality }).toBuffer();
      else if (format === 'gif') buffer = await image.gif().toBuffer();
      else {
        // original — keep source format with compression
        if (file.mimetype === 'image/jpeg') buffer = await image.jpeg({ quality }).toBuffer();
        else if (file.mimetype === 'image/png') buffer = await image.png({ compressionLevel: 9 }).toBuffer();
        else if (file.mimetype === 'image/webp') buffer = await image.webp({ quality }).toBuffer();
        else if (file.mimetype === 'image/avif') buffer = await image.avif({ quality }).toBuffer();
        else if (file.mimetype === 'image/tiff') buffer = await image.tiff({ quality }).toBuffer();
        else if (file.mimetype === 'image/gif') buffer = await image.gif().toBuffer();
        else buffer = await image.jpeg({ quality }).toBuffer(); // fallback
      }

      const readableName = fixMulterFilename(file.originalname);
      const newExt = getOutputExtension(format, readableName);
      const rawName = format === 'original' ? readableName : changeExtension(readableName, newExt);
      const outputName = transliterate(rawName);
      archive.append(buffer, { name: outputName });

      const compressedSize = buffer.length;
      const savings = originalSize > 0 ? Math.round((1 - compressedSize / originalSize) * 100) : 0;
      stats.push({ filename: readableName, outputFilename: outputName, originalSize, compressedSize, savings });
    }

    await archive.finalize();
    await archiveDone;

    res.json({ success: true, downloadUrl: `/download/${archiveName}`, stats });
  } catch (err) {
    log.error('Compression error', { error: err.message });
    try { fs.existsSync(archivePath) && fs.unlinkSync(archivePath); } catch {}
    res.status(500).json({ error: 'Ошибка при сжатии' });
  } finally {
    if (req.files) {
      req.files.forEach(file => {
        try { fs.existsSync(file.path) && fs.unlinkSync(file.path); } catch {}
      });
    }
  }
});


// Single file compress endpoint
router.post('/single', limiter, uploadSingle, async (req, res) => {
  const resize = parseInt(req.body.resize || '0', 10);
  const format = (req.body.format || 'original').toLowerCase();
  const quality = Math.min(100, Math.max(1, parseInt(req.body.quality || process.env.COMPRESS_QUALITY || '80', 10)));
  const validFormats = ['original', 'webp', 'jpeg', 'png', 'avif', 'tiff', 'gif'];

  if (!validFormats.includes(format)) {
    return res.status(400).json({ error: 'Неверный формат' });
  }
  if (!req.file) {
    return res.status(400).json({ error: 'Файл не загружен' });
  }

  const country = getCountry(req.ip);
  const inputPath = req.file.path;
  const originalSize = req.file.size;

  try {
    const image = sharp(inputPath);
    const metadata = await image.metadata();

    if (resize > 0 && metadata.width && metadata.height && Math.max(metadata.width, metadata.height) > resize) {
      const scale = resize / Math.max(metadata.width, metadata.height);
      const newWidth = Math.round(metadata.width * scale);
      const newHeight = Math.round(metadata.height * scale);
      image.resize({ width: newWidth, height: newHeight });
      const msg = "[" + country + "] " + req.ip + " single resized: " + req.file.originalname + " " + metadata.width + "x" + metadata.height + " -> " + newWidth + "x" + newHeight + " (" + (originalSize / 1024).toFixed(0) + "KB)";
      log.info(msg); log.logToFile(msg);
    } else {
      const msg = "[" + country + "] " + req.ip + " single compress: " + req.file.originalname + " " + metadata.width + "x" + metadata.height + " format=" + format + " q=" + quality + " (" + (originalSize / 1024).toFixed(0) + "KB)";
      log.info(msg); log.logToFile(msg);
    }

    let buffer;
    let outputMimetype;
    if (format === 'webp') { buffer = await image.webp({ quality }).toBuffer(); outputMimetype = 'image/webp'; }
    else if (format === 'jpeg') { buffer = await image.jpeg({ quality }).toBuffer(); outputMimetype = 'image/jpeg'; }
    else if (format === 'png') { buffer = await image.png({ compressionLevel: 9 }).toBuffer(); outputMimetype = 'image/png'; }
    else if (format === 'avif') { buffer = await image.avif({ quality }).toBuffer(); outputMimetype = 'image/avif'; }
    else if (format === 'tiff') { buffer = await image.tiff({ quality }).toBuffer(); outputMimetype = 'image/tiff'; }
    else if (format === 'gif') { buffer = await image.gif().toBuffer(); outputMimetype = 'image/gif'; }
    else {
      if (req.file.mimetype === 'image/jpeg') { buffer = await image.jpeg({ quality }).toBuffer(); outputMimetype = 'image/jpeg'; }
      else if (req.file.mimetype === 'image/png') { buffer = await image.png({ compressionLevel: 9 }).toBuffer(); outputMimetype = 'image/png'; }
      else if (req.file.mimetype === 'image/webp') { buffer = await image.webp({ quality }).toBuffer(); outputMimetype = 'image/webp'; }
      else if (req.file.mimetype === 'image/avif') { buffer = await image.avif({ quality }).toBuffer(); outputMimetype = 'image/avif'; }
      else if (req.file.mimetype === 'image/tiff') { buffer = await image.tiff({ quality }).toBuffer(); outputMimetype = 'image/tiff'; }
      else if (req.file.mimetype === 'image/gif') { buffer = await image.gif().toBuffer(); outputMimetype = 'image/gif'; }
      else { buffer = await image.jpeg({ quality }).toBuffer(); outputMimetype = 'image/jpeg'; }
    }

    const readableName = fixMulterFilename(req.file.originalname);
    const newExt = getOutputExtension(format, readableName);
    const rawName = format === 'original' ? readableName : changeExtension(readableName, newExt);
    const outputFilename = 'single_' + Date.now() + '_' + transliterate(rawName);
    const outputPath = require('path').join(RESULTS_DIR, outputFilename);

    require('fs').writeFileSync(outputPath, buffer);

    const compressedSize = buffer.length;
    const savings = originalSize > 0 ? Math.round((1 - compressedSize / originalSize) * 100) : 0;

    setTimeout(() => {
      try { require('fs').existsSync(outputPath) && require('fs').unlinkSync(outputPath); } catch(e) {}
    }, 30 * 60 * 1000);

    res.json({
      filename: readableName,
      outputFilename,
      originalSize,
      compressedSize,
      savings,
      downloadUrl: '/compress/download/' + outputFilename,
      mimetype: outputMimetype,
    });
  } catch (err) {
    log.error('Single compression error', { error: err.message });
    res.status(500).json({ error: 'Ошибка при сжатии' });
  } finally {
    try { require('fs').existsSync(inputPath) && require('fs').unlinkSync(inputPath); } catch(e) {}
  }
});

// Individual file download
router.get('/download/:filename', (req, res) => {
  const filename = require('path').basename(req.params.filename);
  const filePath = require('path').join(RESULTS_DIR, filename);
  if (!require('fs').existsSync(filePath)) {
    return res.status(404).json({ error: 'Файл не найден или истёк срок хранения' });
  }
  res.download(filePath, filename);
});

// Multer error handler
router.use((err, req, res, next) => {
  if (err instanceof multer.MulterError) {
    if (err.code === 'LIMIT_FILE_SIZE') return res.status(413).json({ error: `Файл слишком большой. Максимум ${process.env.MAX_FILE_SIZE_MB || 20}MB.` });
    if (err.code === 'LIMIT_FILE_COUNT') return res.status(400).json({ error: `Слишком много файлов. Максимум ${maxFiles}.` });
    return res.status(400).json({ error: err.message });
  }
  if (err) return res.status(500).json({ error: 'Внутренняя ошибка сервера' });
  next();
});

module.exports = router;
module.exports.RESULTS_DIR = RESULTS_DIR;
