/**
 * Audit Service
 *
 * Zentraler Service für das Logging von Audit-Events.
 * Verwendet von Hooks und anderen System-Komponenten.
 */

import type { Payload, PayloadRequest } from 'payload'
import { maskString, maskObject, maskError } from '../security/data-masking'

export type AuditAction =
  | 'login_success'
  | 'login_failed'
  | 'logout'
  | 'password_changed'
  | 'password_reset'
  | 'create'
  | 'update'
  | 'delete'
  | 'config_changed'
  | 'email_failed'
  | 'access_denied'
  | 'rate_limit'

export type AuditSeverity = 'info' | 'warning' | 'error' | 'critical'

export type AuditEntityType =
  | 'users'
  | 'tenants'
  | 'pages'
  | 'posts'
  | 'media'
  | 'forms'
  | 'email'
  | 'global'
  | 'system'

export interface AuditLogInput {
  action: AuditAction
  severity?: AuditSeverity
  entityType?: AuditEntityType
  entityId?: string | number
  userId?: number
  userEmail?: string
  tenantId?: number
  ipAddress?: string
  userAgent?: string
  description?: string
  previousValue?: Record<string, unknown>
  newValue?: Record<string, unknown>
  metadata?: Record<string, unknown>
}

/**
 * Extrahiert einen Header-Wert aus verschiedenen Header-Formaten
 *
 * Unterstützt:
 * - Express/Node IncomingHttpHeaders (object mit lowercase keys)
 * - Fetch API Headers (mit .get() Methode)
 * - Request mit .get() Methode (Express-style)
 */
function getHeaderValue(
  req: PayloadRequest,
  headerName: string,
): string | undefined {
  const lowerName = headerName.toLowerCase()

  // 1. Versuche req.get() (Express Request Methode)
  const reqWithGet = req as unknown as { get?: (name: string) => string | undefined }
  if (typeof reqWithGet.get === 'function') {
    const value = reqWithGet.get(lowerName)
    if (value) return value
  }

  // 2. Versuche headers.get() (Fetch API Headers)
  const headersWithGet = req.headers as unknown as { get?: (name: string) => string | null }
  if (req.headers && typeof headersWithGet.get === 'function') {
    const value = headersWithGet.get(lowerName)
    if (value) return value
  }

  // 3. Direkter Zugriff auf headers object (IncomingHttpHeaders)
  if (req.headers && typeof req.headers === 'object') {
    const headers = req.headers as unknown as Record<string, string | string[] | undefined>
    const value = headers[lowerName]
    if (typeof value === 'string') return value
    if (Array.isArray(value) && value.length > 0) return value[0]
  }

  return undefined
}

/**
 * Extrahiert Client-Informationen aus dem Request
 *
 * Funktioniert mit:
 * - PayloadRequest (Express-basiert mit IncomingHttpHeaders)
 * - Next.js Request (Fetch API basiert)
 */
function getClientInfo(req?: PayloadRequest): { ipAddress?: string; userAgent?: string } {
  if (!req) return {}

  // IP-Adresse aus verschiedenen Headern extrahieren
  const forwarded = getHeaderValue(req, 'x-forwarded-for')
  const realIp = getHeaderValue(req, 'x-real-ip')

  let ipAddress: string | undefined
  if (forwarded) {
    ipAddress = forwarded.split(',')[0]?.trim()
  } else if (realIp) {
    ipAddress = realIp
  }

  const userAgent = getHeaderValue(req, 'user-agent')

  return { ipAddress, userAgent }
}

/**
 * Bestimmt den Schweregrad basierend auf der Aktion
 */
function getDefaultSeverity(action: AuditAction): AuditSeverity {
  switch (action) {
    case 'login_failed':
    case 'access_denied':
    case 'rate_limit':
      return 'warning'
    case 'email_failed':
      return 'error'
    case 'delete':
      return 'warning'
    default:
      return 'info'
  }
}

/**
 * Erstellt einen Audit-Log-Eintrag
 *
 * Sensible Daten in previousValue, newValue und metadata werden automatisch maskiert.
 */
export async function createAuditLog(
  payload: Payload,
  input: AuditLogInput,
  req?: PayloadRequest,
): Promise<void> {
  try {
    const clientInfo = getClientInfo(req)

    // Sensible Daten in Objekten maskieren
    const maskedPreviousValue = input.previousValue
      ? maskObject(input.previousValue)
      : undefined
    const maskedNewValue = input.newValue ? maskObject(input.newValue) : undefined
    const maskedMetadata = input.metadata ? maskObject(input.metadata) : undefined

    type CreateArgs = Parameters<typeof payload.create>[0]
    await payload.create({
      collection: 'audit-logs',
      data: {
        action: input.action,
        severity: input.severity || getDefaultSeverity(input.action),
        entityType: input.entityType,
        entityId: input.entityId?.toString(),
        user: input.userId,
        userEmail: input.userEmail,
        tenant: input.tenantId,
        ipAddress: input.ipAddress || clientInfo.ipAddress,
        userAgent: input.userAgent || clientInfo.userAgent,
        description: input.description ? maskString(input.description) : undefined,
        previousValue: maskedPreviousValue,
        newValue: maskedNewValue,
        metadata: maskedMetadata,
      },
      // Bypass Access Control für System-Logging
      overrideAccess: true,
    } as CreateArgs)
  } catch (error) {
    // Fehler beim Audit-Logging sollten die Hauptoperation nicht blockieren
    // Auch Fehlermeldungen maskieren
    console.error('[AuditService] Error creating audit log:', maskError(error))
  }
}

/**
 * Loggt einen erfolgreichen Login
 */
export async function logLoginSuccess(
  payload: Payload,
  userId: number,
  userEmail: string,
  req?: PayloadRequest,
): Promise<void> {
  await createAuditLog(
    payload,
    {
      action: 'login_success',
      entityType: 'users',
      entityId: userId,
      userId,
      userEmail,
      description: `Benutzer ${userEmail} hat sich erfolgreich angemeldet`,
    },
    req,
  )
}

/**
 * Client-Info Interface für direkte Übergabe (wenn kein PayloadRequest verfügbar)
 */
export interface ClientInfo {
  ipAddress?: string
  userAgent?: string
}

/**
 * Prüft ob ein Objekt ein Request ist (hat headers property)
 * im Gegensatz zu einem einfachen ClientInfo-Objekt
 */
function isRequest(obj: unknown): obj is PayloadRequest {
  if (!obj || typeof obj !== 'object') return false

  // Ein Request hat typischerweise headers, method, url etc.
  // ClientInfo hat nur ipAddress und/oder userAgent
  const asRecord = obj as Record<string, unknown>

  // Wenn es headers hat, ist es ein Request
  if ('headers' in asRecord) return true

  // Wenn es method oder url hat, ist es ein Request
  if ('method' in asRecord || 'url' in asRecord) return true

  // Wenn es NUR ipAddress und/oder userAgent hat, ist es ClientInfo
  const keys = Object.keys(asRecord)
  const clientInfoKeys = ['ipAddress', 'userAgent']
  if (keys.every((key) => clientInfoKeys.includes(key))) return false

  // Im Zweifel als Request behandeln
  return true
}

/**
 * Extrahiert ClientInfo aus einem Request oder gibt das übergebene ClientInfo zurück
 */
function extractClientInfo(reqOrClientInfo?: PayloadRequest | ClientInfo): ClientInfo {
  if (!reqOrClientInfo) return {}

  if (isRequest(reqOrClientInfo)) {
    return getClientInfo(reqOrClientInfo)
  }

  // Es ist bereits ClientInfo
  return reqOrClientInfo
}

/**
 * Loggt einen fehlgeschlagenen Login
 *
 * @param payload - Payload-Instanz
 * @param email - E-Mail des Login-Versuchs
 * @param reason - Grund für das Fehlschlagen
 * @param reqOrClientInfo - PayloadRequest ODER direktes ClientInfo-Objekt
 */
export async function logLoginFailed(
  payload: Payload,
  email: string,
  reason: string,
  reqOrClientInfo?: PayloadRequest | ClientInfo,
): Promise<void> {
  const clientInfo = extractClientInfo(reqOrClientInfo)

  await createAuditLog(
    payload,
    {
      action: 'login_failed',
      severity: 'warning',
      entityType: 'users',
      userEmail: email,
      ipAddress: clientInfo.ipAddress,
      userAgent: clientInfo.userAgent,
      description: `Fehlgeschlagener Login-Versuch für ${email}: ${reason}`,
      metadata: { reason },
    },
    undefined, // Kein req mehr nötig, Client-Info ist bereits im Input
  )
}

/**
 * Loggt eine Passwort-Reset-Anfrage
 *
 * @param payload - Payload-Instanz
 * @param email - E-Mail für den Reset
 * @param reqOrClientInfo - PayloadRequest ODER direktes ClientInfo-Objekt
 */
export async function logPasswordReset(
  payload: Payload,
  email: string,
  reqOrClientInfo?: PayloadRequest | ClientInfo,
): Promise<void> {
  const clientInfo = extractClientInfo(reqOrClientInfo)

  await createAuditLog(
    payload,
    {
      action: 'password_reset',
      severity: 'info',
      entityType: 'users',
      userEmail: email,
      ipAddress: clientInfo.ipAddress,
      userAgent: clientInfo.userAgent,
      description: `Passwort-Reset angefordert für ${email}`,
    },
    undefined,
  )
}

/**
 * Loggt eine Tenant-Änderung
 */
export async function logTenantChange(
  payload: Payload,
  tenantId: number,
  action: 'create' | 'update' | 'delete',
  userId: number,
  userEmail: string,
  previousValue?: Record<string, unknown>,
  newValue?: Record<string, unknown>,
  req?: PayloadRequest,
): Promise<void> {
  const actionLabels = {
    create: 'erstellt',
    update: 'aktualisiert',
    delete: 'gelöscht',
  }

  await createAuditLog(
    payload,
    {
      action,
      severity: action === 'delete' ? 'warning' : 'info',
      entityType: 'tenants',
      entityId: tenantId,
      userId,
      userEmail,
      tenantId,
      description: `Tenant ${tenantId} wurde ${actionLabels[action]} von ${userEmail}`,
      previousValue,
      newValue,
    },
    req,
  )
}

/**
 * Loggt eine User-Änderung
 */
export async function logUserChange(
  payload: Payload,
  targetUserId: number,
  action: 'create' | 'update' | 'delete',
  performedByUserId: number,
  performedByEmail: string,
  changes?: { previousValue?: Record<string, unknown>; newValue?: Record<string, unknown> },
  req?: PayloadRequest,
): Promise<void> {
  const actionLabels = {
    create: 'erstellt',
    update: 'aktualisiert',
    delete: 'gelöscht',
  }

  await createAuditLog(
    payload,
    {
      action,
      severity: action === 'delete' ? 'warning' : 'info',
      entityType: 'users',
      entityId: targetUserId,
      userId: performedByUserId,
      userEmail: performedByEmail,
      description: `Benutzer ${targetUserId} wurde ${actionLabels[action]} von ${performedByEmail}`,
      previousValue: changes?.previousValue,
      newValue: changes?.newValue,
    },
    req,
  )
}

/**
 * Loggt einen E-Mail-Fehler
 */
export async function logEmailFailed(
  payload: Payload,
  tenantId: number,
  to: string,
  subject: string,
  error: string,
  userId?: number,
  userEmail?: string,
): Promise<void> {
  await createAuditLog(payload, {
    action: 'email_failed',
    severity: 'error',
    entityType: 'email',
    tenantId,
    userId,
    userEmail,
    description: `E-Mail an ${to} fehlgeschlagen: ${subject}`,
    metadata: {
      to,
      subject,
      // Fehler maskieren um Secrets zu schützen
      error: maskSensitiveData(error),
    },
  })
}

/**
 * Loggt einen Zugriffsverweigerung
 */
export async function logAccessDenied(
  payload: Payload,
  resource: string,
  userId?: number,
  userEmail?: string,
  req?: PayloadRequest,
): Promise<void> {
  await createAuditLog(
    payload,
    {
      action: 'access_denied',
      severity: 'warning',
      entityType: 'system',
      userId,
      userEmail,
      description: `Zugriff auf ${resource} verweigert`,
      metadata: { resource },
    },
    req,
  )
}

/**
 * Loggt ein Rate-Limit-Ereignis
 */
export async function logRateLimit(
  payload: Payload,
  endpoint: string,
  userId?: number,
  userEmail?: string,
  req?: PayloadRequest,
): Promise<void> {
  await createAuditLog(
    payload,
    {
      action: 'rate_limit',
      severity: 'warning',
      entityType: 'system',
      userId,
      userEmail,
      description: `Rate-Limit erreicht für ${endpoint}`,
      metadata: { endpoint },
    },
    req,
  )
}

/**
 * Maskiert sensible Daten in Fehlermeldungen
 * Verwendet jetzt den zentralen Data-Masking-Service
 */
function maskSensitiveData(text: string): string {
  return maskString(text)
}

// Re-export für externe Nutzung
export { maskError, maskObject, maskString }
