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 archiver = require('archiver');
const log = require('../lib/logger');
const { UPLOADS_DIR, RESULTS_DIR } = require('../lib/storage');

const router = express.Router();

const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB

const upload = multer({
  dest: UPLOADS_DIR,
  limits: { fileSize: MAX_FILE_SIZE },
  fileFilter: (req, file, cb) => {
    if (file.mimetype === 'application/pdf' || file.originalname.endsWith('.pdf')) cb(null, true);
    else if (file.mimetype.startsWith('image/')) cb(null, true); // for fromImages
    else cb(new Error('Только PDF и изображения'));
  },
});

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

// Rate-limit ALL pdf routes (several spawn Ghostscript / do heavy pdf-lib work)
router.use(limiter);

// Fix multer filename encoding (Latin-1 → UTF-8 for Cyrillic)
function fixFilename(str) {
  try {
    const buf = Buffer.from(str, 'latin1');
    const decoded = buf.toString('utf8');
    if (/[а-яёА-ЯЁ]/.test(decoded)) return decoded;
  } catch {}
  return str;
}

// File storage for uploaded PDFs
const pdfFiles = new Map();

function registerFile(multerFile, pageCount) {
  const id = `pdf_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
  const entry = {
    id,
    path: multerFile.path,
    name: fixFilename(multerFile.originalname),
    size: multerFile.size,
    pages: pageCount || 0,
  };
  pdfFiles.set(id, entry);
  setTimeout(() => {
    try { fs.existsSync(entry.path) && fs.unlinkSync(entry.path); } catch {}
    pdfFiles.delete(id);
  }, 30 * 60 * 1000);
  return entry;
}

function getFile(id) {
  const f = pdfFiles.get(id);
  if (!f || !fs.existsSync(f.path)) return null;
  return f;
}

function saveResult(buffer, ext) {
  const name = `result_${Date.now()}_${Math.random().toString(36).slice(2, 6)}${ext}`;
  const outPath = path.join(RESULTS_DIR, name);
  fs.writeFileSync(outPath, buffer);
  setTimeout(() => { try { fs.unlinkSync(outPath); } catch {} }, 30 * 60 * 1000);
  return `/pdf/download/${name}`;
}

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

// Upload
router.post('/upload', upload.array('files', 10), async (req, res) => {
  if (!req.files || !req.files.length) return res.status(400).json({ error: 'Файлы не загружены' });

  const results = [];

  for (const file of req.files) {
    let pages = 0;
    try {
      if (file.mimetype === 'application/pdf' || file.originalname.endsWith('.pdf')) {
        const buf = fs.readFileSync(file.path);
        // Use pdf-lib for page count (more reliable than pdf-parse)
        const { PDFDocument } = await import('pdf-lib');
        const doc = await PDFDocument.load(buf, { ignoreEncryption: true });
        pages = doc.getPageCount();
      }
    } catch (e) {
      log.warn('PDF page count failed', { file: file.originalname, error: e.message });
    }
    const entry = registerFile(file, pages);
    results.push({ id: entry.id, name: entry.name, size: entry.size, pages });
  }

  log.info(`PDF upload: ${results.length} files`);
  res.json({ files: results });
});

// Preview — render PDF page as PNG thumbnail via Ghostscript
router.get('/preview/:fileId', (req, res) => renderPreview(req, res, 1));
router.get('/preview/:fileId/:page', (req, res) => renderPreview(req, res, parseInt(req.params.page) || 1));

async function renderPreview(req, res, pageNum) {
  const f = getFile(req.params.fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  const cacheKey = `${f.id}_p${pageNum}`;
  const cachePath = path.join(RESULTS_DIR, `preview_${cacheKey}.png`);

  // Return cached if exists
  if (fs.existsSync(cachePath)) {
    return res.type('png').sendFile(cachePath);
  }

  try {
    await new Promise((resolve, reject) => {
      const proc = spawn('gs', [
        '-sDEVICE=png16m', '-r72', '-dNOPAUSE', '-dBATCH', '-dQUIET',
        `-dFirstPage=${pageNum}`, `-dLastPage=${pageNum}`,
        '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
        `-sOutputFile=${cachePath}`, f.path,
      ], { timeout: 15000 });
      proc.on('close', code => code === 0 ? resolve() : reject(new Error('Preview failed')));
      proc.on('error', reject);
    });

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

    res.type('png').sendFile(cachePath);
  } catch (err) {
    res.status(500).json({ error: 'Не удалось создать превью' });
  }
}

// Thumbnails — return preview URLs for all pages
router.get('/thumbnails/:fileId', async (req, res) => {
  const f = getFile(req.params.fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  const urls = [];
  for (let i = 1; i <= Math.min(f.pages, 50); i++) {
    urls.push(`/pdf/preview/${f.id}/${i}`);
  }
  res.json({ pages: f.pages, thumbnails: urls });
});

// Info
router.get('/info/:fileId', async (req, res) => {
  const f = getFile(req.params.fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  try {
    const pdfParse = require('pdf-parse');
    const buf = fs.readFileSync(f.path);
    const data = await pdfParse(buf);
    res.json({
      pages: data.numpages,
      title: data.info?.Title || '',
      author: data.info?.Author || '',
      creator: data.info?.Creator || '',
      size: f.size,
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Merge
router.post('/merge', express.json(), async (req, res) => {
  const { fileIds } = req.body;
  if (!fileIds || fileIds.length < 2) return res.status(400).json({ error: 'Нужно минимум 2 файла' });

  try {
    const { PDFDocument } = await import('pdf-lib');
    const merged = await PDFDocument.create();

    for (const id of fileIds) {
      const f = getFile(id);
      if (!f) return res.status(404).json({ error: `Файл ${id} не найден` });
      const buf = fs.readFileSync(f.path);
      const doc = await PDFDocument.load(buf);
      const pages = await merged.copyPages(doc, doc.getPageIndices());
      pages.forEach(p => merged.addPage(p));
    }

    const result = await merged.save();
    const url = saveResult(Buffer.from(result), '.pdf');
    res.json({ downloadUrl: url, size: result.length });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Split
router.post('/split', express.json(), async (req, res) => {
  const { fileId, ranges } = req.body;
  const f = getFile(fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  try {
    const { PDFDocument } = await import('pdf-lib');
    const buf = fs.readFileSync(f.path);
    const srcDoc = await PDFDocument.load(buf);
    const totalPages = srcDoc.getPageCount();

    // Parse ranges: "1-3,5,7-9"
    const pageNums = [];
    for (const part of ranges.split(',')) {
      const trimmed = part.trim();
      if (trimmed.includes('-')) {
        const [start, end] = trimmed.split('-').map(Number);
        for (let i = start; i <= Math.min(end, totalPages); i++) pageNums.push(i);
      } else {
        const n = parseInt(trimmed);
        if (n >= 1 && n <= totalPages) pageNums.push(n);
      }
    }

    if (pageNums.length === 0) return res.status(400).json({ error: 'Нет валидных страниц' });

    // Single output PDF with selected pages
    const newDoc = await PDFDocument.create();
    const indices = pageNums.map(n => n - 1); // 0-based
    const copiedPages = await newDoc.copyPages(srcDoc, indices);
    copiedPages.forEach(p => newDoc.addPage(p));

    const result = await newDoc.save();
    const url = saveResult(Buffer.from(result), '.pdf');
    res.json({ downloadUrl: url, size: result.length, pages: pageNums.length });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Rotate
router.post('/rotate', express.json(), async (req, res) => {
  const { fileId, pages, angle } = req.body;
  const f = getFile(fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  try {
    const { PDFDocument, degrees } = await import('pdf-lib');
    const buf = fs.readFileSync(f.path);
    const doc = await PDFDocument.load(buf);
    const total = doc.getPageCount();

    const targetPages = pages === 'all'
      ? Array.from({ length: total }, (_, i) => i)
      : pages.split(',').map(n => parseInt(n.trim()) - 1).filter(i => i >= 0 && i < total);

    for (const idx of targetPages) {
      const page = doc.getPage(idx);
      const current = page.getRotation().angle;
      page.setRotation(degrees(current + (angle || 90)));
    }

    const result = await doc.save();
    const url = saveResult(Buffer.from(result), '.pdf');
    res.json({ downloadUrl: url, size: result.length });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Delete pages
router.post('/delete', express.json(), async (req, res) => {
  const { fileId, pages } = req.body;
  const f = getFile(fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  try {
    const { PDFDocument } = await import('pdf-lib');
    const buf = fs.readFileSync(f.path);
    const srcDoc = await PDFDocument.load(buf);
    const total = srcDoc.getPageCount();

    const deleteSet = new Set(pages.split(',').map(n => parseInt(n.trim()) - 1));
    const keepIndices = Array.from({ length: total }, (_, i) => i).filter(i => !deleteSet.has(i));

    if (keepIndices.length === 0) return res.status(400).json({ error: 'Нельзя удалить все страницы' });

    const newDoc = await PDFDocument.create();
    const copied = await newDoc.copyPages(srcDoc, keepIndices);
    copied.forEach(p => newDoc.addPage(p));

    const result = await newDoc.save();
    const url = saveResult(Buffer.from(result), '.pdf');
    res.json({ downloadUrl: url, size: result.length, pages: keepIndices.length });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Reorder
router.post('/reorder', express.json(), async (req, res) => {
  const { fileId, order } = req.body;
  const f = getFile(fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  try {
    const { PDFDocument } = await import('pdf-lib');
    const buf = fs.readFileSync(f.path);
    const srcDoc = await PDFDocument.load(buf);
    const indices = order.map(n => n - 1); // 1-based to 0-based

    const newDoc = await PDFDocument.create();
    const copied = await newDoc.copyPages(srcDoc, indices);
    copied.forEach(p => newDoc.addPage(p));

    const result = await newDoc.save();
    const url = saveResult(Buffer.from(result), '.pdf');
    res.json({ downloadUrl: url, size: result.length });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Watermark
router.post('/watermark', express.json(), async (req, res) => {
  const { fileId, text, fontSize = 48, opacity = 0.3, color = '#888888' } = req.body;
  const f = getFile(fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  try {
    const { PDFDocument, rgb, StandardFonts } = await import('pdf-lib');
    const buf = fs.readFileSync(f.path);
    const doc = await PDFDocument.load(buf);
    const font = await doc.embedFont(StandardFonts.Helvetica);

    const r = parseInt(color.slice(1, 3), 16) / 255;
    const g = parseInt(color.slice(3, 5), 16) / 255;
    const b = parseInt(color.slice(5, 7), 16) / 255;

    for (let i = 0; i < doc.getPageCount(); i++) {
      const page = doc.getPage(i);
      const { width, height } = page.getSize();
      const textWidth = font.widthOfTextAtSize(text, fontSize);

      page.drawText(text, {
        x: (width - textWidth) / 2,
        y: height / 2,
        size: fontSize,
        font,
        color: rgb(r, g, b),
        opacity,
        rotate: { type: 'degrees', angle: -45 },
      });
    }

    const result = await doc.save();
    const url = saveResult(Buffer.from(result), '.pdf');
    res.json({ downloadUrl: url, size: result.length });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Page numbers
router.post('/pagenumbers', express.json(), async (req, res) => {
  const { fileId, position = 'bottom-center', startFrom = 1 } = req.body;
  const f = getFile(fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  try {
    const { PDFDocument, rgb, StandardFonts } = await import('pdf-lib');
    const buf = fs.readFileSync(f.path);
    const doc = await PDFDocument.load(buf);
    const font = await doc.embedFont(StandardFonts.Helvetica);
    const total = doc.getPageCount();

    for (let i = 0; i < total; i++) {
      const page = doc.getPage(i);
      const { width } = page.getSize();
      const numText = String(i + startFrom);
      const textWidth = font.widthOfTextAtSize(numText, 10);

      let x = (width - textWidth) / 2; // center
      let y = 30;

      if (position.includes('left')) x = 40;
      if (position.includes('right')) x = width - 40 - textWidth;
      if (position.includes('top')) y = page.getSize().height - 30;

      page.drawText(numText, { x, y, size: 10, font, color: rgb(0.4, 0.4, 0.4) });
    }

    const result = await doc.save();
    const url = saveResult(Buffer.from(result), '.pdf');
    res.json({ downloadUrl: url, size: result.length });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Compress (Ghostscript)
router.post('/compress', express.json(), async (req, res) => {
  const { fileId, quality = 'ebook' } = req.body;
  const f = getFile(fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  const outName = `compressed_${Date.now()}.pdf`;
  const outPath = path.join(RESULTS_DIR, outName);
  const settings = { screen: '/screen', ebook: '/ebook', printer: '/printer' };

  try {
    await new Promise((resolve, reject) => {
      const proc = spawn('gs', [
        '-sDEVICE=pdfwrite', '-dCompatibilityLevel=1.4',
        `-dPDFSETTINGS=${settings[quality] || '/ebook'}`,
        '-dNOPAUSE', '-dBATCH', '-dQUIET',
        `-sOutputFile=${outPath}`, f.path,
      ], { timeout: 120000 });
      proc.on('close', code => code === 0 ? resolve() : reject(new Error('Ghostscript error')));
      proc.on('error', reject);
    });

    const stat = fs.statSync(outPath);
    const savings = f.size > 0 ? Math.round((1 - stat.size / f.size) * 100) : 0;
    setTimeout(() => { try { fs.unlinkSync(outPath); } catch {} }, 30 * 60 * 1000);

    res.json({ downloadUrl: `/pdf/download/${outName}`, size: stat.size, savings });
  } catch (err) {
    try { fs.unlinkSync(outPath); } catch {}
    res.status(500).json({ error: err.message });
  }
});

// Protect (add password)
router.post('/protect', express.json(), async (req, res) => {
  const { fileId, password } = req.body;
  const f = getFile(fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });
  if (!password) return res.status(400).json({ error: 'Пароль обязателен' });

  try {
    const { PDFDocument } = await import('pdf-lib');
    const buf = fs.readFileSync(f.path);
    const doc = await PDFDocument.load(buf);
    doc.encrypt({ userPassword: password, ownerPassword: password });

    const result = await doc.save();
    const url = saveResult(Buffer.from(result), '.pdf');
    res.json({ downloadUrl: url, size: result.length });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Extract text
router.post('/extract-text', express.json(), async (req, res) => {
  const { fileId } = req.body;
  const f = getFile(fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  try {
    const pdfParse = require('pdf-parse');
    const buf = fs.readFileSync(f.path);
    const data = await pdfParse(buf);
    res.json({
      text: data.text,
      pages: data.numpages,
      metadata: data.info || {},
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// PDF to Images (Ghostscript)
router.post('/toImages', express.json(), async (req, res) => {
  const { fileId, format = 'png' } = req.body;
  // Clamp DPI to avoid Ghostscript rendering gigantic bitmaps (memory/disk DoS)
  const dpi = Math.min(300, Math.max(36, parseInt(req.body.dpi) || 150));
  const f = getFile(fileId);
  if (!f) return res.status(404).json({ error: 'Файл не найден' });

  const tmpDir = path.join(RESULTS_DIR, `img_${Date.now()}`);
  fs.mkdirSync(tmpDir, { recursive: true });
  const device = format === 'jpg' ? 'jpeg' : 'png16m';

  try {
    await new Promise((resolve, reject) => {
      const proc = spawn('gs', [
        `-sDEVICE=${device}`, `-r${dpi}`,
        '-dNOPAUSE', '-dBATCH', '-dQUIET',
        `-sOutputFile=${tmpDir}/page_%03d.${format === 'jpg' ? 'jpg' : 'png'}`,
        f.path,
      ], { timeout: 120000 });
      proc.on('close', code => code === 0 ? resolve() : reject(new Error('Ghostscript error')));
      proc.on('error', reject);
    });

    // ZIP the images
    const zipName = `pages_${Date.now()}.zip`;
    const zipPath = path.join(RESULTS_DIR, zipName);
    const output = fs.createWriteStream(zipPath);
    const archive = archiver('zip', { zlib: { level: 6 } });
    const zipDone = new Promise((resolve, reject) => { output.on('close', resolve); output.on('error', reject); archive.on('error', reject); });
    archive.pipe(output);
    archive.directory(tmpDir, false);
    await archive.finalize();
    await zipDone;

    // Cleanup temp dir
    fs.readdirSync(tmpDir).forEach(f => fs.unlinkSync(path.join(tmpDir, f)));
    fs.rmdirSync(tmpDir);

    setTimeout(() => { try { fs.unlinkSync(zipPath); } catch {} }, 30 * 60 * 1000);
    const stat = fs.statSync(zipPath);
    res.json({ downloadUrl: `/pdf/download/${zipName}`, size: stat.size });
  } catch (err) {
    try { fs.readdirSync(tmpDir).forEach(f => fs.unlinkSync(path.join(tmpDir, f))); fs.rmdirSync(tmpDir); } catch {}
    res.status(500).json({ error: err.message });
  }
});

// Images to PDF
router.post('/fromImages', upload.array('images', 50), async (req, res) => {
  if (!req.files || !req.files.length) return res.status(400).json({ error: 'Изображения не загружены' });

  try {
    const { PDFDocument } = await import('pdf-lib');
    const doc = await PDFDocument.create();

    for (const file of req.files) {
      const imgBytes = fs.readFileSync(file.path);
      let img;
      if (file.mimetype === 'image/png') img = await doc.embedPng(imgBytes);
      else img = await doc.embedJpg(imgBytes);

      const page = doc.addPage([img.width, img.height]);
      page.drawImage(img, { x: 0, y: 0, width: img.width, height: img.height });

      try { fs.unlinkSync(file.path); } catch {}
    }

    const result = await doc.save();
    const url = saveResult(Buffer.from(result), '.pdf');
    res.json({ downloadUrl: url, size: result.length, pages: req.files.length });
  } catch (err) {
    req.files.forEach(f => { try { fs.unlinkSync(f.path); } catch {} });
    res.status(500).json({ error: err.message });
  }
});

// Download
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') return res.status(413).json({ error: 'Файл слишком большой. Максимум 50MB.' });
    return res.status(400).json({ error: err.message });
  }
  if (err) return res.status(400).json({ error: err.message });
  next();
});

module.exports = router;
