/**
 * Data Masking Service
 *
 * Maskiert sensible Daten für Logs, Audit-Einträge und API-Responses.
 * Verhindert das Leaken von Passwörtern, Tokens und anderen Secrets.
 */

// Liste von Feldnamen die immer maskiert werden sollen
const SENSITIVE_FIELD_NAMES = [
  'password',
  'pass',
  'passwd',
  'secret',
  'token',
  'apikey',
  'api_key',
  'apiKey',
  'auth',
  'authorization',
  'credential',
  'credentials',
  'private',
  'privateKey',
  'private_key',
  'accessToken',
  'access_token',
  'refreshToken',
  'refresh_token',
  'sessionId',
  'session_id',
  'cookie',
  'jwt',
  'bearer',
  'smtp_pass',
  'smtpPass',
  'smtp_password',
  'smtpPassword',
  'db_password',
  'database_password',
  'encryption_key',
  'encryptionKey',
  'salt',
  'hash',
  'resetPasswordToken',
  'verificationToken',
]

// Patterns für sensible Werte (in Strings)
const SENSITIVE_PATTERNS: Array<{ pattern: RegExp; replacement: string | ((match: string) => string) }> = [
  // Passwörter in verschiedenen Formaten
  { pattern: /password['":\s]*['"]?[^'"\s,}]+['"]?/gi, replacement: 'password: [REDACTED]' },
  { pattern: /pass['":\s]*['"]?[^'"\s,}]+['"]?/gi, replacement: 'pass: [REDACTED]' },

  // API Keys und Tokens
  { pattern: /api[_-]?key['":\s]*['"]?[^'"\s,}]+['"]?/gi, replacement: 'apiKey: [REDACTED]' },
  { pattern: /token['":\s]*['"]?[^'"\s,}]+['"]?/gi, replacement: 'token: [REDACTED]' },
  { pattern: /bearer\s+[A-Za-z0-9_.-]+/gi, replacement: 'Bearer [REDACTED]' },

  // Authorization Headers
  { pattern: /authorization['":\s]*['"]?[^'"\s,}]+['"]?/gi, replacement: 'authorization: [REDACTED]' },

  // SMTP Credentials - Multiple formats
  { pattern: /smtp[_-]?pass(?:word)?['":\s]*['"]?[^'"\s,}]+['"]?/gi, replacement: 'smtp_pass: [REDACTED]' },
  { pattern: /auth[_-]?pass(?:word)?['":\s]*['"]?[^'"\s,}]+['"]?/gi, replacement: 'auth_pass: [REDACTED]' },

  // SMTP Error Messages - Common patterns from nodemailer/SMTP providers
  // AUTH LOGIN failed: Username and Password not accepted
  { pattern: /AUTH\s+(?:LOGIN|PLAIN|CRAM-MD5)\s+failed[^.]*password[^.]*\./gi, replacement: 'AUTH failed: [CREDENTIALS REDACTED].' },
  // 535 5.7.8 Authentication credentials invalid
  { pattern: /535\s+[\d.]+\s+[^:]*(?:credentials?|authentication)[^.]*/gi, replacement: '535 Authentication error: [DETAILS REDACTED]' },
  // Invalid login: 535-5.7.8 Username and Password not accepted
  { pattern: /Invalid\s+login[^:]*:[^.]*/gi, replacement: 'Invalid login: [DETAILS REDACTED]' },
  // SMTP username/password patterns in error stack traces
  { pattern: /user(?:name)?['":\s]*['"]?[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+['"]?/gi, replacement: 'user: [REDACTED]' },

  // Connection Strings mit Passwörtern
  { pattern: /:\/\/[^:]+:([^@]+)@/g, replacement: '://[USER]:[REDACTED]@' },

  // Private Keys
  { pattern: /-----BEGIN [A-Z ]+ PRIVATE KEY-----[\s\S]*?-----END [A-Z ]+ PRIVATE KEY-----/g, replacement: '[PRIVATE KEY REDACTED]' },

  // JWT Tokens (behalte Header für Debugging, redact Payload und Signature)
  { pattern: /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, replacement: (match) => {
    const parts = match.split('.')
    return `${parts[0]}.[PAYLOAD REDACTED].[SIGNATURE REDACTED]`
  }},

  // Base64 encoded credentials (common in SMTP AUTH) - minimum 16 chars to avoid false positives
  { pattern: /(?:AUTH|auth)\s+(?:PLAIN|LOGIN|CRAM-MD5)?\s*[A-Za-z0-9+/=]{16,}/gi, replacement: 'AUTH [BASE64 REDACTED]' },

  // E-Mail-Adressen teilweise maskieren (für Datenschutz, nicht Security)
  // Deaktiviert - E-Mails werden für Audit-Logs benötigt
]

/**
 * Maskiert einen einzelnen String-Wert
 */
export function maskString(value: string): string {
  if (!value || typeof value !== 'string') return value

  let masked = value

  for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
    if (typeof replacement === 'function') {
      masked = masked.replace(pattern, replacement as (substring: string, ...args: unknown[]) => string)
    } else {
      masked = masked.replace(pattern, replacement)
    }
  }

  return masked
}

/**
 * Prüft ob ein Feldname sensitiv ist
 */
export function isSensitiveField(fieldName: string): boolean {
  const lowerName = fieldName.toLowerCase()
  return SENSITIVE_FIELD_NAMES.some((sensitive) => lowerName.includes(sensitive.toLowerCase()))
}

/**
 * Maskiert ein Objekt rekursiv
 */
export function maskObject<T extends Record<string, unknown>>(
  obj: T,
  options: {
    /** Maximale Rekursionstiefe */
    maxDepth?: number
    /** Zusätzliche Felder zum Maskieren */
    additionalFields?: string[]
    /** Felder die NICHT maskiert werden sollen */
    preserveFields?: string[]
  } = {},
): T {
  const { maxDepth = 10, additionalFields = [], preserveFields = [] } = options

  function maskRecursive(value: unknown, depth: number, path: string): unknown {
    if (depth > maxDepth) return '[MAX DEPTH EXCEEDED]'

    // Null/undefined passieren lassen
    if (value === null || value === undefined) return value

    // String maskieren
    if (typeof value === 'string') {
      return maskString(value)
    }

    // Arrays durchlaufen
    if (Array.isArray(value)) {
      return value.map((item, index) => maskRecursive(item, depth + 1, `${path}[${index}]`))
    }

    // Objekte durchlaufen
    if (typeof value === 'object') {
      const result: Record<string, unknown> = {}

      for (const [key, val] of Object.entries(value)) {
        const currentPath = path ? `${path}.${key}` : key

        // Felder die explizit erhalten werden sollen
        if (preserveFields.includes(key)) {
          result[key] = val
          continue
        }

        // Sensitive Felder komplett maskieren
        if (isSensitiveField(key) || additionalFields.includes(key)) {
          if (val === null || val === undefined) {
            result[key] = val
          } else if (typeof val === 'string' && val.length > 0) {
            result[key] = '[REDACTED]'
          } else if (typeof val === 'object') {
            result[key] = '[REDACTED OBJECT]'
          } else {
            result[key] = '[REDACTED]'
          }
          continue
        }

        // Rekursiv maskieren
        result[key] = maskRecursive(val, depth + 1, currentPath)
      }

      return result
    }

    // Primitive Werte durchlassen
    return value
  }

  return maskRecursive(obj, 0, '') as T
}

/**
 * Maskiert Tenant-Daten (speziell für Audit-Logs)
 */
export function maskTenantData(tenant: Record<string, unknown>): Record<string, unknown> {
  return maskObject(tenant, {
    additionalFields: ['smtpPass', 'smtp_pass', 'pass'],
    preserveFields: ['id', 'name', 'slug', 'domain', 'createdAt', 'updatedAt'],
  })
}

/**
 * Maskiert E-Mail-Log-Daten
 */
export function maskEmailLogData(emailLog: Record<string, unknown>): Record<string, unknown> {
  const masked = { ...emailLog }

  // Error-Messages maskieren
  if (masked.error && typeof masked.error === 'string') {
    masked.error = maskString(masked.error)
  }

  // SMTP-Config maskieren falls vorhanden
  if (masked.smtpConfig && typeof masked.smtpConfig === 'object') {
    masked.smtpConfig = maskObject(masked.smtpConfig as Record<string, unknown>)
  }

  return masked
}

/**
 * Maskiert SMTP Error-Messages für sichere Anzeige im Dashboard
 *
 * Speziell für den Fall, dass SMTP-Server Credential-Details in Fehlern zurückgeben.
 * Behält nützliche Debugging-Informationen, entfernt aber sensible Daten.
 */
export function maskSmtpError(error: string | null | undefined): string | null {
  if (!error || typeof error !== 'string') return null

  // Leere oder sehr kurze Errors durchlassen
  if (error.length < 3) return error

  // Bekannte SMTP-Fehler-Kategorien mit benutzerfreundlichen Ersetzungen
  let masked = error

  // Allgemeine maskString-Patterns zuerst anwenden
  masked = maskString(masked)

  // Zusätzliche SMTP-spezifische Patterns
  // Hostnames/IPs in SMTP-Antworten beibehalten, aber Auth-Details entfernen
  // "530 5.7.0 Must issue a STARTTLS command first" - behalten
  // "535 5.7.8 Error: authentication failed" - behalten (ohne Details)
  // "454 4.7.0 Too many login attempts, please try later" - behalten

  // Entferne detaillierte Auth-Failure-Gründe aber behalte Fehlercode
  masked = masked.replace(
    /(\d{3}\s+[\d.]+\s+).*(?:Username|Password|Credentials?|Authentication).*$/gi,
    '$1Authentication failed'
  )

  // Truncate exzessiv lange Errors (Stack traces etc.)
  const MAX_ERROR_LENGTH = 200
  if (masked.length > MAX_ERROR_LENGTH) {
    masked = masked.substring(0, MAX_ERROR_LENGTH) + '...'
  }

  return masked
}

/**
 * Maskiert Error-Objekte für sichere Logs
 */
export function maskError(error: Error | unknown): Record<string, unknown> {
  if (error instanceof Error) {
    return {
      name: error.name,
      message: maskString(error.message),
      stack: error.stack ? maskString(error.stack.substring(0, 1000)) : undefined,
    }
  }

  if (typeof error === 'string') {
    return { message: maskString(error) }
  }

  if (typeof error === 'object' && error !== null) {
    return maskObject(error as Record<string, unknown>)
  }

  return { message: String(error) }
}

/**
 * Safe JSON.stringify mit Maskierung
 */
export function safeStringify(obj: unknown, space?: number): string {
  try {
    if (typeof obj === 'object' && obj !== null) {
      return JSON.stringify(maskObject(obj as Record<string, unknown>), null, space)
    }
    if (typeof obj === 'string') {
      return maskString(obj)
    }
    return JSON.stringify(obj, null, space)
  } catch {
    return '[STRINGIFY ERROR]'
  }
}

/**
 * Erstellt eine sichere Log-Funktion
 */
export function createSafeLogger(prefix: string) {
  return {
    info: (message: string, data?: unknown) => {
      console.log(`[${prefix}]`, message, data ? safeStringify(data) : '')
    },
    warn: (message: string, data?: unknown) => {
      console.warn(`[${prefix}]`, message, data ? safeStringify(data) : '')
    },
    error: (message: string, error?: unknown) => {
      console.error(`[${prefix}]`, message, error ? safeStringify(maskError(error)) : '')
    },
  }
}
