import { Request, Response } from 'express';
import * as backupService from '../services/backup.service.js';
import prisma from '../lib/prisma.js';

/**
 * Validiert Backup-Namen: nur Zeichen die auch der Backup-Generator erstellen darf
 * (ISO-Zeitstempel mit Buchstaben, Zahlen, Bindestrich, optional -N Suffix).
 * Blockt Path-Traversal-Versuche wie "../../etc/passwd".
 */
function isValidBackupName(name: string): boolean {
  return /^[A-Za-z0-9_-]+$/.test(name) && !name.includes('..');
}
import { logChange } from '../services/audit.service.js';

// Fängt console.log/info/warn/error für die Laufzeit einer Operation in
// einen Puffer mit ab (zusätzlich landet alles weiterhin in stdout/stderr).
// Wird in createBackup/restoreBackup verwendet, um den vollständigen
// Verlauf in `BackupLog.fullLog` zu persistieren. Da die Backup-Operationen
// in der Praxis nicht parallel laufen (Single-User-Admin-UI), reicht die
// process-globale Patch-Variante.
function startLogCapture(): { lines: string[]; restore: () => void } {
  const lines: string[] = [];
  const orig = {
    log: console.log,
    info: console.info,
    warn: console.warn,
    error: console.error,
  };
  function fmt(args: unknown[]): string {
    return args
      .map((a) => {
        if (a instanceof Error) return a.stack || a.message;
        if (typeof a === 'object') {
          try {
            return JSON.stringify(a);
          } catch {
            return String(a);
          }
        }
        return String(a);
      })
      .join(' ');
  }
  console.log = (...args: unknown[]) => { lines.push(fmt(args)); orig.log(...args); };
  console.info = (...args: unknown[]) => { lines.push(fmt(args)); orig.info(...args); };
  console.warn = (...args: unknown[]) => { lines.push(`[WARN] ${fmt(args)}`); orig.warn(...args); };
  console.error = (...args: unknown[]) => { lines.push(`[ERROR] ${fmt(args)}`); orig.error(...args); };
  return {
    lines,
    restore: () => {
      console.log = orig.log;
      console.info = orig.info;
      console.warn = orig.warn;
      console.error = orig.error;
    },
  };
}

async function recordBackupLog(opts: {
  req: Request;
  operation: 'CREATE' | 'RESTORE';
  backupName: string | null;
  success: boolean;
  durationMs: number;
  summary: string;
  fullLog: string;
}) {
  try {
    const user = (opts.req as any).user;
    await prisma.backupLog.create({
      data: {
        operation: opts.operation,
        backupName: opts.backupName,
        success: opts.success,
        durationMs: opts.durationMs,
        summary: opts.summary.slice(0, 2000),
        // LongText: bis ~4 GB, aber wir cappen bei 1 MB damit nichts entgleist
        fullLog: opts.fullLog.slice(0, 1_000_000),
        userId: user?.userId ?? null,
        userEmail: user?.email ?? null,
        ipAddress:
          (opts.req as any).socket?.remoteAddress ||
          (opts.req.headers?.['x-forwarded-for'] as string) ||
          null,
      },
    });
  } catch (err) {
    console.error('[BackupLog] Konnte Log nicht persistieren:', err);
  }
}

/**
 * Liste aller Backups abrufen
 * GET /api/settings/backups
 */
export async function listBackups(req: Request, res: Response) {
  try {
    const backups = await backupService.listBackups();
    res.json({ data: backups });
  } catch (error: any) {
    res.status(500).json({ error: 'Fehler beim Laden der Backups', details: error.message });
  }
}

/**
 * Neues Backup erstellen
 * POST /api/settings/backup
 */
export async function createBackup(req: Request, res: Response) {
  const start = Date.now();
  const capture = startLogCapture();
  try {
    const result = await backupService.createBackup();
    const durationMs = Date.now() - start;

    if (result.success) {
      capture.restore();
      const summary = `Backup ${result.backupName} erstellt (${(durationMs / 1000).toFixed(1)}s)`;
      await recordBackupLog({
        req, operation: 'CREATE', backupName: result.backupName ?? null,
        success: true, durationMs, summary,
        fullLog: capture.lines.join('\n') || summary,
      });
      await logChange({
        req, action: 'CREATE', resourceType: 'Backup',
        label: `Backup ${result.backupName} erstellt`,
      });
      res.json({ data: { backupName: result.backupName }, message: 'Backup erfolgreich erstellt' });
    } else {
      capture.restore();
      await recordBackupLog({
        req, operation: 'CREATE', backupName: null,
        success: false, durationMs,
        summary: `Backup fehlgeschlagen: ${result.error || 'unbekannt'}`,
        fullLog: capture.lines.join('\n') + '\n[Fehler] ' + (result.error || ''),
      });
      res.status(500).json({ error: 'Backup fehlgeschlagen', details: result.error });
    }
  } catch (error: any) {
    const durationMs = Date.now() - start;
    capture.restore();
    await recordBackupLog({
      req, operation: 'CREATE', backupName: null,
      success: false, durationMs,
      summary: `Fehler: ${error?.message || 'unbekannt'}`,
      fullLog: capture.lines.join('\n') + '\n[Exception] ' + (error?.stack || error?.message || error),
    });
    res.status(500).json({ error: 'Fehler beim Erstellen des Backups', details: error.message });
  }
}

/**
 * Backup wiederherstellen
 * POST /api/settings/backup/:name/restore
 */
// Macht eine Fehlermeldung admin-lesbar OHNE den globalen ORM-Leak-Filter
// auszulösen: Stack-Frames raus, "TypeError: …" → "Code-Fehler: …",
// "Cannot read properties of undefined" → "Interner Code-Fehler".
// Vollständiger Stack landet immer im Server-Log (siehe `console.error`).
function makeRestoreErrorReadable(raw: unknown): string {
  if (!raw) return 'Unbekannter Fehler';
  let s = typeof raw === 'string' ? raw : (raw as any)?.message || String(raw);
  // Stack-Frames "    at …(…:123:45)" abschneiden
  s = s.split('\n').filter((line: string) => !/^\s*at\s+/.test(line)).join('\n').trim();
  // Bekannte JS-Runtime-Marker rephrasen, damit der orm-leak-guard nicht
  // alles auf "Operation fehlgeschlagen" maskiert.
  s = s
    .replace(/^TypeError:?\s*/i, 'Code-Fehler: ')
    .replace(/^ReferenceError:?\s*/i, 'Code-Fehler: ')
    .replace(/^SyntaxError:?\s*/i, 'Code-Fehler: ')
    .replace(/^RangeError:?\s*/i, 'Code-Fehler: ')
    .replace(/Cannot read propert(?:y|ies) of (undefined|null) \(reading '([^']+)'\)/i, 'Wert fehlt: $2')
    .replace(/is not a function/i, '(ungültiger Funktionsaufruf)')
    .replace(/is not defined$/i, '(Wert nicht definiert)')
    .replace(/Invalid `prisma\.[^`]+`/i, 'DB-Fehler');
  return s.slice(0, 500); // Längenlimit für UI
}

export async function restoreBackup(req: Request, res: Response) {
  const start = Date.now();
  const { name } = req.params;

  if (!name || !isValidBackupName(name)) {
    return res.status(400).json({ error: 'Ungültiger Backup-Name' });
  }

  // Pflicht-Confirm im Body, gleiche Defensive wie factoryReset.
  // Pentest 2026-05-19 (KRITISCH): leerer POST-Body löste vorher
  // sofort den destruktiven Restore aus – ein versehentlicher
  // Re-Fire (Browser-Tab, CSRF auf eingeloggten Admin, doppelter
  // Klick) konnte die DB ungewollt überschreiben. Der String ist
  // bewusst ein unique Magic-Value, kein Boolean.
  const confirm = (req.body && req.body.confirm) ? String(req.body.confirm) : '';
  if (confirm !== 'RESTORE-BESTAETIGT') {
    return res.status(400).json({
      error: 'Bestätigung fehlt. Body muss { "confirm": "RESTORE-BESTAETIGT" } enthalten.',
    });
  }

  const capture = startLogCapture();
  try {
    const result = await backupService.restoreBackup(name);
    const durationMs = Date.now() - start;

    if (result.success) {
      capture.restore();
      const summary = `${result.restoredRecords} Datensätze, ${result.restoredFiles || 0} Dateien (${(durationMs / 1000).toFixed(1)}s)`;
      await recordBackupLog({
        req, operation: 'RESTORE', backupName: name,
        success: true, durationMs, summary,
        fullLog: capture.lines.join('\n') || summary,
      });
      await logChange({
        req, action: 'UPDATE', resourceType: 'Backup',
        label: `Backup ${name} wiederhergestellt`,
      });
      res.json({
        data: {
          restoredRecords: result.restoredRecords,
          restoredFiles: result.restoredFiles,
        },
        message: `${result.restoredRecords} Datensätze und ${result.restoredFiles || 0} Dateien wiederhergestellt`,
      });
    } else {
      console.error(`[restore] Backup ${name} fehlgeschlagen:`, result.error);
      capture.restore();
      await recordBackupLog({
        req, operation: 'RESTORE', backupName: name,
        success: false, durationMs,
        summary: `Fehlgeschlagen: ${makeRestoreErrorReadable(result.error)}`,
        fullLog: capture.lines.join('\n') + '\n[Fehler] ' + (result.error || ''),
      });
      res.status(500).json({
        error: 'Wiederherstellung fehlgeschlagen',
        details: makeRestoreErrorReadable(result.error),
        hint: 'Vollständiger Verlauf in den Backup-Logs unterhalb der Backup-Liste.',
      });
    }
  } catch (error: any) {
    const durationMs = Date.now() - start;
    console.error(`[restore] Exception bei Backup ${name}:`, error?.stack || error);
    capture.restore();
    await recordBackupLog({
      req, operation: 'RESTORE', backupName: name,
      success: false, durationMs,
      summary: `Exception: ${makeRestoreErrorReadable(error)}`,
      fullLog: capture.lines.join('\n') + '\n[Exception] ' + (error?.stack || error?.message || error),
    });
    res.status(500).json({
      error: 'Fehler bei der Wiederherstellung',
      details: makeRestoreErrorReadable(error),
      hint: 'Vollständiger Verlauf in den Backup-Logs unterhalb der Backup-Liste.',
    });
  }
}

/**
 * Backup löschen
 * DELETE /api/settings/backup/:name
 */
export async function deleteBackup(req: Request, res: Response) {
  try {
    const { name } = req.params;

    if (!name || !isValidBackupName(name)) {
      return res.status(400).json({ error: 'Ungültiger Backup-Name' });
    }

    const result = await backupService.deleteBackup(name);

    if (result.success) {
      await logChange({
        req, action: 'DELETE', resourceType: 'Backup',
        label: `Backup ${name} gelöscht`,
      });
      res.json({ message: 'Backup gelöscht' });
    } else {
      res.status(500).json({ error: 'Löschen fehlgeschlagen', details: result.error });
    }
  } catch (error: any) {
    res.status(500).json({ error: 'Fehler beim Löschen des Backups', details: error.message });
  }
}

/**
 * Backup als ZIP herunterladen
 * GET /api/settings/backup/:name/download
 */
export async function downloadBackup(req: Request, res: Response) {
  try {
    const { name } = req.params;

    if (!name || !isValidBackupName(name)) {
      return res.status(400).json({ error: 'Ungültiger Backup-Name' });
    }

    const result = await backupService.createBackupZip(name);

    if ('error' in result) {
      return res.status(404).json({ error: result.error });
    }

    // Response-Header setzen
    res.setHeader('Content-Type', 'application/zip');
    res.setHeader('Content-Disposition', `attachment; filename="${result.filename}"`);

    // Archiver zum Response pipen
    result.stream.pipe(res);

    // Archiver finalisieren (startet das Schreiben)
    result.stream.finalize();
  } catch (error: any) {
    res.status(500).json({ error: 'Fehler beim Download', details: error.message });
  }
}

/**
 * Backup-ZIP hochladen
 * POST /api/settings/backup/upload
 */
export async function uploadBackup(req: Request, res: Response) {
  try {
    if (!req.file) {
      return res.status(400).json({ error: 'Keine Datei hochgeladen' });
    }

    // Prüfen ob es eine ZIP-Datei ist
    if (!req.file.originalname.endsWith('.zip')) {
      return res.status(400).json({ error: 'Nur ZIP-Dateien sind erlaubt' });
    }

    const result = await backupService.uploadBackupZip(req.file.buffer);

    if (result.success) {
      res.json({
        data: { backupName: result.backupName },
        message: 'Backup erfolgreich hochgeladen',
      });
    } else {
      res.status(400).json({ error: 'Upload fehlgeschlagen', details: result.error });
    }
  } catch (error: any) {
    res.status(500).json({ error: 'Fehler beim Upload', details: error.message });
  }
}

/**
 * Werkseinstellungen - Alle Daten löschen
 * POST /api/settings/factory-reset
 */
export async function factoryReset(req: Request, res: Response) {
  try {
    // Bestätigung erforderlich: client MUSS explizit
    // `confirm: "FACTORY-RESET-BESTAETIGT"` schicken. Ohne diesen Schritt
    // konnte ein eingeloggter Admin die komplette DB mit einem einfachen
    // POST plätten (Pentest Runde 11 (2026-05-18) – C2 KRITISCH:
    // 3× DB-Plättung in einer Session). Body-Wert ist absichtlich ein
    // unique String und kein boolean, damit kein Auto-JSON-Tooling /
    // Replay-Angriff aus Versehen triggern kann.
    const confirm = (req.body && req.body.confirm) ? String(req.body.confirm) : '';
    if (confirm !== 'FACTORY-RESET-BESTAETIGT') {
      res.status(400).json({
        success: false,
        error: 'Bestätigung fehlt. Body muss { "confirm": "FACTORY-RESET-BESTAETIGT" } enthalten.',
      });
      return;
    }

    const result = await backupService.factoryReset();

    if (result.success) {
      await logChange({
        req, action: 'DELETE', resourceType: 'System',
        label: `Werkseinstellungen wiederhergestellt`,
      });
      res.json({
        message: 'Werkseinstellungen wiederhergestellt. Bitte melden Sie sich mit admin@admin.com / admin an.',
      });
    } else {
      res.status(500).json({ error: 'Werkseinstellungen fehlgeschlagen', details: result.error });
    }
  } catch (error: any) {
    res.status(500).json({ error: 'Fehler bei Werkseinstellungen' });
    console.error('factoryReset error:', error);
  }
}

/**
 * Liste der Backup-Logs (CREATE oder RESTORE)
 * GET /api/settings/backup-logs?operation=CREATE|RESTORE&limit=50
 * Liefert die Übersichtsdaten OHNE den großen fullLog.
 */
export async function listBackupLogs(req: Request, res: Response) {
  try {
    const op = String(req.query.operation || '').toUpperCase();
    const limit = Math.min(Math.max(parseInt(String(req.query.limit || '50'), 10) || 50, 1), 200);

    const where: any = {};
    if (op === 'CREATE' || op === 'RESTORE') {
      where.operation = op;
    }

    const logs = await prisma.backupLog.findMany({
      where,
      orderBy: { createdAt: 'desc' },
      take: limit,
      select: {
        id: true,
        operation: true,
        backupName: true,
        success: true,
        durationMs: true,
        summary: true,
        userEmail: true,
        ipAddress: true,
        createdAt: true,
      },
    });

    res.json({ data: logs });
  } catch (error: any) {
    res.status(500).json({ error: 'Fehler beim Laden der Logs', details: error.message });
  }
}

/**
 * Detail eines Backup-Logs inkl. fullLog
 * GET /api/settings/backup-logs/:id
 */
export async function getBackupLogDetail(req: Request, res: Response) {
  try {
    const id = parseInt(req.params.id, 10);
    if (!Number.isFinite(id) || id < 1) {
      return res.status(400).json({ error: 'Ungültige ID' });
    }
    const log = await prisma.backupLog.findUnique({ where: { id } });
    if (!log) {
      return res.status(404).json({ error: 'Log-Eintrag nicht gefunden' });
    }
    res.json({ data: log });
  } catch (error: any) {
    res.status(500).json({ error: 'Fehler beim Laden des Log-Details', details: error.message });
  }
}
