import { Response, NextFunction } from 'express';
import { AuditAction } from '@prisma/client';
import { AuthRequest } from '../types/index.js';
import { createAuditLog } from '../services/audit.service.js';
import { getAuditContext, AuditContext } from './auditContext.js';

// Resource-Typ-Mapping basierend auf Route-Patterns
const RESOURCE_MAPPING: Record<string, { type: string; extractId?: (req: AuthRequest) => string | undefined }> = {
  '/api/customers': { type: 'Customer', extractId: (req) => req.params.id || req.params.customerId },
  '/api/customers/*/bank-cards': { type: 'BankCard', extractId: (req) => req.params.bankCardId },
  '/api/customers/*/documents': { type: 'IdentityDocument', extractId: (req) => req.params.documentId },
  '/api/customers/*/addresses': { type: 'Address', extractId: (req) => req.params.addressId },
  '/api/customers/*/meters': { type: 'Meter', extractId: (req) => req.params.meterId },
  '/api/customers/*/consents': { type: 'CustomerConsent', extractId: (req) => req.params.type },
  '/api/contracts': { type: 'Contract', extractId: (req) => req.params.id },
  '/api/contracts/*/history': { type: 'ContractHistoryEntry', extractId: (req) => req.params.entryId },
  '/api/contracts/*/tasks': { type: 'ContractTask', extractId: (req) => req.params.taskId },
  '/api/users': { type: 'User', extractId: (req) => req.params.id },
  '/api/providers': { type: 'Provider', extractId: (req) => req.params.id },
  '/api/tariffs': { type: 'Tariff', extractId: (req) => req.params.id },
  '/api/platforms': { type: 'SalesPlatform', extractId: (req) => req.params.id },
  '/api/contract-categories': { type: 'ContractCategory', extractId: (req) => req.params.id },
  '/api/cancellation-periods': { type: 'CancellationPeriod', extractId: (req) => req.params.id },
  '/api/contract-durations': { type: 'ContractDuration', extractId: (req) => req.params.id },
  '/api/settings': { type: 'AppSetting', extractId: (req) => req.params.key },
  '/api/email-providers': { type: 'EmailProviderConfig', extractId: (req) => req.params.id },
  '/api/meters': { type: 'Meter', extractId: (req) => req.params.id || req.params.meterId },
  '/api/upload': { type: 'Upload' },
  '/api/email-logs': { type: 'EmailLog' },
  '/api/auth': { type: 'Authentication' },
  '/api/audit-logs': { type: 'AuditLog', extractId: (req) => req.params.id },
  '/api/gdpr': { type: 'GDPR' },
};

// Routen die nicht geloggt werden sollen
const EXCLUDED_ROUTES = [
  '/api/health',
  '/api/uploads',
];

/**
 * Bestimmt die Aktion basierend auf HTTP-Methode und Erfolg
 */
function determineAction(method: string, path: string, success: boolean): AuditAction {
  // Spezielle Auth-Aktionen
  if (path.includes('/auth/login')) {
    return success ? 'LOGIN' : 'LOGIN_FAILED';
  }
  if (path.includes('/auth/logout')) {
    return 'LOGOUT';
  }
  // Stiller Token-Refresh (Cookie) – kein interaktiver Login, eigene Action.
  // Erfolg vs. Fehlschlag wird NICHT über die Action getrennt (semantisch beides
  // ein Refresh), sondern downstream über success-Flag + Sensitivität (LOW/HIGH)
  // und den TOKEN_REJECTED-SecurityEvent im Controller (Pentest R164-01).
  if (path.includes('/auth/refresh')) {
    return 'TOKEN_REFRESH';
  }

  // Standard CRUD-Aktionen
  switch (method.toUpperCase()) {
    case 'GET':
      return 'READ';
    case 'POST':
      return 'CREATE';
    case 'PUT':
    case 'PATCH':
      return 'UPDATE';
    case 'DELETE':
      return 'DELETE';
    default:
      return 'READ';
  }
}

/**
 * Findet den passenden Resource-Typ für einen Pfad
 */
function findResourceMapping(path: string): { type: string; extractId?: (req: AuthRequest) => string | undefined } | null {
  // Exakte Matches zuerst prüfen
  for (const [pattern, mapping] of Object.entries(RESOURCE_MAPPING)) {
    // Konvertiere Pattern zu Regex
    const regexPattern = pattern
      .replace(/\*/g, '[^/]+')
      .replace(/\//g, '\\/');
    const regex = new RegExp(`^${regexPattern}(?:/|$)`);

    if (regex.test(path)) {
      return mapping;
    }
  }
  return null;
}

/**
 * Extrahiert die betroffene Kunden-ID für DSGVO-Tracking
 */
function extractDataSubjectId(req: AuthRequest, fullPath: string): number | undefined {
  // Aus Route-Parameter
  const customerId = req.params.customerId || req.params.id;
  if (customerId && fullPath.includes('/customers')) {
    return parseInt(customerId);
  }

  // Aus Request-Body (bei Create)
  if (req.body?.customerId) {
    return parseInt(req.body.customerId);
  }

  // Bei Kundenportal-Zugriff
  if (req.user?.customerId) {
    return req.user.customerId;
  }

  return undefined;
}

/**
 * Extrahiert die IP-Adresse des Clients
 */
function getClientIp(req: AuthRequest): string {
  const forwarded = req.headers['x-forwarded-for'];
  if (typeof forwarded === 'string') {
    return forwarded.split(',')[0].trim();
  }
  return req.socket.remoteAddress || 'unknown';
}

// Menschenlesbare Bezeichnungen für Resource-Typen
const RESOURCE_TYPE_LABELS: Record<string, string> = {
  Customer: 'Kunde',
  Contract: 'Vertrag',
  BankCard: 'Bankverbindung',
  IdentityDocument: 'Ausweis',
  Address: 'Adresse',
  Meter: 'Zähler',
  MeterReading: 'Zählerstand',
  User: 'Benutzer',
  Provider: 'Anbieter',
  Tariff: 'Tarif',
  SalesPlatform: 'Vertriebsplattform',
  ContractCategory: 'Vertragskategorie',
  CancellationPeriod: 'Kündigungsfrist',
  ContractDuration: 'Vertragslaufzeit',
  EmailProviderConfig: 'E-Mail-Provider',
  AppSetting: 'Einstellung',
  CustomerConsent: 'Einwilligung',
  ContractTask: 'Aufgabe',
  ContractHistoryEntry: 'Vertragshistorie',
  GDPR: 'Datenschutz',
  Authentication: 'Anmeldung',
  AuditLog: 'Audit-Protokoll',
  StressfreiEmail: 'Stressfrei-E-Mail',
  CachedEmail: 'E-Mail',
};

const ACTION_LABELS: Record<string, string> = {
  CREATE: 'erstellt',
  READ: 'aufgerufen',
  UPDATE: 'aktualisiert',
  DELETE: 'gelöscht',
  EXPORT: 'exportiert',
  ANONYMIZE: 'anonymisiert',
  LOGIN: 'angemeldet',
  LOGOUT: 'abgemeldet',
  LOGIN_FAILED: 'Anmeldung fehlgeschlagen',
  TOKEN_REFRESH: 'Sitzung verlängert',
};

/**
 * Ist das die planmaessige Anmeldung eines Dienstkontos? (Pentest R182)
 *
 * Vorhersagbare Ereignisse sind miserable Alarme und exzellente Grundlinien.
 * Das Gegenbuch meldet sich stuendlich an – als CRITICAL gefuehrt trainiert
 * genau diese Routine den Betreiber darauf, die hoechste Stufe wegzuklicken,
 * und der erste ECHTE Vorfall erbt den Reflex. Der Eintrag bleibt (man soll
 * sehen, dass das Gegenbuch arbeitet), aber als Routine.
 */
function istDienstkontoAnmeldung(responseBody: unknown, success: boolean): boolean {
  if (!success || !responseBody || typeof responseBody !== 'object') return false;
  const data = (responseBody as { data?: { user?: { isServiceAccount?: boolean } } }).data;
  return data?.user?.isServiceAccount === true;
}

/**
 * Unterscheidet die drei Ausgaenge von POST /auth/refresh (Pentest R166-03).
 *
 * "Kein Cookie vorhanden" ist KEIN Ablehnungsfall: Der Frontend-Interceptor
 * ruft /refresh beim App-Start auch dann, wenn nie ein Token gesetzt war
 * (normaler Erstbesuch). Das als HIGH/"abgelehnt" zu fuehren erzeugt genau das
 * Rauschen, das mit der Entrauschung beseitigt werden sollte.
 */
function refreshOutcome(responseBody: unknown, success: boolean): 'ok' | 'no-token' | 'rejected' {
  if (success) return 'ok';
  const err = responseBody && typeof responseBody === 'object' && 'error' in responseBody
    ? String((responseBody as { error?: unknown }).error ?? '')
    : '';
  return /kein refresh-token/i.test(err) ? 'no-token' : 'rejected';
}

/**
 * Erzeugt ein menschenlesbares Label für den Audit-Log-Eintrag
 */
function generateHumanLabel(
  action: AuditAction,
  resourceType: string,
  req: AuthRequest,
  responseBody: unknown,
  fullPath: string
): string {
  const typeName = RESOURCE_TYPE_LABELS[resourceType] || resourceType;
  const actionName = ACTION_LABELS[action] || action;

  // Identifikator aus Response oder Request extrahieren
  let identifier = '';
  if (responseBody && typeof responseBody === 'object' && 'data' in responseBody) {
    const data = (responseBody as { data: Record<string, unknown> }).data;
    if (data) {
      identifier =
        (data.contractNumber as string) ||
        (data.customerNumber as string) ||
        (data.meterNumber as string) ||
        (data.name as string) ||
        (data.email as string) ||
        (data.firstName && data.lastName ? `${data.firstName} ${data.lastName}` : '') ||
        '';
    }
  }

  // Spezial-Labels für bestimmte Endpunkte
  // (fullPath statt req.path – siehe Hinweis in auditMiddleware, Pentest R165)
  const path = fullPath;

  // Auth
  if (path.includes('/auth/login') || path.includes('/auth/customer-login')) {
    const email = req.body?.email || '';
    if (action === 'LOGIN' && istDienstkontoAnmeldung(responseBody, true)) {
      return `Dienstkonto ${email} angemeldet (planmäßig)`;
    }
    return action === 'LOGIN'
      ? `Benutzer ${email} hat sich angemeldet`
      : `Anmeldung fehlgeschlagen für ${email}`;
  }
  if (path.includes('/auth/logout')) return 'Benutzer hat sich abgemeldet';
  if (path.includes('/auth/refresh')) {
    const success = !(responseBody && typeof responseBody === 'object' && (responseBody as { success?: boolean }).success === false);
    switch (refreshOutcome(responseBody, success)) {
      case 'ok': return 'Sitzung verlängert (Token erneuert)';
      case 'no-token': return 'Token-Refresh ohne vorliegenden Token (kein Cookie)';
      default: return 'Token-Refresh abgelehnt (ungültig/abgelaufen)';
    }
  }

  // Kunden-Operationen
  if (resourceType === 'Customer') {
    if (action === 'CREATE') return `Kunde ${identifier} angelegt`;
    if (action === 'UPDATE') return `Kundendaten ${identifier} aktualisiert`;
    if (action === 'DELETE') return `Kunde ${identifier} gelöscht`;
    if (action === 'READ' && req.params.id) return `Kundendaten ${identifier} aufgerufen`;
    if (action === 'READ') return 'Kundenliste aufgerufen';
  }

  // Verträge
  if (resourceType === 'Contract') {
    if (path.includes('/cockpit')) return 'Vertrags-Cockpit aufgerufen';
    if (path.includes('/follow-up')) return `Folgevertrag für ${identifier} erstellt`;
    if (path.includes('/snooze')) return `Vertrag ${identifier} zurückgestellt`;
    if (path.includes('/password')) return `Passwort für Vertrag ${identifier} abgerufen`;
    if (path.includes('/sip-credentials')) return 'SIP-Zugangsdaten abgerufen';
    if (path.includes('/internet-credentials')) return `Internet-Zugangsdaten für Vertrag ${identifier} abgerufen`;
    if (path.includes('/successor-meter')) return `Folgezähler zu Vertrag ${identifier} hinzugefügt`;
    if (action === 'CREATE') return `Vertrag ${identifier} angelegt`;
    if (action === 'UPDATE') return `Vertrag ${identifier} aktualisiert`;
    if (action === 'DELETE') return `Vertrag ${identifier} gelöscht`;
    if (action === 'READ' && req.params.id) return `Vertrag ${identifier} aufgerufen`;
    if (action === 'READ') return 'Vertragsliste aufgerufen';
  }

  // Bankverbindungen
  if (resourceType === 'BankCard') {
    if (action === 'CREATE') return `Bankverbindung hinzugefügt`;
    if (action === 'UPDATE') return `Bankverbindung aktualisiert`;
    if (action === 'DELETE') return `Bankverbindung gelöscht`;
  }

  // Ausweise
  if (resourceType === 'IdentityDocument') {
    if (action === 'CREATE') return `Ausweis ${identifier} hinzugefügt`;
    if (action === 'UPDATE') return `Ausweis ${identifier} aktualisiert`;
    if (action === 'DELETE') return `Ausweis gelöscht`;
  }

  // Adressen
  if (resourceType === 'Address') {
    if (action === 'CREATE') return `Adresse hinzugefügt`;
    if (action === 'UPDATE') return `Adresse aktualisiert`;
    if (action === 'DELETE') return `Adresse gelöscht`;
  }

  // Zähler
  if (resourceType === 'Meter') {
    if (action === 'CREATE') return `Zähler ${identifier} angelegt`;
    if (action === 'UPDATE') return `Zähler ${identifier} aktualisiert`;
    if (action === 'DELETE') return `Zähler gelöscht`;
  }

  // Einwilligungen
  if (resourceType === 'CustomerConsent') {
    const consentType = req.params.consentType || '';
    const consentLabels: Record<string, string> = {
      DATA_PROCESSING: 'Datenverarbeitung',
      MARKETING_EMAIL: 'E-Mail-Marketing',
      MARKETING_PHONE: 'Telefonmarketing',
      DATA_SHARING_PARTNER: 'Datenweitergabe',
    };
    const consentName = consentLabels[consentType] || consentType;
    if (action === 'UPDATE') {
      const status = req.body?.status;
      return status === 'GRANTED'
        ? `Einwilligung "${consentName}" erteilt`
        : `Einwilligung "${consentName}" widerrufen`;
    }
    if (action === 'READ') return 'Einwilligungen abgerufen';
  }

  // Benutzer
  if (resourceType === 'User') {
    if (action === 'CREATE') return `Benutzer ${identifier} angelegt`;
    if (action === 'UPDATE') return `Benutzer ${identifier} aktualisiert`;
    if (action === 'DELETE') return `Benutzer ${identifier} gelöscht`;
    if (action === 'READ' && req.params.id) return `Benutzerdaten ${identifier} aufgerufen`;
    if (action === 'READ') return 'Benutzerliste aufgerufen';
  }

  // Aufgaben
  if (resourceType === 'ContractTask') {
    if (path.includes('/complete')) return `Aufgabe als erledigt markiert`;
    if (action === 'CREATE') return `Aufgabe erstellt`;
    if (action === 'UPDATE') return `Aufgabe aktualisiert`;
    if (action === 'DELETE') return `Aufgabe gelöscht`;
  }

  // E-Mail-Provider
  if (resourceType === 'EmailProviderConfig') {
    if (path.includes('/test-connection')) return `E-Mail-Provider Verbindungstest`;
    if (path.includes('/provision')) return `E-Mail-Adresse provisioniert`;
    if (action === 'CREATE') return `E-Mail-Provider ${identifier} angelegt`;
    if (action === 'UPDATE') return `E-Mail-Provider ${identifier} aktualisiert`;
    if (action === 'DELETE') return `E-Mail-Provider ${identifier} gelöscht`;
  }

  // GDPR
  if (resourceType === 'GDPR') {
    if (path.includes('/dashboard')) return 'DSGVO-Dashboard aufgerufen';
    if (path.includes('/export')) return 'Kundendaten exportiert (DSGVO Art. 15)';
    if (path.includes('/privacy-policy')) {
      return action === 'UPDATE' ? 'Datenschutzerklärung aktualisiert' : 'Datenschutzerklärung aufgerufen';
    }
    if (path.includes('/authorization-template')) {
      return action === 'UPDATE' ? 'Vollmacht-Vorlage aktualisiert' : 'Vollmacht-Vorlage aufgerufen';
    }
    if (path.includes('/send-consent-link')) return 'Datenschutz-Link versendet';
    if (path.includes('/authorizations') && path.includes('/send')) return 'Vollmacht-Anfrage versendet';
    if (path.includes('/authorizations') && path.includes('/grant')) return 'Vollmacht erteilt';
    if (path.includes('/authorizations') && path.includes('/withdraw')) return 'Vollmacht widerrufen';
    if (path.includes('/authorizations') && path.includes('/upload')) return 'Vollmacht-PDF hochgeladen';
    if (path.includes('/authorizations') && path.includes('/document') && action === 'DELETE') return 'Vollmacht-PDF gelöscht';
    if (path.includes('/my-privacy')) return 'Eigene Datenschutzseite aufgerufen';
    if (path.includes('/my-consent-status')) return 'Eigener Einwilligungsstatus geprüft';
    if (path.includes('/my-authorizations')) return 'Eigene Vollmachten aufgerufen';
    if (path.includes('/deletions')) {
      if (action === 'CREATE') return 'Löschanfrage erstellt';
      if (path.includes('/process')) return 'Löschanfrage bearbeitet';
      return 'Löschanfragen aufgerufen';
    }
    if (path.includes('/consent-status')) return 'Einwilligungsstatus geprüft';
    if (path.includes('/consents/overview')) return 'Einwilligungsübersicht aufgerufen';
  }

  // Einstellungen
  if (resourceType === 'AppSetting') {
    if (action === 'UPDATE') return `Einstellung "${req.params.key || ''}" geändert`;
    if (action === 'READ') return 'Einstellungen aufgerufen';
  }

  // Zähler-Readings
  if (path.includes('/readings')) {
    if (path.includes('/report')) return 'Zählerstand vom Kunden gemeldet';
    if (path.includes('/transfer')) return 'Zählerstand als übertragen markiert';
    if (action === 'CREATE') return 'Zählerstand erfasst';
    if (action === 'UPDATE') return 'Zählerstand aktualisiert';
    if (action === 'DELETE') return 'Zählerstand gelöscht';
  }

  // Upload-Operationen
  if (path.includes('/upload') || path.includes('/privacy-policy')) {
    if (path.includes('/privacy-policy') && action === 'DELETE') return 'Datenschutzerklärung-PDF gelöscht';
    if (path.includes('/privacy-policy')) return 'Datenschutzerklärung-PDF hochgeladen';
  }

  // Standard-Fallback
  if (identifier) {
    return `${typeName} ${identifier} ${actionName}`;
  }
  return `${typeName} ${actionName}`;
}

/**
 * Audit Middleware - loggt alle API-Aufrufe asynchron
 */
export function auditMiddleware(req: AuthRequest, res: Response, next: NextFunction): void {
  const startTime = Date.now();

  // WICHTIG (Pentest R165): `req.path` ist im res.on('finish')-Handler NICHT mehr der
  // volle Pfad. Express strippt beim Router-Dispatch den Mount-Prefix aus `req.url`
  // und stellt ihn nur beim `next()`-Durchlauf wieder her – ein Handler, der die
  // Response terminiert (res.json()), ruft nie `next()`, also bleibt `req.path`
  // router-relativ (`/refresh` statt `/api/auth/refresh`). Deshalb den vollen Pfad
  // EINMAL hier synchron festhalten und downstream ausschliesslich diesen nutzen –
  // sonst matchen alle Pfad-Checks (/auth/login, /auth/refresh, …) ins Leere.
  const fullPath = req.originalUrl?.split('?')[0] || req.path;

  // Ausgeschlossene Routen überspringen
  if (EXCLUDED_ROUTES.some((route) => fullPath.startsWith(route))) {
    next();
    return;
  }

  // Resource-Mapping finden
  const mapping = findResourceMapping(fullPath);
  if (!mapping) {
    // Unbekannte Route - trotzdem loggen mit generischem Typ
    next();
    return;
  }

  // Original res.json überschreiben um Response zu capturen
  const originalJson = res.json.bind(res);
  let responseBody: unknown = null;
  let responseSuccess = true;

  res.json = function (body: unknown) {
    responseBody = body;
    if (typeof body === 'object' && body !== null && 'success' in body) {
      responseSuccess = (body as { success: boolean }).success;
    }
    return originalJson(body);
  };

  // Response-Ende abfangen für Logging
  // Audit-Kontext hier erfassen (bevor AsyncLocalStorage den Kontext verliert)
  let capturedAuditContext: ReturnType<typeof getAuditContext> | undefined;

  const origEnd = res.end;
  (res as any).end = function(chunk?: any, encoding?: any, cb?: any) {
    // Kontext VOR dem Ende erfassen
    capturedAuditContext = getAuditContext();
    return origEnd.call(this, chunk, encoding, cb);
  };

  res.on('finish', () => {
    // Async Logging - blockiert nicht die Response
    setImmediate(async () => {
      try {
        const durationMs = Date.now() - startTime;
        const action = determineAction(req.method, fullPath, responseSuccess);

        // READ-Aktionen nicht loggen (nur Änderungen, Logins und Exporte)
        if (action === 'READ') return;

        // Routen die bereits gezielt via logChange() geloggt werden → nicht doppelt loggen
        const manuallyLoggedPaths = [
          '/api/customers',
          '/api/contracts',
          '/api/meters',
          '/api/gdpr',
          '/api/upload',
        ];
        // Login/Logout/Refresh immer loggen
        if (action !== 'LOGIN' && action !== 'LOGOUT' && action !== 'LOGIN_FAILED' && action !== 'TOKEN_REFRESH') {
          if (manuallyLoggedPaths.some(p => fullPath.startsWith(p))) return;
        }

        const resourceId = mapping.extractId?.(req);
        const dataSubjectId = extractDataSubjectId(req, fullPath);

        // Audit-Kontext nutzen (wurde vor Response-Ende erfasst)
        const auditContext = capturedAuditContext;

        // Menschenlesbares Label generieren
        const resourceLabel = generateHumanLabel(action, mapping.type, req, responseBody, fullPath);

        await createAuditLog({
          userId: req.user?.userId,
          userEmail: req.user?.email || 'anonymous',
          userRole: req.user?.isCustomerPortal ? 'Kundenportal' : (req.user as any)?.roleName || 'Mitarbeiter',
          customerId: req.user?.customerId,
          isCustomerPortal: req.user?.isCustomerPortal,
          action,
          // Erfolgreicher Token-Refresh ist Routine → LOW statt CRITICAL (sonst Log-Flut).
          // Abgelehnter Refresh (Replay/Brute-Force-Verdacht) → HIGH, damit er in der
          // Triage nicht neben legitimen Refreshes untergeht (Pentest R164-01).
          // "Kein Cookie vorhanden" ist dagegen der normale Erstbesuch → LOW (R166-03).
          // Andere Auth-Events behalten ihre Default-Sensitivität (Authentication → CRITICAL).
          sensitivity: action === 'TOKEN_REFRESH'
            ? (refreshOutcome(responseBody, responseSuccess) === 'rejected' ? 'HIGH' : 'LOW')
            : action === 'LOGIN' && istDienstkontoAnmeldung(responseBody, responseSuccess)
              ? 'LOW'
              : undefined,
          resourceType: mapping.type,
          resourceId,
          resourceLabel,
          endpoint: fullPath,
          httpMethod: req.method,
          ipAddress: getClientIp(req),
          userAgent: req.headers['user-agent'],
          changesBefore: auditContext?.before,
          changesAfter: auditContext?.after,
          dataSubjectId,
          success: responseSuccess,
          errorMessage: !responseSuccess && responseBody && typeof responseBody === 'object' && 'error' in responseBody
            ? (responseBody as { error: string }).error
            : undefined,
          durationMs,
        });
      } catch (error) {
        // Audit-Logging darf niemals die Anwendung beeinträchtigen
        console.error('[AuditMiddleware] Fehler beim Logging:', error);
      }
    });
  });

  next();
}
