import { Response } from 'express';
import { AuthRequest } from '../types/index.js';
import * as auditService from '../services/audit.service.js';
import { logChange } from '../services/audit.service.js';
import { AuditAction, AuditSensitivity } from '@prisma/client';

/**
 * Audit-Logs mit Filtern abrufen
 */
export async function getAuditLogs(req: AuthRequest, res: Response) {
  try {
    const {
      userId,
      customerId,
      dataSubjectId,
      action,
      sensitivity,
      resourceType,
      resourceId,
      startDate,
      endDate,
      success,
      search,
      page,
      limit,
    } = req.query;

    const result = await auditService.searchAuditLogs({
      userId: userId ? parseInt(userId as string) : undefined,
      customerId: customerId ? parseInt(customerId as string) : undefined,
      dataSubjectId: dataSubjectId ? parseInt(dataSubjectId as string) : undefined,
      action: action as AuditAction | undefined,
      sensitivity: sensitivity as AuditSensitivity | undefined,
      resourceType: resourceType as string | undefined,
      resourceId: resourceId as string | undefined,
      startDate: startDate ? new Date(startDate as string) : undefined,
      endDate: endDate ? new Date(endDate as string) : undefined,
      success: success !== undefined ? success === 'true' : undefined,
      search: search as string | undefined,
      page: page ? parseInt(page as string) : 1,
      limit: limit ? parseInt(limit as string) : 50,
    });

    res.json({ success: true, ...result });
  } catch (error) {
    console.error('Fehler beim Abrufen der Audit-Logs:', error);
    res.status(500).json({ success: false, error: 'Fehler beim Abrufen der Audit-Logs' });
  }
}

/**
 * Einzelnes Audit-Log abrufen
 */
export async function getAuditLogById(req: AuthRequest, res: Response) {
  try {
    const id = parseInt(req.params.id);
    // Nicht-numerische :id (z.B. GET /audit-logs/verify statt POST /verify)
    // landete sonst als NaN in Prisma findUnique → 500. Sauberer 400.
    // (Pentest R120 – gleiche „400 statt 500"-Klasse wie R64.1/R104.1.)
    if (Number.isNaN(id)) {
      return res.status(400).json({ success: false, error: 'Ungültige Audit-Log-ID' });
    }
    const log = await auditService.getAuditLogById(id);

    if (!log) {
      return res.status(404).json({ success: false, error: 'Audit-Log nicht gefunden' });
    }

    res.json({ success: true, data: log });
  } catch (error) {
    console.error('Fehler beim Abrufen des Audit-Logs:', error);
    res.status(500).json({ success: false, error: 'Fehler beim Abrufen des Audit-Logs' });
  }
}

/**
 * Audit-Logs für einen Kunden abrufen (DSGVO)
 */
export async function getAuditLogsByCustomer(req: AuthRequest, res: Response) {
  try {
    const customerId = parseInt(req.params.customerId);
    if (Number.isNaN(customerId)) {
      return res.status(400).json({ success: false, error: 'Ungültige Kunden-ID' });
    }
    const logs = await auditService.getAuditLogsByDataSubject(customerId);

    res.json({ success: true, data: logs });
  } catch (error) {
    console.error('Fehler beim Abrufen der Kunden-Audit-Logs:', error);
    res.status(500).json({ success: false, error: 'Fehler beim Abrufen der Audit-Logs' });
  }
}

/**
 * Audit-Logs exportieren
 */
export async function exportAuditLogs(req: AuthRequest, res: Response) {
  try {
    const format = (req.query.format as 'json' | 'csv') || 'json';
    const {
      action,
      sensitivity,
      resourceType,
      startDate,
      endDate,
    } = req.query;

    const content = await auditService.exportAuditLogs(
      {
        action: action as AuditAction | undefined,
        sensitivity: sensitivity as AuditSensitivity | undefined,
        resourceType: resourceType as string | undefined,
        startDate: startDate ? new Date(startDate as string) : undefined,
        endDate: endDate ? new Date(endDate as string) : undefined,
      },
      format
    );

    if (format === 'csv') {
      const filename = `audit-logs-${new Date().toISOString().split('T')[0]}.csv`;
      res.setHeader('Content-Type', 'text/csv; charset=utf-8');
      res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
      // BOM für Excel UTF-8 Erkennung
      res.send('\uFEFF' + content);
    } else {
      res.json({ success: true, data: JSON.parse(content) })
    }
  } catch (error) {
    console.error('Fehler beim Exportieren der Audit-Logs:', error);
    res.status(500).json({ success: false, error: 'Fehler beim Exportieren' });
  }
}

/**
 * Hash-Ketten-Integrität prüfen
 */
export async function verifyIntegrity(req: AuthRequest, res: Response) {
  try {
    const { fromId, toId } = req.query;

    const result = await auditService.verifyIntegrity(
      fromId ? parseInt(fromId as string) : undefined,
      toId ? parseInt(toId as string) : undefined
    );

    // Zwei sehr unterschiedliche Befunde sauber trennen – vorher wurde beides
    // pauschal als "manipuliert" gemeldet, was strukturelle Lücken wie einen
    // echten Angriff aussehen liess (und damit die Meldung entwertete).
    const tampered = result.tamperedEntries.length;
    const gaps = result.chainGaps.length;
    const unexplained = result.unexplainedGaps.length;

    const luecken = gaps > 0
      ? `${gaps} strukturelle Lücken` +
        (unexplained === 0
          ? ' (alle durch protokollierte Löschungen erklärt)'
          : unexplained < gaps
            ? `, davon ${unexplained} ohne dokumentierte Löschung`
            : ' ohne dokumentierte Löschung')
      : '';

    const unverifiable = result.unverifiableEntries.length;
    const keinSchluessel = unverifiable > 0
      ? ` ${unverifiable} Einträge sind HMAC-signiert und ohne konfigurierten AUDIT_HMAC_KEY nicht prüfbar.`
      : '';

    const siegel =
      result.backlogSealStatus === 'gebrochen'
        ? ' ⚠ Bestandssiegel GEBROCHEN:' +
          (result.backlogTampered.length
            ? ` ${result.backlogTampered.length} Alteinträge nachträglich verändert.`
            : '') +
          (result.backlogMissing.length
            ? ` ${result.backlogMissing.length} gesiegelte Einträge wurden ENTFERNT (${result.backlogMissing.join(', ')}).`
            : '') +
          (!result.backlogTampered.length && !result.backlogMissing.length
            ? ' Der Blattbestand des Siegels stimmt nicht mehr mit der signierten Wurzel überein.'
            : '')
        : result.backlogSealStatus === 'entfernt'
          ? ' ⚠ Bestandssiegel ENTFERNT: Es liegen versiegelte Blattwerte vor, aber kein gültiger ' +
            'Siegel-Marker mehr. Der Marker wurde gelöscht oder unbrauchbar gemacht – Änderungen am ' +
            'Altbestand wären dadurch wieder unsichtbar. Das ist KEIN Normalzustand.'
          : result.backlogSealStatus === 'nicht_noetig'
            ? ' Ein Bestandssiegel wird hier nicht gebraucht: Es gibt keine unsignierten Alteinträge.'
            : result.backlogSealStatus === 'kein_siegel'
              ? ' Hinweis: Der Altbestand ist nicht versiegelt – Änderungen daran wären nicht erkennbar. ' +
                'Behebbar mit POST /api/audit-logs/seal-backlog {"confirm":"SEAL"}.'
              : '';

    // Erneutes Siegeln kann legitim sein, verdient aber einen Blick: es
    // ersetzt die zuvor beglaubigte Wurzel (Pentest R173-03).
    const mehrfach =
      result.backlogSealCount > 1
        ? ` Hinweis: Der Altbestand wurde ${result.backlogSealCount}× versiegelt – der jüngste Marker gilt. ` +
          'Jeder Marker enthält den Befund vor dem Siegeln; bei Zweifeln dort vergleichen.'
        : '';

    // Bei problematischem Siegel darf KEIN beruhigender Einstiegssatz stehen –
    // sonst entwertet er die nachfolgende Warnung (Pentest R174-01).
    const siegelProblem =
      result.backlogSealStatus === 'gebrochen' || result.backlogSealStatus === 'entfernt';

    // „Keine Manipulation“ darf NICHT dastehen, solange `valid` falsch ist
    // (Pentest R183-02). Nach einem Cleanup mit abgesenkter Aufbewahrung waren
    // tausende Anmeldeprotokolle endgueltig geloescht – die Luecken durch
    // Tombstones „erklaert“, die Meldung las sich beruhigend, und der Befund
    // stand nur noch im Feld `valid`. Wer die Prosa liest statt des Felds,
    // klickt genau das weg, was ihn haette warnen sollen.
    const message = tampered > 0
      ? `${tampered} MANIPULIERTE Einträge gefunden` + (gaps > 0 ? ` (zusätzlich ${luecken})` : '')
      : siegelProblem
        ? 'Die Kette selbst ist rechnerisch stimmig, ABER:'
        : gaps > 0
          ? `Die Kette ist nicht mehr lückenlos: ${luecken}. Die verbliebenen Inhalte sind ` +
            'unverändert – aber gelöschte Einträge lassen sich naturgemäß nicht mehr prüfen. ' +
            'Dokumentierte Löschungen sind erwartbar; unerwartete gehören nachgegangen.'
          : 'Alle Einträge sind unverändert und lückenlos verkettet';

    res.json({
      success: true,
      data: {
        valid: result.valid,
        checkedCount: result.checkedCount,
        invalidEntries: result.invalidEntries,
        // Ernst: Inhalt einer bestehenden Zeile wurde nachträglich verändert.
        tamperedEntries: result.tamperedEntries,
        // Meist harmlos: Verkettung unterbrochen, Inhalte selbst unversehrt.
        chainGaps: result.chainGaps,
        // Nur Lücken ohne protokollierte Löschung sind erklärungsbedürftig.
        unexplainedGaps: result.unexplainedGaps,
        tampered: tampered > 0,
        message: message + keinSchluessel + siegel + mehrfach,
        unverifiableEntries: result.unverifiableEntries,
        // Zustand des Bestandssiegels ueber den nicht signierbaren Altbestand.
        backlogSealStatus: result.backlogSealStatus,
        backlogTampered: result.backlogTampered,
        backlogMissing: result.backlogMissing,
        backlogSealCount: result.backlogSealCount,
      },
    });
  } catch (error) {
    console.error('Fehler bei der Integritätsprüfung:', error);
    res.status(500).json({ success: false, error: 'Fehler bei der Integritätsprüfung' });
  }
}

/**
 * Hash-Kette reparieren (alle Hashes neu berechnen)
 */
export async function rehashAll(req: AuthRequest, res: Response) {
  try {
    // Ausdrueckliche Bestaetigung verlangen (Pentest R170-01).
    //
    // Ein Rehash macht die Kette rechnerisch stimmig und setzt damit die
    // Beweiskraft der Vergangenheit zurueck - das darf kein Nebeneffekt eines
    // versehentlichen oder tastenden POST sein. Genau so wurde der Endpunkt
    // bei einer Methoden-Erkundung unbeabsichtigt ausgeloest.
    if (req.body?.confirm !== 'REHASH') {
      res.status(400).json({
        success: false,
        error:
          'Rehash setzt die Beweiskraft der bestehenden Einträge zurück und ist nicht ' +
          'umkehrbar. Zum Bestätigen {"confirm":"REHASH"} mitsenden.',
      });
      return;
    }

    const result = await auditService.rehashAll({
      userEmail: req.user?.email,
      ipAddress: req.ip || (req.socket as any)?.remoteAddress,
    });
    res.json({
      success: true,
      data: result,
      message: `${result.rehashedCount} Einträge neu gehasht. Kette ist jetzt intakt.`,
    });
  } catch (error) {
    console.error('Fehler beim Re-Hashing:', error);
    res.status(500).json({ success: false, error: 'Fehler beim Re-Hashing' });
  }
}

/**
 * Kontrollwert fuer das externe Gegenbuch (Notarisierung).
 *
 * Bewusst nur lesend und ohne Geheimnisse - die Gegenstelle braucht keinerlei
 * Schreibrecht auf dieses System, und dieses System kennt den Schluessel der
 * Gegenstelle nicht. Genau diese Trennung ist der Schutz.
 */
export async function getCheckpoint(req: AuthRequest, res: Response) {
  try {
    const roh = req.query.atId as string | undefined;
    const atId = roh !== undefined ? parseInt(roh, 10) : undefined;
    if (roh !== undefined && Number.isNaN(atId as number)) {
      res.status(400).json({ success: false, error: 'atId muss eine Zahl sein' });
      return;
    }
    const data = await auditService.getCheckpoint(atId);
    res.json({ success: true, data });
  } catch (error) {
    console.error('Fehler beim Erstellen des Kontrollwerts:', error);
    res.status(500).json({ success: false, error: 'Fehler beim Erstellen des Kontrollwerts' });
  }
}

/**
 * Altbestand versiegeln (Pentest R171-02).
 *
 * Einmalige, NICHT destruktive Aktion: schreibt nichts um, sondern haelt den
 * Ist-Inhalt der nicht signierbaren Alteintraege fest.
 */
export async function sealBacklog(req: AuthRequest, res: Response) {
  try {
    if (req.body?.confirm !== 'SEAL') {
      res.status(400).json({
        success: false,
        error:
          'Versiegelt den aktuellen Stand des Altbestands. Zum Bestätigen ' +
          '{"confirm":"SEAL"} mitsenden.',
      });
      return;
    }
    const result = await auditService.sealBacklog({
      userEmail: req.user?.email,
      ipAddress: req.ip || (req.socket as any)?.remoteAddress,
    });
    res.json({
      success: true,
      data: result,
      message:
        `${result.sealedCount} Alteinträge (id ${result.fromId}–${result.toId}) versiegelt. ` +
        'Spätere Änderungen an diesen Einträgen fallen ab sofort auf.',
    });
  } catch (error) {
    console.error('Fehler beim Versiegeln des Altbestands:', error);
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Versiegeln',
    });
  }
}

/**
 * Retention-Policies abrufen
 */
export async function getRetentionPolicies(req: AuthRequest, res: Response) {
  try {
    const policies = await auditService.getRetentionPolicies();
    res.json({ success: true, data: policies });
  } catch (error) {
    console.error('Fehler beim Abrufen der Retention-Policies:', error);
    res.status(500).json({ success: false, error: 'Fehler beim Abrufen der Policies' });
  }
}

/**
 * Retention-Policy aktualisieren
 */
export async function updateRetentionPolicy(req: AuthRequest, res: Response) {
  try {
    const id = parseInt(req.params.id);
    if (Number.isNaN(id)) {
      return res.status(400).json({ success: false, error: 'Ungültige Policy-ID' });
    }
    const { retentionDays, description, legalBasis, isActive } = req.body;

    // Die Richtlinie bestimmt, WAS der Cleanup vernichtet – sie ist die
    // geladene Waffe, der Cleanup nur der Abzug (Pentest R183-01). Bisher war
    // ausgerechnet der Abzug gegatet und die Waffe frei zugaenglich: Ein
    // `retentionDays: 0` ging ohne Bestaetigung durch und wurde nur als MEDIUM
    // protokolliert, waehrend seine Wirkung CRITICAL ist. Die ausloesende Tat
    // war damit leiser als die Folge – und lag unter der Schwelle, bei der
    // jemand hinsieht.
    const bisher = (await auditService.getRetentionPolicies()).find((p) => p.id === id);
    if (!bisher) {
      return res.status(404).json({ success: false, error: 'Aufbewahrungsrichtlinie nicht gefunden' });
    }

    const neueTage = typeof retentionDays === 'number' ? retentionDays : bisher.retentionDays;

    // Untergrenze: Auth-Eintraege sind der Einbruchsbeleg schlechthin. Eine
    // Aufbewahrung von faktisch null macht sie mit dem naechsten Cleanup
    // spurlos entfernbar – ueber einen sanktionierten Pfad.
    const UNTERGRENZE: Record<string, number> = { Authentication: 30, AuditLog: 30 };
    const grenze = UNTERGRENZE[bisher.resourceType];
    if (grenze !== undefined && neueTage < grenze) {
      return res.status(400).json({
        success: false,
        error:
          `Für ${bisher.resourceType} sind mindestens ${grenze} Tage Aufbewahrung vorgeschrieben ` +
          `(angefragt: ${neueTage}). Diese Einträge belegen Anmeldungen und Zugriffe – eine ` +
          `Aufbewahrung nahe null macht sie beim nächsten Cleanup spurlos entfernbar.`,
      });
    }

    // Absenken ist die gefaehrliche Richtung und braucht dieselbe ausdrueckliche
    // Bestaetigung wie der Cleanup selbst.
    const senktAb = neueTage < bisher.retentionDays;
    if (senktAb && req.body?.confirm !== 'SHORTEN') {
      return res.status(400).json({
        success: false,
        error:
          `Das verkürzt die Aufbewahrung von ${bisher.retentionDays} auf ${neueTage} Tage. ` +
          'Beim nächsten Cleanup werden dadurch Einträge endgültig gelöscht, die heute noch ' +
          'da sind. Zum Bestätigen {"confirm":"SHORTEN"} mitsenden.',
      });
    }

    const policy = await auditService.updateRetentionPolicy(id, {
      retentionDays,
      description,
      legalBasis,
      isActive,
    });

    await logChange({
      req, action: 'UPDATE', resourceType: 'RetentionPolicy',
      resourceId: id.toString(),
      // Absenkung wird wie ihre Folge eingestuft: CRITICAL, nicht MEDIUM.
      sensitivity: senktAb ? 'CRITICAL' : undefined,
      label: senktAb
        ? `Aufbewahrung VERKÜRZT für ${bisher.resourceType}` +
          (bisher.sensitivity ? `/${bisher.sensitivity}` : '') +
          `: ${bisher.retentionDays} → ${neueTage} Tage`
        : `Aufbewahrungsrichtlinie aktualisiert`,
      before: { retentionDays: bisher.retentionDays },
      details: { retentionDays: neueTage },
    });

    res.json({ success: true, data: policy });
  } catch (error) {
    console.error('Fehler beim Aktualisieren der Retention-Policy:', error);
    res.status(500).json({ success: false, error: 'Fehler beim Aktualisieren' });
  }
}

/**
 * Retention-Cleanup manuell ausführen
 */
export async function runRetentionCleanup(req: AuthRequest, res: Response) {
  try {
    // Auch hier ausdrueckliche Bestaetigung: Cleanup loescht Audit-Zeilen
    // endgueltig und reisst dabei die Kette auf (Pentest R170-01).
    if (req.body?.confirm !== 'CLEANUP') {
      res.status(400).json({
        success: false,
        error:
          'Cleanup löscht Audit-Einträge endgültig. Zum Bestätigen ' +
          '{"confirm":"CLEANUP"} mitsenden.',
      });
      return;
    }

    const result = await auditService.runRetentionCleanup();

    res.json({
      success: true,
      data: result,
      message: `${result.deletedCount} alte Audit-Logs wurden gelöscht`,
    });
  } catch (error) {
    console.error('Fehler beim Retention-Cleanup:', error);
    res.status(500).json({ success: false, error: 'Fehler beim Cleanup' });
  }
}
