/**
 * IP Allowlist Service
 *
 * Ermöglicht die Einschränkung von API-Endpoints auf bestimmte IP-Adressen.
 * Konfiguration über Environment-Variablen.
 */

import { NextRequest } from 'next/server'
import { recordSecurityEvent } from './security-observability'

/**
 * Prüft ob eine IP-Adresse in einem CIDR-Block liegt
 */
function ipInCidr(ip: string, cidr: string): boolean {
  // Einfache Implementierung für IPv4
  const [range, bits] = cidr.split('/')
  if (!range || !bits) return ip === cidr

  const mask = parseInt(bits, 10)
  if (isNaN(mask) || mask < 0 || mask > 32) return false

  const ipParts = ip.split('.').map(Number)
  const rangeParts = range.split('.').map(Number)

  if (ipParts.length !== 4 || rangeParts.length !== 4) return false
  if (ipParts.some(isNaN) || rangeParts.some(isNaN)) return false

  const ipNum =
    (ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3]
  const rangeNum =
    (rangeParts[0] << 24) | (rangeParts[1] << 16) | (rangeParts[2] << 8) | rangeParts[3]

  const maskNum = mask === 0 ? 0 : (~0 << (32 - mask)) >>> 0

  return (ipNum & maskNum) === (rangeNum & maskNum)
}

/**
 * Prüft ob eine IP in einer Liste von IPs/CIDRs enthalten ist
 */
function ipInList(ip: string, list: string[]): boolean {
  for (const entry of list) {
    // Exakte Übereinstimmung
    if (ip === entry) return true

    // CIDR-Prüfung
    if (entry.includes('/') && ipInCidr(ip, entry)) return true

    // Wildcard-Unterstützung (z.B. 192.168.*)
    if (entry.includes('*')) {
      const pattern = entry.replace(/\./g, '\\.').replace(/\*/g, '.*')
      if (new RegExp(`^${pattern}$`).test(ip)) return true
    }
  }

  return false
}

/**
 * Parsed eine komma-getrennte Liste von IPs/CIDRs aus einem String
 */
function parseIpList(value: string | undefined): string[] {
  if (!value) return []
  return value
    .split(',')
    .map((ip) => ip.trim())
    .filter((ip) => ip.length > 0)
}

/**
 * Prüft ob Proxy-Headers vertraut werden sollen
 * TRUST_PROXY muss explizit auf 'true' gesetzt sein
 */
function shouldTrustProxy(): boolean {
  return process.env.TRUST_PROXY === 'true'
}

/**
 * Extrahiert die Client-IP aus einem NextRequest
 *
 * SECURITY: Nur bei TRUST_PROXY=true werden X-Forwarded-For/X-Real-IP Headers
 * berücksichtigt. Ohne diese Einstellung könnten Angreifer ihre IP spoofing
 * und IP-Allowlists/Blocklists umgehen.
 *
 * In einer Reverse-Proxy-Umgebung (z.B. hinter Caddy) sollte TRUST_PROXY=true
 * gesetzt werden.
 */
export function getClientIpFromRequest(req: NextRequest): string {
  // Nur Proxy-Headers vertrauen wenn explizit konfiguriert
  if (shouldTrustProxy()) {
    const forwarded = req.headers.get('x-forwarded-for')
    if (forwarded) {
      // Nimm nur die erste IP (vom Client-nächsten Proxy)
      return forwarded.split(',')[0]?.trim() || 'unknown'
    }

    const realIp = req.headers.get('x-real-ip')
    if (realIp) {
      return realIp.trim()
    }
  }

  // Fallback: Ohne TRUST_PROXY können wir keine vertrauenswürdige IP ermitteln
  // Gibt 'direct-connection' zurück um anzuzeigen, dass IP-basierte Prüfungen
  // nicht zuverlässig sind
  return 'direct-connection'
}

// ============================================================================
// Allowlist-Konfigurationen
// ============================================================================

interface AllowlistConfig {
  /** Environment-Variable für die Allowlist */
  envVar: string
  /** Beschreibung für Logging */
  description: string
  /** Fallback wenn keine Konfiguration vorhanden */
  allowAllIfEmpty: boolean
}

const allowlistConfigs: Record<string, AllowlistConfig> = {
  sendEmail: {
    envVar: 'SEND_EMAIL_ALLOWED_IPS',
    description: '/api/send-email',
    allowAllIfEmpty: true, // Erlaube alle wenn nicht konfiguriert
  },
  admin: {
    envVar: 'ADMIN_ALLOWED_IPS',
    description: 'Admin Panel',
    allowAllIfEmpty: true,
  },
  generatePdf: {
    envVar: 'GENERATE_PDF_ALLOWED_IPS',
    description: '/api/generate-pdf',
    allowAllIfEmpty: true,
  },
  webhooks: {
    envVar: 'WEBHOOK_ALLOWED_IPS',
    description: 'Webhook Endpoints',
    allowAllIfEmpty: false, // Blockiere alle wenn nicht konfiguriert
  },
}

// ============================================================================
// Startup-Warnungen für Sicherheitskonfiguration
// ============================================================================

let startupWarningsLogged = false

/**
 * Loggt Warnungen für leere Allowlists bei Startup
 * SECURITY: Leere Allowlists mit allowAllIfEmpty=true bieten keinen Schutz
 */
function logStartupWarnings() {
  if (startupWarningsLogged) return
  startupWarningsLogged = true

  const isProduction = process.env.NODE_ENV === 'production'
  const warnings: string[] = []

  for (const [key, config] of Object.entries(allowlistConfigs)) {
    const configuredIps = parseIpList(process.env[config.envVar])

    if (configuredIps.length === 0 && config.allowAllIfEmpty) {
      warnings.push(
        `  - ${config.description} (${config.envVar}): Keine IPs konfiguriert, ALLE IPs erlaubt`
      )
    }
  }

  if (warnings.length > 0) {
    const level = isProduction ? 'warn' : 'log'
    console[level](
      `[Security${isProduction ? ' Warning' : ''}] IP-Allowlists ohne Einschränkung:\n` +
      warnings.join('\n') +
      (isProduction
        ? '\n  Konfigurieren Sie die entsprechenden Umgebungsvariablen für erhöhte Sicherheit.'
        : '')
    )
  }

  // Prüfe auch TRUST_PROXY
  if (!shouldTrustProxy()) {
    if (isProduction) {
      console.warn(
        '[Security Warning] TRUST_PROXY nicht konfiguriert. ' +
        'IP-basierte Sicherheitsfunktionen (Rate-Limiting, Allowlists, Blocklists) ' +
        'funktionieren nicht korrekt hinter einem Reverse-Proxy. ' +
        'Setzen Sie TRUST_PROXY=true wenn Sie hinter Caddy/Nginx/etc. sind.'
      )
    }
  }
}

/**
 * Prüft ob eine IP für einen bestimmten Endpoint erlaubt ist
 */
export function isIpAllowed(
  ip: string,
  endpoint: keyof typeof allowlistConfigs,
): { allowed: boolean; reason?: string } {
  // Startup-Warnungen beim ersten Aufruf loggen
  logStartupWarnings()

  const config = allowlistConfigs[endpoint]
  if (!config) {
    return { allowed: false, reason: `Unknown allowlist endpoint: ${String(endpoint)}` }
  }

  const allowedIps = parseIpList(process.env[config.envVar])

  // Keine Konfiguration vorhanden
  if (allowedIps.length === 0) {
    if (config.allowAllIfEmpty) {
      return { allowed: true }
    }
    return {
      allowed: false,
      reason: `No IPs configured for ${config.description}`,
    }
  }

  // Prüfe ob IP in Allowlist
  if (ipInList(ip, allowedIps)) {
    return { allowed: true }
  }

  return {
    allowed: false,
    reason: `IP ${ip} not in allowlist for ${config.description}`,
  }
}

/**
 * Middleware-Helper für IP-Allowlist-Prüfung
 */
export function checkIpAllowlist(
  req: NextRequest,
  endpoint: keyof typeof allowlistConfigs,
): { allowed: boolean; ip: string; reason?: string } {
  const ip = getClientIpFromRequest(req)
  const result = isIpAllowed(ip, endpoint)

  return {
    ...result,
    ip,
  }
}

// ============================================================================
// Blocklist (für bekannte böswillige IPs)
// ============================================================================

/**
 * Prüft ob eine IP auf der globalen Blocklist steht
 * Konfiguration über BLOCKED_IPS Environment-Variable
 */
export function isIpBlocked(ip: string): boolean {
  const blockedIps = parseIpList(process.env.BLOCKED_IPS)
  if (blockedIps.length === 0) return false

  return ipInList(ip, blockedIps)
}

/**
 * Kombinierte Prüfung: Nicht blockiert UND erlaubt
 */
export function validateIpAccess(
  req: NextRequest,
  endpoint: keyof typeof allowlistConfigs,
): { allowed: boolean; ip: string; reason?: string } {
  const ip = getClientIpFromRequest(req)

  // Erst Blocklist prüfen
  if (isIpBlocked(ip)) {
    recordSecurityEvent('ip_access_denied', {
      ip,
      endpoint,
      reason: `IP ${ip} is blocked`,
    })
    return {
      allowed: false,
      ip,
      reason: `IP ${ip} is blocked`,
    }
  }

  // Dann Allowlist prüfen
  const allowlistResult = isIpAllowed(ip, endpoint)
  if (!allowlistResult.allowed) {
    recordSecurityEvent('ip_access_denied', {
      ip,
      endpoint,
      reason: allowlistResult.reason,
    })
  }

  return {
    ...allowlistResult,
    ip,
  }
}
