'use strict';

const express = require('express');
const fs = require('fs');
const os = require('os');
const path = require('path');
const multer = require('multer');
const asyncHandler = require('../middleware/asyncHandler');
const pipelineService = require('../services/pipelineService');
const converterScanService = require('../services/converterScanService');
const historyService = require('../services/historyService');
const logger = require('../services/logger').child('CONVERTER_ROUTE');

/** Pfadauflösung mit Traversal-Schutz (wie Klangkiste resolveMediaTarget) */
function resolveTarget(rawDir, input) {
  const rel = converterScanService.normalizeRelPath(input != null ? String(input) : '');
  if (rel === null) return { error: 'Ungültiger Pfad' };
  const absolute = path.join(rawDir, rel || '.');
  return { rel: rel || '', absolute };
}

const router = express.Router();

const converterUpload = multer({
  dest: path.join(os.tmpdir(), 'ripster-converter-uploads')
});

// ── Scan ──────────────────────────────────────────────────────────────────

/**
 * GET /api/converter/tree
 * Vollständiger FS-Verzeichnisbaum des converter_raw_dir (keine DB).
 */
router.get(
  '/tree',
  asyncHandler(async (req, res) => {
    logger.debug('get:tree');
    const result = await converterScanService.getTree();
    res.json(result);
  })
);

/**
 * GET /api/converter/browse?parent=relPath
 * File-Explorer (DB-basiert, wird weiterhin für Job-Zuweisung gebraucht).
 */
router.get(
  '/browse',
  asyncHandler(async (req, res) => {
    const parent = req.query.parent ? String(req.query.parent).trim() : null;
    logger.debug('get:browse', { parent });
    const entries = await converterScanService.getEntries(parent);
    const rawDir = await converterScanService.getRawDir();
    res.json({ entries, rawDir });
  })
);

/**
 * POST /api/converter/scan
 * Manuellen Scan des converter_raw_dir auslösen.
 */
router.post(
  '/scan',
  asyncHandler(async (req, res) => {
    logger.info('post:scan');
    const result = await converterScanService.scan();
    res.json({ result });
  })
);

// ── Jobs erstellen ────────────────────────────────────────────────────────

/**
 * POST /api/converter/create-jobs
 * Body: { entries: [{ relPath, converterMediaType }] }
 * Erstellt Jobs aus Scan-Einträgen (File-Explorer-Auswahl).
 */
router.post(
  '/create-jobs',
  asyncHandler(async (req, res) => {
    const entries = Array.isArray(req.body?.entries) ? req.body.entries : [];
    if (entries.length === 0) {
      const error = new Error('Keine Einträge ausgewählt.');
      error.statusCode = 400;
      throw error;
    }
    logger.info('post:create-jobs', { count: entries.length });

    const jobs = [];
    for (const entry of entries) {
      const relPath = String(entry?.relPath || '').trim();
      if (!relPath) continue;
      const job = await pipelineService.createFileJob({
        kind: 'converter_entry',
        relPath,
        options: {
          converterMediaType: entry?.converterMediaType || null
        }
      });
      jobs.push(job);
    }
    res.json({ jobs });
  })
);

/**
 * POST /api/converter/upload
 * Datei(en) hochladen → Unterordner anlegen (kein Job-Anlegen).
 * Gibt { folders: [{folderRelPath, fileCount}] } zurück.
 */
router.post(
  '/upload',
  converterUpload.array('files', 50),
  asyncHandler(async (req, res) => {
    const files = Array.isArray(req.files) ? req.files : [];
    if (files.length === 0) {
      const error = new Error('Keine Dateien hochgeladen.');
      error.statusCode = 400;
      throw error;
    }
    const folderName = req.body?.folderName ? String(req.body.folderName).trim() : null;
    logger.info('post:upload', {
      fileCount: files.length,
      folderName,
      files: files.map((f) => ({ name: f.originalname, size: f.size }))
    });
    const result = await pipelineService.uploadConverterFiles(files, { folderName });
    res.json(result);
  })
);

/**
 * POST /api/converter/jobs/from-selection
 * Body: { relPaths: string[], audioMode: 'individual'|'shared' }
 * Erstellt Jobs aus im File-Explorer ausgewählten Dateien.
 */
router.post(
  '/jobs/from-selection',
  asyncHandler(async (req, res) => {
    const relPaths = Array.isArray(req.body?.relPaths) ? req.body.relPaths : [];
    const audioMode = String(req.body?.audioMode || 'individual');
    if (relPaths.length === 0) {
      const error = new Error('Keine Dateien ausgewählt.');
      error.statusCode = 400;
      throw error;
    }
    logger.info('post:jobs:from-selection', { count: relPaths.length, audioMode });
    const jobs = await pipelineService.createConverterJobsFromSelection(relPaths, audioMode);
    res.json({ jobs });
  })
);

/**
 * POST /api/converter/jobs/:jobId/assign-files
 * Body: { relPaths: string[] }
 * Fügt ausgewählte Dateien einem bestehenden (nicht gestarteten) Converter-Job hinzu.
 */
router.post(
  '/jobs/:jobId/assign-files',
  asyncHandler(async (req, res) => {
    const jobId = Number(req.params.jobId);
    const relPaths = Array.isArray(req.body?.relPaths) ? req.body.relPaths : [];
    if (!Number.isFinite(jobId) || jobId <= 0) {
      return res.status(400).json({ detail: 'Ungültige jobId.' });
    }
    if (relPaths.length === 0) {
      return res.status(400).json({ detail: 'Keine Dateien übergeben.' });
    }
    logger.info('post:jobs:assign-files', { jobId, count: relPaths.length });
    const result = await pipelineService.assignConverterFilesToJob(jobId, relPaths);
    res.json(result);
  })
);

/**
 * POST /api/converter/jobs/:jobId/remove-file
 * Body: { relPath: string }
 * Entfernt eine Datei aus einem bestehenden Converter-Job.
 */
router.post(
  '/jobs/:jobId/remove-file',
  asyncHandler(async (req, res) => {
    const jobId = Number(req.params.jobId);
    const relPath = String(req.body?.relPath || '').trim();
    if (!Number.isFinite(jobId) || jobId <= 0) {
      return res.status(400).json({ detail: 'Ungültige jobId.' });
    }
    if (!relPath) {
      return res.status(400).json({ detail: 'relPath fehlt.' });
    }
    logger.info('post:jobs:remove-file', { jobId, relPath });
    const result = await pipelineService.removeConverterFileFromJob(jobId, relPath);
    res.json(result);
  })
);

/**
 * POST /api/converter/jobs/:jobId/remove-input
 * Body: { inputPath: string }
 * Entfernt eine Datei aus einem Converter-Job anhand des absoluten Input-Pfads.
 */
router.post(
  '/jobs/:jobId/remove-input',
  asyncHandler(async (req, res) => {
    const jobId = Number(req.params.jobId);
    const inputPath = String(req.body?.inputPath || '').trim();
    if (!Number.isFinite(jobId) || jobId <= 0) {
      return res.status(400).json({ detail: 'Ungültige jobId.' });
    }
    if (!inputPath) {
      return res.status(400).json({ detail: 'inputPath fehlt.' });
    }
    logger.info('post:jobs:remove-input', { jobId, inputPath });
    const result = await pipelineService.removeConverterInputFromJob(jobId, inputPath);
    res.json(result);
  })
);

/**
 * POST /api/converter/jobs/:jobId/config
 * Body: partial config draft (outputFormat, presets, metadata, tracks, MusicBrainz-UI-Stand)
 * Speichert den Draft für READY_TO_START Jobs persistent im encode_plan_json.
 */
router.post(
  '/jobs/:jobId/config',
  asyncHandler(async (req, res) => {
    const jobId = Number(req.params.jobId);
    if (!Number.isFinite(jobId) || jobId <= 0) {
      return res.status(400).json({ detail: 'Ungültige jobId.' });
    }
    logger.debug('post:jobs:config', { jobId });
    const result = await pipelineService.updateConverterJobConfig(jobId, req.body || {});
    res.json(result);
  })
);

// ── Datei-Operationen (reines FS, keine DB) ───────────────────────────────

/**
 * DELETE /api/converter/files
 * Body: { relPath }
 * Datei oder Ordner löschen (fs.rmSync, ohne DB).
 */
router.delete(
  '/files',
  asyncHandler(async (req, res) => {
    const rawDir = await converterScanService.getRawDir();
    if (!rawDir) return res.status(400).json({ detail: 'Kein RAW-Verzeichnis konfiguriert.' });
    const relPath = String(req.body?.relPath || '').trim();
    if (!relPath) return res.status(400).json({ detail: 'relPath fehlt.' });
    const target = resolveTarget(rawDir, relPath);
    if (target.error || !target.rel) return res.status(400).json({ detail: target.error || 'Ungültiger Pfad.' });
    logger.info('delete:files', { relPath });
    fs.rmSync(target.absolute, { recursive: true, force: true });
    res.json({ ok: true });
  })
);

/**
 * POST /api/converter/files/rename
 * Body: { relPath, newName }
 * Umbenennen (fs.renameSync, ohne DB).
 */
router.post(
  '/files/rename',
  asyncHandler(async (req, res) => {
    const rawDir = await converterScanService.getRawDir();
    if (!rawDir) return res.status(400).json({ detail: 'Kein RAW-Verzeichnis konfiguriert.' });
    const relPath = String(req.body?.relPath || '').trim();
    const newName = String(req.body?.newName || '').trim();
    if (!relPath || !newName) return res.status(400).json({ detail: 'relPath und newName erforderlich.' });
    if (newName.includes('/') || newName.includes('\\')) return res.status(400).json({ detail: 'Ungültiger Name.' });
    const source = resolveTarget(rawDir, relPath);
    if (source.error || !source.rel) return res.status(400).json({ detail: source.error || 'Ungültiger Quellpfad.' });
    const parentAbs = path.dirname(source.absolute);
    const destAbs = path.join(parentAbs, newName);
    logger.info('post:files:rename', { relPath, newName });
    fs.renameSync(source.absolute, destAbs);
    res.json({ ok: true });
  })
);

/**
 * POST /api/converter/files/move
 * Body: { relPath, targetParentRelPath }
 * Verschieben (fs.renameSync, ohne DB). targetParentRelPath = '' → Root.
 */
router.post(
  '/files/move',
  asyncHandler(async (req, res) => {
    const rawDir = await converterScanService.getRawDir();
    if (!rawDir) return res.status(400).json({ detail: 'Kein RAW-Verzeichnis konfiguriert.' });
    const relPath = String(req.body?.relPath || '').trim();
    if (!relPath) return res.status(400).json({ detail: 'relPath erforderlich.' });
    const targetParentRelPath = req.body?.targetParentRelPath != null ? String(req.body.targetParentRelPath) : '';
    const source = resolveTarget(rawDir, relPath);
    if (source.error || !source.rel) return res.status(400).json({ detail: source.error || 'Ungültiger Quellpfad.' });
    const targetParent = resolveTarget(rawDir, targetParentRelPath);
    if (targetParent.error) return res.status(400).json({ detail: targetParent.error });
    const name = path.basename(source.absolute);
    const destAbs = path.join(targetParent.absolute, name);
    logger.info('post:files:move', { relPath, targetParentRelPath });
    fs.renameSync(source.absolute, destAbs);
    res.json({ ok: true });
  })
);

/**
 * POST /api/converter/files/folder
 * Body: { parentRelPath, name }
 * Neuen Ordner anlegen (fs.mkdirSync, ohne DB).
 */
router.post(
  '/files/folder',
  asyncHandler(async (req, res) => {
    const rawDir = await converterScanService.getRawDir();
    if (!rawDir) return res.status(400).json({ detail: 'Kein RAW-Verzeichnis konfiguriert.' });
    const parentRelPath = req.body?.parentRelPath != null ? String(req.body.parentRelPath) : '';
    const name = String(req.body?.name || '').trim();
    if (!name || name.includes('/') || name.includes('\\')) return res.status(400).json({ detail: 'Ungültiger Name.' });
    const parent = resolveTarget(rawDir, parentRelPath);
    if (parent.error) return res.status(400).json({ detail: parent.error });
    logger.info('post:files:folder', { parentRelPath, name });
    fs.mkdirSync(path.join(parent.absolute, name), { recursive: true });
    res.json({ ok: true });
  })
);

// ── Job-Status ────────────────────────────────────────────────────────────

/**
 * GET /api/converter/jobs
 * Alle Converter-Jobs zurückgeben.
 */
router.get(
  '/jobs',
  asyncHandler(async (req, res) => {
    const jobs = await pipelineService.getConverterJobs();
    res.json({ jobs });
  })
);

/**
 * GET /api/converter/jobs/:jobId
 * Einzelnen Converter-Job abrufen.
 */
router.get(
  '/jobs/:jobId',
  asyncHandler(async (req, res) => {
    const jobId = Number(req.params.jobId);
    const job = await historyService.getJobById(jobId);
    if (!job) {
      const error = new Error(`Job ${jobId} nicht gefunden.`);
      error.statusCode = 404;
      throw error;
    }
    res.json({ job });
  })
);

/**
 * POST /api/converter/jobs/:jobId/start
 * Job mit finaler Konfiguration starten.
 * Body: { converterMediaType, outputFormat, userPreset, trackSelection, handBrakeTitleId, audioFormatOptions }
 */
router.post(
  '/jobs/:jobId/start',
  asyncHandler(async (req, res) => {
    const jobId = Number(req.params.jobId);
    const config = req.body || {};
    logger.info('post:jobs:start', {
      jobId,
      converterMediaType: config.converterMediaType,
      outputFormat: config.outputFormat
    });
    const result = await pipelineService.startConverterJob(jobId, config);
    res.json({ result });
  })
);

/**
 * POST /api/converter/jobs/:jobId/cancel
 * Job abbrechen.
 */
router.post(
  '/jobs/:jobId/cancel',
  asyncHandler(async (req, res) => {
    const jobId = Number(req.params.jobId);
    logger.info('post:jobs:cancel', { jobId });
    const result = await pipelineService.cancel(jobId);
    res.json({ result });
  })
);

/**
 * DELETE /api/converter/jobs/:jobId
 * Job aus der DB löschen.
 */
router.delete(
  '/jobs/:jobId',
  asyncHandler(async (req, res) => {
    const jobId = Number(req.params.jobId);
    logger.info('delete:jobs', { jobId });
    await historyService.deleteJob(jobId, 'none', { includeRelated: false });
    await converterScanService.clearAssignmentsForJob(jobId);
    res.json({ ok: true });
  })
);

module.exports = router;
