import fs from 'fs';
import path from 'path';
import { pipeline } from 'stream/promises';
import { randomUUID } from 'crypto';
import { fileURLToPath } from 'url';
import multer from 'multer';
import db from '../../db.js';
import { badRequest, UUID } from '../../lib/http.js';
import { mapEvent } from '../../lib/mappers.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

/** Beschreibungstexte beim Zusammenführen (ohne doppelte Blöcke). */
function mergeDescriptions(existing, incoming) {
  const a = String(existing || '').trim();
  const b = String(incoming || '').trim();
  if (!b) return a;
  if (!a) return b;
  if (a.includes(b)) return a;
  return `${a}\n\n${b}`;
}
const uploadsRoot = path.resolve(
  process.env.UPLOAD_DIR || path.join(__dirname, '..', '..', 'data', 'uploads'),
);

const maxFileSize = Number(process.env.ATTACHMENT_MAX_BYTES) || 20 * 1024 * 1024;
const maxFiles = Number(process.env.ATTACHMENT_MAX_FILES) || 20;

function safeBasename(name) {
  let base = path.basename(name || 'file');
  base = base.replace(/[/\\?%*:|"<>]/g, '_');
  return base.slice(0, 200) || 'file';
}

function handleMulterError(err, res) {
  if (err?.name === 'MulterError') {
    if (err.code === 'LIMIT_FILE_SIZE') {
      return res.status(400).json({ message: 'Datei zu groß.' });
    }
    if (err.code === 'LIMIT_FILE_COUNT' || err.code === 'LIMIT_UNEXPECTED_FILE') {
      return res.status(400).json({ message: 'Zu viele Dateien.' });
    }
    return res.status(400).json({ message: err.message || 'Upload fehlgeschlagen.' });
  }
  return res.status(400).json({ message: err.message || 'Upload fehlgeschlagen.' });
}

/** Multer 2.x: kein diskStorage — Dateien liegen temporär vor, Zugriff über `stream`. */
function uploadMiddleware(req, res, next) {
  const { ticketId } = req.params;
  if (!UUID.test(ticketId)) {
    return res.status(404).json({ message: 'Nicht gefunden' });
  }
  const upload = multer({
    limits: { fileSize: maxFileSize, files: maxFiles },
  }).array('files', maxFiles);

  upload(req, res, (err) => {
    if (err) return handleMulterError(err, res);
    next();
  });
}

/** Streams nach `data/uploads/tickets/<ticketId>/` schreiben (permanent). */
async function persistUploadedFiles(files, ticketId) {
  const destDir = path.join(uploadsRoot, 'tickets', ticketId);
  fs.mkdirSync(destDir, { recursive: true });
  const saved = [];
  for (const f of files) {
    const filename = `${randomUUID()}_${safeBasename(f.originalName)}`;
    const absPath = path.join(destDir, filename);
    await pipeline(f.stream, fs.createWriteStream(absPath));
    const stat = fs.statSync(absPath);
    saved.push({
      path: absPath,
      originalname: f.originalName || 'Datei',
      mimetype: f.clientReportedMimeType,
      size: stat.size,
    });
  }
  return saved;
}

function unlinkUploaded(files) {
  for (const f of files || []) {
    try {
      if (f?.path) fs.unlinkSync(f.path);
    } catch {
      /* ignore */
    }
  }
}

/** Temporäre Multer-Dateien verwerfen, wenn kein persist erfolgt. */
function discardIncomingFiles(files) {
  for (const f of files || []) {
    try {
      if (f.stream && typeof f.stream.destroy === 'function') f.stream.destroy();
      if (f.path && fs.existsSync(f.path)) fs.unlinkSync(f.path);
    } catch {
      /* ignore */
    }
  }
}

export function registerAttachmentRoutes(api) {
  api.post(
    '/tickets/:ticketId/events/attachments',
    uploadMiddleware,
    async (req, res) => {
      const { ticketId } = req.params;
      const incoming = req.files || [];
      if (incoming.length === 0) {
        return badRequest(res, 'Mindestens eine Datei erforderlich.');
      }

      const t = db
        .prepare('SELECT 1 AS ok FROM tickets WHERE id = ?')
        .get(ticketId);
      if (!t) {
        discardIncomingFiles(incoming);
        return res.status(404).json({ message: 'Nicht gefunden' });
      }

      const descRaw =
        req.body && req.body.description != null
          ? String(req.body.description).trim()
          : '';
      const description = descRaw || '';

      let saved;
      try {
        saved = await persistUploadedFiles(incoming, ticketId);
      } catch (e) {
        console.error(e);
        return res.status(500).json({ message: 'Dateien konnten nicht gespeichert werden.' });
      }

      const existing = db
        .prepare(
          `SELECT id, description FROM events
           WHERE ticket_id = ? AND type = 'ATTACHMENT'
           AND date(created_at, 'localtime') = date('now', 'localtime')
           ORDER BY created_at ASC
           LIMIT 1`,
        )
        .get(ticketId);

      let eid;
      let createdNewEvent = false;

      try {
        db.exec('BEGIN');
        try {
          if (existing) {
            eid = existing.id;
            const mergedDesc = mergeDescriptions(existing.description, description);
            db.prepare('UPDATE events SET description = ? WHERE id = ?').run(
              mergedDesc,
              eid,
            );
          } else {
            eid = randomUUID();
            createdNewEvent = true;
            db.prepare(
              `INSERT INTO events (id, ticket_id, type, description, callback_number, teamviewer_id, article_number, remote_duration_seconds, teamviewer_notes)
               VALUES (?, ?, 'ATTACHMENT', ?, NULL, NULL, NULL, NULL, NULL)`,
            ).run(eid, ticketId, description);
          }

          const insAtt = db.prepare(
            `INSERT INTO ticket_attachments (id, event_id, original_name, stored_path, mime_type, size_bytes)
             VALUES (?, ?, ?, ?, ?, ?)`,
          );

          for (const f of saved) {
            const aid = randomUUID();
            const rel = path
              .relative(uploadsRoot, f.path)
              .split(path.sep)
              .join('/');
            if (rel.startsWith('..') || path.isAbsolute(rel)) {
              throw new Error('Ungültiger Speicherpfad');
            }
            const mime =
              f.mimetype && String(f.mimetype).trim()
                ? String(f.mimetype).trim()
                : null;
            insAtt.run(
              aid,
              eid,
              f.originalname,
              rel,
              mime,
              f.size,
            );
          }

          db.prepare(
            `UPDATE tickets SET updated_at = datetime('now'),
             sla_anchor_at = CASE WHEN status IN ('OPEN', 'WAITING') THEN datetime('now') ELSE sla_anchor_at END
             WHERE id = ?`,
          ).run(ticketId);

          db.exec('COMMIT');
        } catch (e) {
          db.exec('ROLLBACK');
          throw e;
        }

        const row = db.prepare('SELECT * FROM events WHERE id = ?').get(eid);
        const attRows = db
          .prepare(
            'SELECT * FROM ticket_attachments WHERE event_id = ? ORDER BY created_at ASC',
          )
          .all(eid);
        res
          .status(createdNewEvent ? 201 : 200)
          .json(mapEvent(row, attRows));
      } catch (e) {
        unlinkUploaded(saved);
        console.error(e);
        return res.status(500).json({ message: 'Speichern fehlgeschlagen.' });
      }
    },
  );

  api.get('/tickets/:ticketId/attachments/:attachmentId/file', (req, res) => {
    const { ticketId, attachmentId } = req.params;
    if (!UUID.test(ticketId) || !UUID.test(attachmentId)) {
      return res.status(404).json({ message: 'Nicht gefunden' });
    }
    const row = db
      .prepare(
        `SELECT ta.id, ta.original_name, ta.stored_path, e.ticket_id AS ev_tid
         FROM ticket_attachments ta
         JOIN events e ON e.id = ta.event_id
         WHERE ta.id = ? AND e.ticket_id = ?`,
      )
      .get(attachmentId, ticketId);
    if (!row) {
      return res.status(404).json({ message: 'Nicht gefunden' });
    }
    if (String(row.stored_path).includes('..')) {
      return res.status(400).json({ message: 'Ungültiger Pfad' });
    }
    const abs = path.resolve(path.join(uploadsRoot, row.stored_path));
    const rootResolved = path.resolve(uploadsRoot);
    if (abs !== rootResolved && !abs.startsWith(`${rootResolved}${path.sep}`)) {
      return res.status(400).json({ message: 'Ungültiger Pfad' });
    }
    if (!fs.existsSync(abs)) {
      return res.status(404).json({ message: 'Datei fehlt' });
    }

    const inline = req.query.inline === '1' || req.query.inline === 'true';
    let mime =
      row.mime_type && String(row.mime_type).trim()
        ? String(row.mime_type).trim()
        : null;
    if (!mime) {
      mime = guessMimeFromFilename(row.original_name);
    }

    if (inline) {
      res.setHeader('Content-Type', mime);
      res.setHeader(
        'Content-Disposition',
        `inline; filename*=UTF-8''${encodeURIComponent(row.original_name)}`,
      );
      return res.sendFile(abs);
    }

    res.setHeader('Content-Type', mime);
    res.download(abs, row.original_name);
  });
}

function guessMimeFromFilename(name) {
  const ext = path.extname(name || '').toLowerCase();
  const map = {
    '.pdf': 'application/pdf',
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.gif': 'image/gif',
    '.webp': 'image/webp',
    '.svg': 'image/svg+xml',
    '.bmp': 'image/bmp',
    '.txt': 'text/plain; charset=utf-8',
    '.csv': 'text/csv; charset=utf-8',
    '.json': 'application/json',
    '.xml': 'application/xml',
    '.html': 'text/html; charset=utf-8',
    '.htm': 'text/html; charset=utf-8',
    '.md': 'text/markdown; charset=utf-8',
    '.webm': 'video/webm',
    '.mp4': 'video/mp4',
    '.ogg': 'video/ogg',
    '.mp3': 'audio/mpeg',
    '.wav': 'audio/wav',
  };
  return map[ext] || 'application/octet-stream';
}
