/**
 * Audit Hooks für Authentifizierungs-Events
 *
 * Loggt Login-Erfolge, Login-Fehlschläge und Logouts für Compliance und Security.
 */

import type {
  CollectionAfterLoginHook,
  CollectionAfterLogoutHook,
  CollectionAfterForgotPasswordHook,
  Payload,
  PayloadRequest,
} from 'payload'
import {
  logLoginSuccess,
  logLoginFailed,
  logPasswordReset,
  createAuditLog,
  type ClientInfo,
} from '../lib/audit/audit-service'

interface AuthUser {
  id: number
  email: string
  isSuperAdmin?: boolean
}

/**
 * Hook: Loggt erfolgreiche Logins
 */
export const auditAfterLogin: CollectionAfterLoginHook = async ({ user, req }) => {
  const typedUser = user as AuthUser

  // Fire-and-forget: Audit-Log darf Login nicht blockieren
  // In Payload 3.76.1+ verursacht ein awaited payload.create() innerhalb
  // des afterLogin-Hooks einen Deadlock mit PgBouncer (Transaction-Mode)
  logLoginSuccess(req.payload, typedUser.id, typedUser.email, req).catch((err) => {
    console.error(`[Audit:Auth] Failed to log login for ${typedUser.email}:`, err)
  })

  console.log(`[Audit:Auth] Login success for ${typedUser.email}`)

  return user
}

/**
 * Hook: Loggt Logouts
 */
export const auditAfterLogout: CollectionAfterLogoutHook = async ({ req }) => {
  const user = req.user as AuthUser | undefined

  if (!user) return

  await createAuditLog(
    req.payload,
    {
      action: 'logout',
      entityType: 'users',
      entityId: user.id,
      userId: user.id,
      userEmail: user.email,
      description: `Benutzer ${user.email} hat sich abgemeldet`,
    },
    req,
  )

  console.log(`[Audit:Auth] Logout for ${user.email}`)
}

/**
 * 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-Info aus einem PayloadRequest
 *
 * Funktioniert mit:
 * - PayloadRequest (Express-basiert mit IncomingHttpHeaders)
 * - Next.js Request (Fetch API basiert)
 */
function extractClientInfo(req?: PayloadRequest): ClientInfo {
  if (!req) return {}

  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 }
}

/**
 * Hook: Loggt Passwort-Reset-Anfragen
 *
 * WICHTIG: Dieser Hook muss in Users.hooks.afterForgotPassword registriert werden
 *
 * Der Hook bekommt { args, collection, context } wobei:
 * - args.req.payload = Payload-Instanz
 * - args.data.email = E-Mail-Adresse
 * - args.req = PayloadRequest (für IP/User-Agent)
 */
export const auditAfterForgotPassword: CollectionAfterForgotPasswordHook = async ({ args }) => {
  // Bei Forgot Password haben wir nur die E-Mail, nicht die User-ID
  // Aus Sicherheitsgründen wird nicht offengelegt ob die E-Mail existiert
  const email = args?.data?.email as string | undefined

  if (!email) {
    console.warn('[Audit:Auth] Password reset hook called without email')
    return
  }

  // Payload-Instanz und Request aus args holen
  const req = args?.req as PayloadRequest | undefined
  const payload = req?.payload

  // Client-Info extrahieren (auch für Fallback-Log)
  const clientInfo = extractClientInfo(req)

  if (payload) {
    // Normaler Pfad: Logging über Audit-Service
    await logPasswordReset(payload, email, req)
    console.log(`[Audit:Auth] Password reset requested for ${email}`)
  } else {
    // Fallback: Strukturiertes Log wenn Payload nicht verfügbar
    // Dies sollte nie passieren, aber wir loggen zumindest strukturiert
    const fallbackEntry = {
      timestamp: new Date().toISOString(),
      action: 'password_reset',
      severity: 'info',
      entityType: 'users',
      userEmail: email,
      ipAddress: clientInfo.ipAddress || 'unknown',
      userAgent: clientInfo.userAgent || 'unknown',
      description: `Passwort-Reset angefordert für ${email}`,
      fallback: true,
      reason: 'Payload instance not available in hook context',
    }

    // Strukturiertes JSON-Log für spätere Analyse/Import
    console.error('[Audit:Auth:Fallback]', JSON.stringify(fallbackEntry))

    // Zusätzlich lesbare Warnung
    console.warn(
      `[Audit:Auth] FALLBACK: Password reset for ${email} logged to console only ` +
        `(IP: ${clientInfo.ipAddress || 'unknown'})`,
    )
  }
}

/**
 * Hilfsfunktion: Loggt fehlgeschlagene Logins
 *
 * Diese Funktion kann von Custom-Auth-Handlern oder Middleware aufgerufen werden,
 * da Payload keinen nativen afterLoginFailed Hook hat.
 *
 * Verwendung:
 * - In Custom-API-Routes wenn Login fehlschlägt
 * - In Middleware die Auth-Errors abfängt
 *
 * @param payload - Payload-Instanz
 * @param email - E-Mail des fehlgeschlagenen Login-Versuchs
 * @param reason - Grund für das Fehlschlagen
 * @param reqOrClientInfo - PayloadRequest ODER direktes ClientInfo-Objekt
 */
export async function auditLoginFailed(
  payload: Payload,
  email: string,
  reason: string,
  reqOrClientInfo?: PayloadRequest | ClientInfo,
): Promise<void> {
  await logLoginFailed(payload, email, reason, reqOrClientInfo)
  console.log(`[Audit:Auth] Login failed for ${email}: ${reason}`)
}
