import { AuditAction, AuditSensitivity, Prisma } from '@prisma/client';
import crypto from 'crypto';
import { encrypt, decrypt } from '../utils/encryption.js';
import prisma from '../lib/prisma.js';

/**
 * Vereinfachte Audit-Log-Funktion für gezielte Änderungsprotokolle.
 * Wird direkt in Controllern aufgerufen mit aussagekräftigen Details.
 */
export async function logChange(opts: {
  req: any;                    // Express Request (für userId, email, IP)
  action: AuditAction;
  resourceType: string;
  resourceId?: string;
  label: string;               // Menschenlesbares Label z.B. "Vollmacht für Stefan Hacker widerrufen"
  details?: Record<string, unknown>;  // Zusätzliche Details z.B. { vorher: 'erteilt', nachher: 'widerrufen' }
  customerId?: number;
}) {
  try {
    const user = opts.req?.user;
    await createAuditLog({
      userId: user?.userId,
      userEmail: user?.email || 'system',
      userRole: user?.isCustomerPortal ? 'Kundenportal' : 'Mitarbeiter',
      customerId: user?.customerId,
      isCustomerPortal: user?.isCustomerPortal,
      action: opts.action,
      resourceType: opts.resourceType,
      resourceId: opts.resourceId,
      resourceLabel: opts.label,
      endpoint: opts.req?.path || '',
      httpMethod: opts.req?.method || '',
      ipAddress: opts.req?.socket?.remoteAddress || opts.req?.headers?.['x-forwarded-for'] || 'unknown',
      dataSubjectId: opts.customerId,
      changesAfter: opts.details,
    });
  } catch (error) {
    console.error('[logChange] Fehler:', error);
  }
}

export interface CreateAuditLogData {
  userId?: number;
  userEmail: string;
  userRole?: string;
  customerId?: number;
  isCustomerPortal?: boolean;
  action: AuditAction;
  sensitivity?: AuditSensitivity;
  resourceType: string;
  resourceId?: string;
  resourceLabel?: string;
  endpoint: string;
  httpMethod: string;
  ipAddress: string;
  userAgent?: string;
  changesBefore?: Record<string, unknown>;
  changesAfter?: Record<string, unknown>;
  dataSubjectId?: number;
  legalBasis?: string;
  success?: boolean;
  errorMessage?: string;
  durationMs?: number;
}

export interface AuditLogSearchParams {
  userId?: number;
  customerId?: number;
  dataSubjectId?: number;
  action?: AuditAction;
  sensitivity?: AuditSensitivity;
  resourceType?: string;
  resourceId?: string;
  startDate?: Date;
  endDate?: Date;
  success?: boolean;
  search?: string;
  page?: number;
  limit?: number;
}

/**
 * Generiert einen SHA-256 Hash für einen Audit-Log-Eintrag
 */
function generateHash(data: {
  userEmail: string;
  action: AuditAction;
  resourceType: string;
  resourceId?: string | null;
  endpoint: string;
  createdAt: Date;
  previousHash?: string | null;
}): string {
  // WICHTIG – Pentest R121 (Verify-Fehlalarm, ~73% "manipuliert"):
  // Beim Schreiben war `resourceId` bei middleware-generierten Einträgen
  // oft `undefined`. JSON.stringify LÄSST einen undefined-Wert weg – der
  // Hash wurde also OHNE `resourceId`-Key gebildet. In der DB landet der
  // Wert aber als `NULL`. verifyIntegrity/rehashAll lasen ihn als `null`
  // zurück, und `JSON.stringify({resourceId: null})` schreibt
  // `"resourceId":null` REIN → anderer Hash → falscher Tamper-Alarm für
  // jede Zeile mit leerem resourceId.
  //
  // Fix: nullish resourceId weglassen – reproduziert exakt das historische
  // Schreibverhalten (undefined → Key weg). Kein Caller hat je `null`
  // explizit übergeben (verifiziert), daher matchen ALLE Bestands-Hashes
  // ohne Rehash. Feldreihenfolge bleibt identisch zur Alt-Serialisierung.
  const payload: Record<string, unknown> = {
    userEmail: data.userEmail,
    action: data.action,
    resourceType: data.resourceType,
  };
  if (data.resourceId !== null && data.resourceId !== undefined) {
    payload.resourceId = data.resourceId;
  }
  payload.endpoint = data.endpoint;
  payload.createdAt = data.createdAt.toISOString();
  payload.previousHash = data.previousHash || '';

  return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex');
}

/**
 * Historische Hash-Variante (Bestandsdaten ~08.02.–01.05.2026).
 *
 * Der R121-Fix ging davon aus, dass `resourceId` beim Schreiben immer
 * `undefined` war (→ Key faellt bei JSON.stringify weg) und daher ALLE
 * Bestands-Hashes ohne Rehash matchen. Das stimmt erst ab ~01.05.2026:
 * aeltere Zeilen wurden mit explizitem `null` serialisiert, der Key war also
 * DRIN. Ergebnis war ein Dauer-Fehlalarm ueber ~3100 Zeilen (67 % des Logs) –
 * und ein Alarm, der staendig grundlos ausloest, verdeckt echte Manipulation.
 *
 * Diese Funktion reproduziert exakt das alte Schreibverhalten, damit
 * verifyIntegrity Altbestand als gueltig erkennt – OHNE Rehash. Ein Rehash
 * waere der naheliegende Schnellfix, wuerde die Manipulations-Beweiskraft der
 * Vergangenheit aber unwiederbringlich zerstoeren.
 *
 * Sicherheit: Beide Varianten hashen dieselben Feldwerte, nur die
 * Serialisierung des nullish `resourceId` unterscheidet sich. Ein Angreifer
 * gewinnt dadurch keinen Spielraum, Inhalte zu aendern – nur die Kodierung
 * eines leeren Feldes ist doppelt zulaessig.
 */
function generateHashLegacy(data: {
  userEmail: string;
  action: AuditAction;
  resourceType: string;
  resourceId?: string | null;
  endpoint: string;
  createdAt: Date;
  previousHash?: string | null;
}): string {
  const payload: Record<string, unknown> = {
    userEmail: data.userEmail,
    action: data.action,
    resourceType: data.resourceType,
    resourceId: data.resourceId ?? null,
    endpoint: data.endpoint,
    createdAt: data.createdAt.toISOString(),
    previousHash: data.previousHash || '',
  };

  return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex');
}

/**
 * Hash-Version 2 (Pentest R166-02): deckt ALLE Inhaltsspalten ab.
 *
 * Version 1 hashte nur 7 Felder (userEmail, action, resourceType, resourceId,
 * endpoint, createdAt, previousHash). Nicht abgedeckt waren u. a.
 * `changesBefore`/`changesAfter` – also die eigentliche Nutzlast –, dazu
 * `success`, `ipAddress`, `resourceLabel`, `dataSubjectId`, `userId`,
 * `customerId`. Ein nachtraeglicher Einzeledit an genau diesen Feldern
 * (z. B. `success` false→true oder das Umschreiben von `changesAfter`) war
 * damit unsichtbar – exakt der Angriff, gegen den die Kette schuetzen soll.
 *
 * Bestandszeilen behalten `hashVersion = 1` und werden weiter mit dem alten
 * Verfahren geprueft; ein Rehash waere nicht noetig und wuerde die
 * Beweiskraft der Vergangenheit zerstoeren.
 *
 * `undefined` wird bewusst zu `null` normalisiert, damit die Serialisierung
 * deterministisch ist (die Uneindeutigkeit an dieser Stelle war die Ursache
 * des Dauer-Fehlalarms bei Version 1).
 */
export interface AuditHashV2Input {
  userId?: number | null;
  userEmail: string;
  userRole?: string | null;
  customerId?: number | null;
  isCustomerPortal?: boolean | null;
  action: AuditAction;
  sensitivity?: AuditSensitivity | null;
  resourceType: string;
  resourceId?: string | null;
  resourceLabel?: string | null;
  endpoint: string;
  httpMethod?: string | null;
  ipAddress?: string | null;
  userAgent?: string | null;
  changesBefore?: string | null;
  changesAfter?: string | null;
  changesEncrypted?: boolean | null;
  dataSubjectId?: number | null;
  legalBasis?: string | null;
  success?: boolean | null;
  errorMessage?: string | null;
  durationMs?: number | null;
  createdAt: Date;
  previousHash?: string | null;
}

function generateHashV2(data: AuditHashV2Input): string {
  const n = <T>(v: T | null | undefined): T | null => (v === undefined ? null : v);
  // Feldreihenfolge ist Teil des Hashes und darf nicht veraendert werden.
  const payload = {
    v: 2,
    userId: n(data.userId),
    userEmail: data.userEmail,
    userRole: n(data.userRole),
    customerId: n(data.customerId),
    isCustomerPortal: n(data.isCustomerPortal) ?? false,
    action: data.action,
    sensitivity: n(data.sensitivity),
    resourceType: data.resourceType,
    resourceId: n(data.resourceId),
    resourceLabel: n(data.resourceLabel),
    endpoint: data.endpoint,
    httpMethod: n(data.httpMethod),
    ipAddress: n(data.ipAddress),
    userAgent: n(data.userAgent),
    changesBefore: n(data.changesBefore),
    changesAfter: n(data.changesAfter),
    changesEncrypted: n(data.changesEncrypted) ?? false,
    dataSubjectId: n(data.dataSubjectId),
    legalBasis: n(data.legalBasis),
    success: n(data.success) ?? true,
    errorMessage: n(data.errorMessage),
    durationMs: n(data.durationMs),
    createdAt: data.createdAt.toISOString(),
    previousHash: data.previousHash || '',
  };

  return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex');
}

/**
 * Bestimmt die Sensitivität basierend auf dem Ressourcentyp
 */
function determineSensitivity(resourceType: string): AuditSensitivity {
  const sensitivityMap: Record<string, AuditSensitivity> = {
    // CRITICAL
    Authentication: 'CRITICAL',
    BankCard: 'CRITICAL',
    IdentityDocument: 'CRITICAL',
    // Klartext-Passwort-Reads – jeder Decrypt-Vorgang muss nachvollziehbar sein
    PortalPassword: 'CRITICAL',
    ContractPassword: 'CRITICAL',
    SimCardCredentials: 'CRITICAL',
    InternetCredentials: 'CRITICAL',
    SipCredentials: 'CRITICAL',
    MailboxCredentials: 'CRITICAL',
    // HIGH
    Customer: 'HIGH',
    User: 'HIGH',
    CustomerConsent: 'HIGH',
    DataDeletionRequest: 'HIGH',
    // Abgewehrter IDOR-/Cross-Boundary-Zugriffsversuch (canAccess*-403).
    // HIGH, weil ein Treffer auf gezielte Fremddaten-Enumeration hindeutet.
    AccessDenied: 'HIGH',
    // MEDIUM
    Contract: 'MEDIUM',
    Address: 'MEDIUM',
    Meter: 'MEDIUM',
    MeterReading: 'MEDIUM',
    StressfreiEmail: 'MEDIUM',
    CachedEmail: 'MEDIUM',
    // LOW
    Provider: 'LOW',
    Tariff: 'LOW',
    SalesPlatform: 'LOW',
    AppSetting: 'LOW',
    ContractCategory: 'LOW',
    CancellationPeriod: 'LOW',
    ContractDuration: 'LOW',
  };
  return sensitivityMap[resourceType] || 'MEDIUM';
}

/**
 * Prüft ob Änderungen verschlüsselt werden sollen.
 * Deaktiviert - sensible Felder werden bereits von der Prisma-Middleware als [REDACTED] gefiltert.
 */
function shouldEncryptChanges(_resourceType: string): boolean {
  return false;
}

/**
 * Erstellt einen neuen Audit-Log-Eintrag mit Hash-Kette
 */
export async function createAuditLog(data: CreateAuditLogData): Promise<void> {
  try {
    // Sensitivität bestimmen falls nicht angegeben
    const sensitivity = data.sensitivity || determineSensitivity(data.resourceType);

    // Änderungen serialisieren und ggf. verschlüsseln
    let changesBefore: string | null = null;
    let changesAfter: string | null = null;
    let changesEncrypted = false;

    if (data.changesBefore || data.changesAfter) {
      changesEncrypted = shouldEncryptChanges(data.resourceType);

      if (data.changesBefore) {
        const json = JSON.stringify(data.changesBefore);
        changesBefore = changesEncrypted ? encrypt(json) : json;
      }

      if (data.changesAfter) {
        const json = JSON.stringify(data.changesAfter);
        changesAfter = changesEncrypted ? encrypt(json) : json;
      }
    }

    // Kette atomar fortschreiben: Vorgaenger-Hash lesen UND neuen Eintrag
    // schreiben muessen eine Einheit sein. Vorher lagen beide Schritte offen
    // nebeneinander – zwei parallele Requests lasen denselben letzten Hash und
    // haengten sich beide daran, was die Kette zerriss (echte Bruchstellen im
    // Bestand, u. a. 05.05./07.05.2026).
    //
    // Serialisiert wird ueber eine Zeilensperre auf dem Einzeiler-Mutex
    // `AuditChainLock`. Zwei Alternativen wurden verworfen:
    //  - `SELECT … FOR UPDATE` am Kettenende von AuditLog nimmt Gap-/Next-Key-
    //    Locks, die mit den gleichzeitigen INSERTs kollidieren – gemessen gingen
    //    38 von 40 parallelen Eintraegen durch Deadlocks verloren. Ein FEHLENDER
    //    Audit-Eintrag ist unsichtbar und damit schlimmer als ein sichtbarer
    //    Kettenbruch.
    //  - GET_LOCK muss auf derselben Verbindung freigegeben werden, das Release
    //    fiel damit VOR den COMMIT. In diesem Fenster las der naechste Schreiber
    //    ein noch nicht sichtbares Kettenende und hing sich an denselben
    //    Vorgaenger (Fork, Pentest R166-01).
    // Die Zeilensperre faellt erst beim COMMIT und liegt in der DB – wirkt also
    // auch ueber mehrere App-Instanzen hinweg.
    // Alles Rechenintensive (Serialisieren/Verschluesseln) passiert bewusst
    // VOR der Transaktion, damit die Sperre so kurz wie moeglich gehalten wird.
    await prisma.$transaction(async (tx) => {
      // Exklusive Zeilensperre auf den Einzeiler-Mutex. Sie faellt erst beim
      // COMMIT – dadurch sieht der naechste Schreiber die Vorgaengerzeile
      // garantiert bereits festgeschrieben (Pentest R166-01).
      await tx.$queryRaw`SELECT id FROM AuditChainLock WHERE id = 1 FOR UPDATE`;

      const lastRows = await tx.$queryRaw<Array<{ hash: string }>>`
        SELECT hash FROM AuditLog ORDER BY id DESC LIMIT 1
      `;
      const previousHash = lastRows[0]?.hash || null;
      const createdAt = new Date();

      // Neue Eintraege immer mit Version 2 (volle Feldabdeckung, R166-02).
      const hash = generateHashV2({
        userId: data.userId,
        userEmail: data.userEmail,
        userRole: data.userRole,
        customerId: data.customerId,
        isCustomerPortal: data.isCustomerPortal || false,
        action: data.action,
        sensitivity,
        resourceType: data.resourceType,
        resourceId: data.resourceId,
        resourceLabel: data.resourceLabel,
        endpoint: data.endpoint,
        httpMethod: data.httpMethod,
        ipAddress: data.ipAddress,
        userAgent: data.userAgent,
        changesBefore,
        changesAfter,
        changesEncrypted,
        dataSubjectId: data.dataSubjectId,
        legalBasis: data.legalBasis,
        success: data.success ?? true,
        errorMessage: data.errorMessage,
        durationMs: data.durationMs,
        createdAt,
        previousHash,
      });

      await tx.auditLog.create({
        data: {
          userId: data.userId,
          userEmail: data.userEmail,
          userRole: data.userRole,
          customerId: data.customerId,
          isCustomerPortal: data.isCustomerPortal || false,
          action: data.action,
          sensitivity,
          resourceType: data.resourceType,
          resourceId: data.resourceId,
          resourceLabel: data.resourceLabel,
          endpoint: data.endpoint,
          httpMethod: data.httpMethod,
          ipAddress: data.ipAddress,
          userAgent: data.userAgent,
          changesBefore,
          changesAfter,
          changesEncrypted,
          dataSubjectId: data.dataSubjectId,
          legalBasis: data.legalBasis,
          success: data.success ?? true,
          errorMessage: data.errorMessage,
          durationMs: data.durationMs,
          createdAt,
          hash,
          previousHash,
          hashVersion: 2,
        },
      });
    }, {
      // READ COMMITTED: der Lesevorgang nach der Sperre muss den gerade
      // festgeschriebenen Stand sehen. Unter REPEATABLE READ koennte ein
      // Snapshot greifen, der die Vorgaengerzeile noch nicht enthaelt.
      isolationLevel: 'ReadCommitted',
      timeout: 20000,
      maxWait: 15000,
    });
  } catch (error) {
    // Audit-Logging darf niemals die Hauptoperation blockieren
    console.error('[AuditService] Fehler beim Erstellen des Audit-Logs:', error);
  }
}

/**
 * Sucht Audit-Logs mit Filtern und Paginierung
 */
export async function searchAuditLogs(params: AuditLogSearchParams) {
  const {
    userId,
    customerId,
    dataSubjectId,
    action,
    sensitivity,
    resourceType,
    resourceId,
    startDate,
    endDate,
    success,
    search,
    page = 1,
    limit = 50,
  } = params;

  const where: Prisma.AuditLogWhereInput = {};

  if (userId !== undefined) where.userId = userId;
  if (customerId !== undefined) where.customerId = customerId;
  if (dataSubjectId !== undefined) where.dataSubjectId = dataSubjectId;
  if (action) where.action = action;
  if (sensitivity) where.sensitivity = sensitivity;
  if (resourceType) where.resourceType = resourceType;
  if (resourceId) where.resourceId = resourceId;
  if (success !== undefined) where.success = success;

  if (startDate || endDate) {
    where.createdAt = {};
    if (startDate) where.createdAt.gte = startDate;
    if (endDate) where.createdAt.lte = endDate;
  }

  if (search) {
    where.OR = [
      { userEmail: { contains: search } },
      { resourceLabel: { contains: search } },
      { endpoint: { contains: search } },
    ];
  }

  const [logs, total] = await Promise.all([
    prisma.auditLog.findMany({
      where,
      orderBy: { createdAt: 'desc' },
      skip: (page - 1) * limit,
      take: limit,
    }),
    prisma.auditLog.count({ where }),
  ]);

  // Entschlüsselung der Änderungen wenn nötig
  const decryptedLogs = logs.map((log) => ({
    ...log,
    changesBefore: log.changesBefore && log.changesEncrypted
      ? JSON.parse(decrypt(log.changesBefore))
      : log.changesBefore ? JSON.parse(log.changesBefore) : null,
    changesAfter: log.changesAfter && log.changesEncrypted
      ? JSON.parse(decrypt(log.changesAfter))
      : log.changesAfter ? JSON.parse(log.changesAfter) : null,
  }));

  return {
    data: decryptedLogs,
    pagination: {
      page,
      limit,
      total,
      totalPages: Math.ceil(total / limit),
    },
  };
}

/**
 * Holt einen einzelnen Audit-Log-Eintrag
 */
export async function getAuditLogById(id: number) {
  const log = await prisma.auditLog.findUnique({
    where: { id },
  });

  if (!log) return null;

  return {
    ...log,
    changesBefore: log.changesBefore && log.changesEncrypted
      ? JSON.parse(decrypt(log.changesBefore))
      : log.changesBefore ? JSON.parse(log.changesBefore) : null,
    changesAfter: log.changesAfter && log.changesEncrypted
      ? JSON.parse(decrypt(log.changesAfter))
      : log.changesAfter ? JSON.parse(log.changesAfter) : null,
  };
}

/**
 * Holt alle Audit-Logs für eine betroffene Person (DSGVO)
 */
export async function getAuditLogsByDataSubject(customerId: number) {
  const logs = await prisma.auditLog.findMany({
    where: { dataSubjectId: customerId },
    orderBy: { createdAt: 'desc' },
  });

  return logs.map((log) => ({
    ...log,
    changesBefore: log.changesBefore && log.changesEncrypted
      ? JSON.parse(decrypt(log.changesBefore))
      : log.changesBefore ? JSON.parse(log.changesBefore) : null,
    changesAfter: log.changesAfter && log.changesEncrypted
      ? JSON.parse(decrypt(log.changesAfter))
      : log.changesAfter ? JSON.parse(log.changesAfter) : null,
  }));
}

/**
 * Verifiziert die Integrität der Hash-Kette
 */
export async function verifyIntegrity(fromId?: number, toId?: number): Promise<{
  valid: boolean;
  checkedCount: number;
  /** Alle beanstandeten Zeilen (tampered + chainGaps) – Abwaertskompatibilitaet. */
  invalidEntries: number[];
  /**
   * ERNST: Der Inhalt der Zeile passt nicht mehr zu ihrem Hash – jemand hat
   * einen bestehenden Eintrag nachtraeglich veraendert.
   */
  tamperedEntries: number[];
  /**
   * MEIST HARMLOS: Der Inhalt stimmt, aber die Verkettung zur Vorgaengerzeile
   * passt nicht. Ursachen: parallel geschriebene Eintraege (bis zum Fix der
   * Race-Condition) oder geloeschte Zeilen (Retention-Cleanup). Kein Hinweis
   * auf Manipulation – die Zeilen selbst sind unveraendert.
   */
  chainGaps: number[];
}> {
  const where: Prisma.AuditLogWhereInput = {};

  if (fromId !== undefined) where.id = { gte: fromId };
  if (toId !== undefined) where.id = { ...(where.id as object || {}), lte: toId };

  const logs = await prisma.auditLog.findMany({
    where,
    orderBy: { id: 'asc' },
    // Version 2 hasht alle Inhaltsspalten – daher vollstaendig laden.
    select: {
      id: true,
      userId: true,
      userEmail: true,
      userRole: true,
      customerId: true,
      isCustomerPortal: true,
      action: true,
      sensitivity: true,
      resourceType: true,
      resourceId: true,
      resourceLabel: true,
      endpoint: true,
      httpMethod: true,
      ipAddress: true,
      userAgent: true,
      changesBefore: true,
      changesAfter: true,
      changesEncrypted: true,
      dataSubjectId: true,
      legalBasis: true,
      success: true,
      errorMessage: true,
      durationMs: true,
      createdAt: true,
      hash: true,
      previousHash: true,
      hashVersion: true,
    },
  });

  const tamperedEntries: number[] = [];
  const chainGaps: number[] = [];

  for (let i = 0; i < logs.length; i++) {
    const log = logs[i];

    // Pruefverfahren richtet sich nach der Version, mit der geschrieben wurde.
    // Version 2 deckt alle Inhaltsspalten ab; Version 1 nur 7 Felder und darf
    // zusaetzlich die historische Serialisierung nutzen (generateHashLegacy).
    let hashOk: boolean;
    if (log.hashVersion >= 2) {
      hashOk = log.hash === generateHashV2({
        userId: log.userId,
        userEmail: log.userEmail,
        userRole: log.userRole,
        customerId: log.customerId,
        isCustomerPortal: log.isCustomerPortal,
        action: log.action,
        sensitivity: log.sensitivity,
        resourceType: log.resourceType,
        resourceId: log.resourceId,
        resourceLabel: log.resourceLabel,
        endpoint: log.endpoint,
        httpMethod: log.httpMethod,
        ipAddress: log.ipAddress,
        userAgent: log.userAgent,
        changesBefore: log.changesBefore,
        changesAfter: log.changesAfter,
        changesEncrypted: log.changesEncrypted,
        dataSubjectId: log.dataSubjectId,
        legalBasis: log.legalBasis,
        success: log.success,
        errorMessage: log.errorMessage,
        durationMs: log.durationMs,
        createdAt: log.createdAt,
        previousHash: log.previousHash,
      });
    } else {
      const v1 = {
        userEmail: log.userEmail,
        action: log.action,
        resourceType: log.resourceType,
        resourceId: log.resourceId,
        endpoint: log.endpoint,
        createdAt: log.createdAt,
        previousHash: log.previousHash,
      };
      hashOk = log.hash === generateHash(v1) || log.hash === generateHashLegacy(v1);
    }

    if (!hashOk) {
      tamperedEntries.push(log.id);
      continue;
    }

    // Prüfen ob previousHash mit dem Hash des vorherigen Eintrags übereinstimmt
    if (i > 0) {
      const previousLog = logs[i - 1];
      if (log.previousHash !== previousLog.hash) {
        chainGaps.push(log.id);
      }
    }
  }

  const invalidEntries = [...tamperedEntries, ...chainGaps].sort((a, b) => a - b);

  return {
    valid: invalidEntries.length === 0,
    checkedCount: logs.length,
    invalidEntries,
    tamperedEntries,
    chainGaps,
  };
}

/**
 * Hash-Kette komplett neu berechnen (Reparatur)
 */
export async function rehashAll(): Promise<{ rehashedCount: number }> {
  const logs = await prisma.auditLog.findMany({
    orderBy: { id: 'asc' },
    select: {
      id: true,
      userId: true,
      userEmail: true,
      userRole: true,
      customerId: true,
      isCustomerPortal: true,
      action: true,
      sensitivity: true,
      resourceType: true,
      resourceId: true,
      resourceLabel: true,
      endpoint: true,
      httpMethod: true,
      ipAddress: true,
      userAgent: true,
      changesBefore: true,
      changesAfter: true,
      changesEncrypted: true,
      dataSubjectId: true,
      legalBasis: true,
      success: true,
      errorMessage: true,
      durationMs: true,
      createdAt: true,
    },
  });

  let previousHash: string | null = null;
  let count = 0;

  for (const log of logs) {
    // Rehash schreibt immer Version 2 (volle Feldabdeckung) – ein Rueckfall
    // auf Version 1 wuerde die Abdeckung nachtraeglich wieder verkleinern.
    const hash = generateHashV2({ ...log, previousHash });

    await prisma.auditLog.update({
      where: { id: log.id },
      data: { hash, previousHash, hashVersion: 2 },
    });

    previousHash = hash;
    count++;
  }

  // Marker: Ein Rehash setzt die Beweiskraft der Vergangenheit zurueck (jede
  // vorhandene Faelschung wuerde mitbesiegelt). Der Vorgang muss deshalb im
  // Log selbst sichtbar sein. Der Eintrag wird NACH dem Rehash geschrieben und
  // haengt sich an die neu berechnete Kette; entfernen liesse er sich nur unter
  // Hinterlassung einer Luecke.
  await createAuditLog({
    userEmail: 'system',
    userRole: 'System',
    action: 'UPDATE',
    resourceType: 'AuditLog',
    resourceLabel: `Hash-Kette neu berechnet (${count} Einträge) – Beweiskraft der Vergangenheit zurückgesetzt`,
    endpoint: '/api/audit-logs/rehash',
    httpMethod: 'POST',
    ipAddress: 'system',
    sensitivity: 'CRITICAL',
    success: true,
  });

  return { rehashedCount: count };
}

/**
 * Exportiert Audit-Logs als JSON oder CSV
 */
export async function exportAuditLogs(
  params: AuditLogSearchParams,
  format: 'json' | 'csv' = 'json'
): Promise<string> {
  // Alle Logs ohne Paginierung
  const result = await searchAuditLogs({ ...params, limit: 100000, page: 1 });
  const logs = result.data;

  if (format === 'json') {
    return JSON.stringify(logs, null, 2);
  }

  // CSV Export
  const headers = [
    'ID',
    'Zeitstempel',
    'Benutzer',
    'Aktion',
    'Ressource',
    'Ressource-ID',
    'Bezeichnung',
    'Endpoint',
    'IP-Adresse',
    'Erfolg',
    'Sensitivität',
  ];

  const rows = logs.map((log) => [
    log.id.toString(),
    log.createdAt.toISOString(),
    log.userEmail,
    log.action,
    log.resourceType,
    log.resourceId || '',
    log.resourceLabel || '',
    log.endpoint,
    log.ipAddress,
    log.success ? 'Ja' : 'Nein',
    log.sensitivity,
  ]);

  const csvContent = [
    headers.join(';'),
    ...rows.map((row) => row.map((cell) => `"${cell.replace(/"/g, '""')}"`).join(';')),
  ].join('\n');

  return csvContent;
}

/**
 * Löscht alte Audit-Logs basierend auf Retention-Policies
 * Hinweis: Diese Funktion sollte nur von einem autorisierten Admin-Prozess aufgerufen werden
 */
export async function runRetentionCleanup(): Promise<{
  deletedCount: number;
  policies: Array<{ resourceType: string; sensitivity: string | null; deletedCount: number }>;
}> {
  const policies = await prisma.auditRetentionPolicy.findMany({
    where: { isActive: true },
  });

  const results: Array<{ resourceType: string; sensitivity: string | null; deletedCount: number }> = [];
  let totalDeleted = 0;

  // Loeschungs-Manifest (Pentest R166, Design): Jede geloeschte Zeile reisst die
  // Hash-Kette auf. Ohne Nachweis, WELCHE Bereiche legitim entfernt wurden,
  // koennte sich eine boeswillige Loeschung als "harmloser Gap" tarnen. Deshalb
  // halten wir Bereich + Anzahl fest und schreiben sie als eigenen, selbst
  // wieder verketteten Audit-Eintrag. Luecken ausserhalb dieser Bereiche
  // bleiben damit erklaerungsbeduerftig.
  const manifest: Array<Record<string, unknown>> = [];

  for (const policy of policies) {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - policy.retentionDays);

    const where: Prisma.AuditLogWhereInput = {
      createdAt: { lt: cutoffDate },
    };

    if (policy.resourceType !== '*') {
      where.resourceType = policy.resourceType;
    }

    if (policy.sensitivity) {
      where.sensitivity = policy.sensitivity;
    }

    // Betroffenen ID-Bereich VOR dem Loeschen festhalten.
    const range = await prisma.auditLog.aggregate({
      where,
      _count: true,
      _min: { id: true },
      _max: { id: true },
    });

    const deleted = await prisma.auditLog.deleteMany({ where });

    if (deleted.count > 0) {
      manifest.push({
        resourceType: policy.resourceType,
        sensitivity: policy.sensitivity,
        retentionDays: policy.retentionDays,
        cutoff: cutoffDate.toISOString(),
        deletedCount: deleted.count,
        fromId: range._min.id,
        toId: range._max.id,
      });
    }

    results.push({
      resourceType: policy.resourceType,
      sensitivity: policy.sensitivity,
      deletedCount: deleted.count,
    });

    totalDeleted += deleted.count;
  }

  if (totalDeleted > 0) {
    await createAuditLog({
      userEmail: 'system',
      userRole: 'System',
      action: 'DELETE',
      resourceType: 'AuditLog',
      resourceLabel: `Retention-Cleanup: ${totalDeleted} Audit-Einträge gelöscht`,
      endpoint: '/api/audit-logs/cleanup',
      httpMethod: 'POST',
      ipAddress: 'system',
      sensitivity: 'CRITICAL',
      changesAfter: { manifest },
      success: true,
    });
  }

  return {
    deletedCount: totalDeleted,
    policies: results,
  };
}

/**
 * Holt die Retention-Policies
 */
export async function getRetentionPolicies() {
  return prisma.auditRetentionPolicy.findMany({
    orderBy: [{ resourceType: 'asc' }, { sensitivity: 'asc' }],
  });
}

/**
 * Aktualisiert eine Retention-Policy
 */
export async function updateRetentionPolicy(
  id: number,
  data: { retentionDays?: number; description?: string; legalBasis?: string; isActive?: boolean }
) {
  return prisma.auditRetentionPolicy.update({
    where: { id },
    data,
  });
}
