## Platform Detection and Isolation Strategy Selection
##
## This module provides runtime detection of OS capabilities and selection of
## appropriate isolation strategies for multi-platform support.
##
## Core Philosophy:
## - Detect, don't assume
## - Graceful degradation when advanced features unavailable
## - Platform-native solutions for each OS
## - No false security - be honest about what each strategy provides

import std/[os, strutils, sequtils, options]
import std/[osproc, strformat]
when defined(posix):
  import posix

type
  ## Operating system types
  OSType* = enum
    Linux = "linux"
    FreeBSD = "freebsd"
    OpenBSD = "openbsd"
    NetBSD = "netbsd"
    macOS = "macos"
    Embedded = "embedded"

  ## Isolation strategy options
  IsolationStrategy* = enum
    LinuxNamespace = "linux-namespace"      ## unshare -r -m (Linux 4.19+)
    FreeBSDJail = "freebsd-jail"            ## jail + nullfs (FreeBSD 11+)
    OpenBSDUnveil = "openbsd-unveil"        ## unveil + pledge (OpenBSD 6.4+)
    POSIXFallback = "posix-fallback"        ## chmod + Merkle verification (all UNIX)

  ## Installation mode
  InstallMode* = enum
    UserMode = "user"                       ## --user (Linux only with namespaces)
    SystemMode = "system"                   ## --system (root required)

  ## Platform capabilities detected at runtime
  PlatformCapabilities* = object
    osType*: OSType
    hasUserNamespaces*: bool                ## Linux user namespace support
    hasJails*: bool                         ## FreeBSD jail support
    hasUnveil*: bool                        ## OpenBSD unveil support
    isRoot*: bool                           ## Running as root
    kernelVersion*: string                  ## Kernel version string
    isEmbedded*: bool                       ## Embedded/IoT device detected
    memoryTotal*: int64                     ## Total system memory in bytes
    cpuCount*: int                          ## Number of CPU cores

  ## Constraints for embedded devices
  EmbeddedConstraints* = object
    maxConcurrentDownloads*: int
    maxConcurrentBuilds*: int
    maxCacheSize*: int64
    enableCompression*: bool
    enableDeduplication*: bool
    enableParallelization*: bool

  ## Platform detection error
  PlatformError* = object of CatchableError

# ============================================================================
# OS Type Detection
# ============================================================================

proc detectOSType*(): OSType =
  ## Detect operating system type at compile time and runtime
  when defined(linux):
    return Linux
  elif defined(freebsd):
    return FreeBSD
  elif defined(openbsd):
    return OpenBSD
  elif defined(netbsd):
    return NetBSD
  elif defined(macosx):
    return macOS
  else:
    return Embedded

proc getOSTypeString*(osType: OSType): string =
  ## Get human-readable OS type name
  case osType:
  of Linux: "Linux"
  of FreeBSD: "FreeBSD"
  of OpenBSD: "OpenBSD"
  of NetBSD: "NetBSD"
  of macOS: "macOS"
  of Embedded: "Embedded/IoT"

# ============================================================================
# Root Check
# ============================================================================

proc isRoot*(): bool =
  ## Check if running as root
  when defined(posix):
    return getuid() == 0
  else:
    return false

# ============================================================================
# Kernel Version Detection
# ============================================================================

proc getKernelVersion*(): string =
  ## Get kernel version string
  try:
    when defined(linux):
      let output = execProcess("uname -r").strip()
      return output
    elif defined(freebsd):
      let output = execProcess("uname -r").strip()
      return output
    elif defined(openbsd):
      let output = execProcess("uname -r").strip()
      return output
    else:
      return "unknown"
  except:
    return "unknown"

proc parseKernelVersion*(versionStr: string): tuple[major: int, minor: int, patch: int] =
  ## Parse kernel version string into components
  let parts = versionStr.split('.')
  var major, minor, patch = 0

  if parts.len > 0:
    try:
      major = parseInt(parts[0])
    except:
      discard

  if parts.len > 1:
    try:
      minor = parseInt(parts[1])
    except:
      discard

  if parts.len > 2:
    try:
      # Extract just the numeric part (e.g., "0-generic" -> "0")
      let patchStr = parts[2].split('-')[0]
      patch = parseInt(patchStr)
    except:
      discard

  return (major, minor, patch)

# ============================================================================
# Capability Detection
# ============================================================================

proc checkUserNamespaceSupport*(): bool =
  ## Check if Linux user namespaces are available
  ## Requires Linux 4.19+ with CONFIG_USER_NS enabled
  when defined(linux):
    try:
      # Check if /proc/sys/user/max_user_namespaces exists and is > 0
      let maxNsPath = "/proc/sys/user/max_user_namespaces"
      if fileExists(maxNsPath):
        let content = readFile(maxNsPath).strip()
        try:
          let maxNs = parseInt(content)
          return maxNs > 0
        except:
          return false
      return false
    except:
      return false
  else:
    return false

proc checkJailSupport*(): bool =
  ## Check if FreeBSD jails are available
  when defined(freebsd):
    try:
      # Check if jail command exists
      let result = execProcess("which jail").strip()
      return result.len > 0
    except:
      return false
  else:
    return false

proc checkUnveilSupport*(): bool =
  ## Check if OpenBSD unveil is available
  ## Requires OpenBSD 6.4+
  when defined(openbsd):
    try:
      # Check kernel version
      let versionStr = getKernelVersion()
      let (major, minor, _) = parseKernelVersion(versionStr)
      # OpenBSD 6.4+ has unveil
      return major > 6 or (major == 6 and minor >= 4)
    except:
      return false
  else:
    return false

# ============================================================================
# System Information Detection
# ============================================================================

proc getMemoryTotal*(): int64 =
  ## Get total system memory in bytes
  try:
    when defined(linux):
      let output = execProcess("grep MemTotal /proc/meminfo").strip()
      let parts = output.split()
      if parts.len >= 2:
        try:
          let kb = parseInt(parts[1])
          return kb * 1024  # Convert KB to bytes
        except:
          return 0
    elif defined(freebsd):
      let output = execProcess("sysctl -n hw.physmem").strip()
      try:
        return parseInt(output)
      except:
        return 0
    elif defined(openbsd):
      let output = execProcess("sysctl -n hw.physmem").strip()
      try:
        return parseInt(output)
      except:
        return 0
    return 0
  except:
    return 0

proc getCPUCount*(): int =
  ## Get number of CPU cores
  try:
    when defined(linux):
      let output = execProcess("nproc").strip()
      try:
        return parseInt(output)
      except:
        discard
    elif defined(freebsd) or defined(openbsd):
      let output = execProcess("sysctl -n hw.ncpu").strip()
      try:
        return parseInt(output)
      except:
        discard
    return 1
  except:
    return 1

# ============================================================================
# Embedded Device Detection
# ============================================================================

proc detectEmbeddedDevice*(): bool =
  ## Detect if running on embedded/IoT device
  ## Uses multiple indicators for robust detection
  try:
    var indicators: seq[bool] = @[]

    # Check for OpenWrt
    indicators.add(fileExists("/etc/openwrt_release"))

    # Check for device tree (ARM devices)
    indicators.add(fileExists("/proc/device-tree"))

    # Check memory (< 512MB suggests embedded)
    let memTotal = getMemoryTotal()
    indicators.add(memTotal > 0 and memTotal < 512 * 1024 * 1024)

    # Check CPU count (<= 2 cores suggests embedded)
    let cpuCount = getCPUCount()
    indicators.add(cpuCount <= 2)

    # Check for Raspberry Pi
    indicators.add(fileExists("/proc/device-tree/model"))

    # Need at least 2 indicators to be confident
    let trueCount = indicators.countIt(it)
    return trueCount >= 2
  except:
    return false

# ============================================================================
# Main Platform Detection
# ============================================================================

proc detectPlatform*(): PlatformCapabilities =
  ## Detect OS and capabilities at runtime
  ##
  ## This is the main entry point for platform detection. It queries the
  ## system for OS type, kernel version, and available isolation capabilities.

  let osType = detectOSType()
  let isRootUser = isRoot()
  let kernelVersion = getKernelVersion()
  let isEmbedded = detectEmbeddedDevice()
  let memoryTotal = getMemoryTotal()
  let cpuCount = getCPUCount()

  case osType:
  of Linux:
    let hasUserNS = checkUserNamespaceSupport()
    return PlatformCapabilities(
      osType: Linux,
      hasUserNamespaces: hasUserNS,
      hasJails: false,
      hasUnveil: false,
      isRoot: isRootUser,
      kernelVersion: kernelVersion,
      isEmbedded: isEmbedded,
      memoryTotal: memoryTotal,
      cpuCount: cpuCount
    )

  of FreeBSD:
    let hasJails = checkJailSupport()
    return PlatformCapabilities(
      osType: FreeBSD,
      hasUserNamespaces: false,
      hasJails: hasJails,
      hasUnveil: false,
      isRoot: isRootUser,
      kernelVersion: kernelVersion,
      isEmbedded: isEmbedded,
      memoryTotal: memoryTotal,
      cpuCount: cpuCount
    )

  of OpenBSD:
    let hasUnveil = checkUnveilSupport()
    return PlatformCapabilities(
      osType: OpenBSD,
      hasUserNamespaces: false,
      hasJails: false,
      hasUnveil: hasUnveil,
      isRoot: isRootUser,
      kernelVersion: kernelVersion,
      isEmbedded: isEmbedded,
      memoryTotal: memoryTotal,
      cpuCount: cpuCount
    )

  else:
    return PlatformCapabilities(
      osType: osType,
      hasUserNamespaces: false,
      hasJails: false,
      hasUnveil: false,
      isRoot: isRootUser,
      kernelVersion: kernelVersion,
      isEmbedded: isEmbedded,
      memoryTotal: memoryTotal,
      cpuCount: cpuCount
    )

# ============================================================================
# Isolation Strategy Selection
# ============================================================================

proc selectStrategy*(caps: PlatformCapabilities): IsolationStrategy =
  ## Select best isolation strategy based on platform capabilities
  ##
  ## This implements the strategy selection algorithm:
  ## 1. Check for platform-specific advanced isolation
  ## 2. Fall back to POSIX fallback if not available
  ## 3. Ensure graceful degradation

  case caps.osType:
  of Linux:
    if caps.hasUserNamespaces:
      return LinuxNamespace  # Preferred: kernel-enforced isolation
    else:
      return POSIXFallback   # Fallback: chmod + Merkle verification

  of FreeBSD:
    if caps.hasJails and caps.isRoot:
      return FreeBSDJail     # Preferred: elegant BSD solution
    else:
      return POSIXFallback   # Fallback: chmod + root

  of OpenBSD:
    if caps.hasUnveil and caps.isRoot:
      return OpenBSDUnveil   # Preferred: capability-based security
    else:
      return POSIXFallback   # Fallback: chmod + root

  else:
    return POSIXFallback     # Default: POSIX fallback for all others

proc selectMode*(strategy: IsolationStrategy, userRequest: Option[InstallMode]): InstallMode =
  ## Select installation mode based on strategy and user request
  ##
  ## Modes:
  ## - UserMode: User-level installation (Linux with namespaces only)
  ## - SystemMode: System-wide installation (requires root)

  # User explicitly requested a mode
  if userRequest.isSome:
    let requested = userRequest.get()

    case requested:
    of UserMode:
      if strategy == LinuxNamespace:
        return UserMode  # OK: Linux with namespaces
      else:
        echo "❌ User mode not available on this platform"
        echo "   Strategy: " & $strategy
        echo "   Falling back to system mode (requires root)"
        return SystemMode

    of SystemMode:
      return SystemMode  # Always possible if root

  # Auto-select based on strategy
  case strategy:
  of LinuxNamespace:
    return UserMode      # Linux: prefer user mode

  of FreeBSDJail, OpenBSDUnveil:
    return SystemMode    # BSD: requires root

  of POSIXFallback:
    if isRoot():
      return SystemMode  # Root: use system mode
    else:
      return UserMode    # Non-root: use user mode (with warnings)

# ============================================================================
# Strategy Information
# ============================================================================

proc getStrategyDescription*(strategy: IsolationStrategy): string =
  ## Get human-readable description of isolation strategy
  case strategy:
  of LinuxNamespace:
    return "Linux user namespaces (kernel-enforced read-only)"
  of FreeBSDJail:
    return "FreeBSD jails with nullfs (elegant BSD solution)"
  of OpenBSDUnveil:
    return "OpenBSD unveil + pledge (capability-based security)"
  of POSIXFallback:
    return "POSIX fallback (chmod + Merkle verification)"

proc getSecurityLevel*(strategy: IsolationStrategy): int =
  ## Get security level (1-5 stars)
  ## This is informational only - all strategies provide security through Merkle verification
  case strategy:
  of LinuxNamespace:
    return 5  # Kernel-enforced
  of FreeBSDJail:
    return 5  # Kernel-enforced, mature
  of OpenBSDUnveil:
    return 4  # Capability-based, but reset on exec
  of POSIXFallback:
    return 1  # UX convenience only (Merkle is primary security)

proc getStrategyInfo*(strategy: IsolationStrategy): string =
  ## Get detailed information about isolation strategy
  let desc = getStrategyDescription(strategy)
  let level = getSecurityLevel(strategy)
  let stars = "⭐".repeat(level)

  case strategy:
  of LinuxNamespace:
    return fmt"{desc}\n{stars}\nKernel-enforced read-only mount prevents any writes"
  of FreeBSDJail:
    return fmt"{desc}\n{stars}\nProcess confined to jail, cannot escape"
  of OpenBSDUnveil:
    return fmt"{desc}\n{stars}\nPath-based access control with capability restrictions"
  of POSIXFallback:
    return fmt"{desc}\n{stars}\nPrimary security: Merkle verification detects tampering"

# ============================================================================
# Embedded Device Constraints
# ============================================================================

proc getEmbeddedConstraints*(): EmbeddedConstraints =
  ## Get constraints for embedded devices
  ##
  ## Embedded devices have limited resources, so we adjust:
  ## - Reduce concurrent operations
  ## - Limit cache size
  ## - Disable parallelization on single-core devices

  let memoryTotal = getMemoryTotal()
  let cpuCount = getCPUCount()

  return EmbeddedConstraints(
    maxConcurrentDownloads: if memoryTotal < 256 * 1024 * 1024: 1 else: 2,
    maxConcurrentBuilds: 1,
    maxCacheSize: if memoryTotal < 256 * 1024 * 1024: 50 * 1024 * 1024 else: 100 * 1024 * 1024,
    enableCompression: true,
    enableDeduplication: true,
    enableParallelization: cpuCount > 2
  )

proc formatBytes*(bytes: int64): string =
  ## Format bytes as human-readable string
  if bytes < 1024:
    return fmt"{bytes}B"
  elif bytes < 1024 * 1024:
    return fmt"{bytes div 1024}KB"
  elif bytes < 1024 * 1024 * 1024:
    return fmt"{bytes div (1024 * 1024)}MB"
  else:
    return fmt"{bytes div (1024 * 1024 * 1024)}GB"

proc printEmbeddedConstraints*(constraints: EmbeddedConstraints) =
  ## Print embedded device constraints
  echo "📱 Embedded device detected"
  echo "   Max concurrent downloads: " & $constraints.maxConcurrentDownloads
  echo "   Max concurrent builds: " & $constraints.maxConcurrentBuilds
  echo "   Max cache size: " & formatBytes(constraints.maxCacheSize)
  echo "   Compression enabled: " & $constraints.enableCompression
  echo "   Deduplication enabled: " & $constraints.enableDeduplication
  echo "   Parallelization enabled: " & $constraints.enableParallelization

# ============================================================================
# Platform Summary
# ============================================================================

proc printPlatformInfo*(caps: PlatformCapabilities) =
  ## Print platform information for debugging
  echo "🖥️  Platform Information"
  echo "   OS: " & getOSTypeString(caps.osType)
  echo "   Kernel: " & caps.kernelVersion
  echo "   Root: " & $caps.isRoot
  echo "   Memory: " & formatBytes(caps.memoryTotal)
  echo "   CPUs: " & $caps.cpuCount
  echo "   Embedded: " & $caps.isEmbedded

  echo ""
  echo "🔒 Isolation Capabilities"
  echo "   User Namespaces: " & $caps.hasUserNamespaces
  echo "   Jails: " & $caps.hasJails
  echo "   Unveil: " & $caps.hasUnveil

  let strategy = selectStrategy(caps)
  echo ""
  echo "📋 Selected Strategy"
  echo "   " & getStrategyDescription(strategy)
  echo "   Security Level: " & "⭐".repeat(getSecurityLevel(strategy))

  if caps.isEmbedded:
    echo ""
    let constraints = getEmbeddedConstraints()
    printEmbeddedConstraints(constraints)
