## nimpak/cache/compatibility.nim
## Binary compatibility detection for NimPak cache system
##
## This module provides intelligent binary compatibility detection including:
## - CPU architecture and feature detection
## - libc version compatibility checking
## - Memory allocator compatibility
## - ABI version validation
## - Platform-specific compatibility rules

import std/[os, strutils, strformat, sequtils, tables, algorithm, sets]
import ../cli/core

type
  Architecture* = enum
    ArchX86_64 = "x86_64"
ch64 = "aarch64"
    ArchArm = "arm"
    ArchRiscV64 = "riscv64"
    ArchPpc64 = "ppc64"
    ArchS390x = "s390x"

  LibcType* = enum
    LibcGlibc = "glibc"
    LibcMusl = "musl"
    LibcBionic = "bionic"
    LibcNewlib = "newlib"

  AllocatorType* = enum
    AllocDefault = "default"
    AllocJemalloc = "jemalloc"
    AllocTcmalloc = "tcmalloc"
    AllocMimalloc = "mimalloc"

  CompatibilityInfo* = object
    architecture*: Architecture
    libc*: LibcType
    libcVersion*: string
    allocator*: AllocatorType
    allocatorVersion*: string
    cpuFeatures*: seq[string]
    abiVersion*: string
    kernelVersion*: string
    additionalTags*: seq[string]

  SystemInfo* = object
    architecture*: Architecture
    libc*: LibcType
    libcVersion*: string
    allocator*: AllocatorType
    allocatorVersion*: string
    availableCpuFeatures*: seq[string]
    abiVersion*: string
    kernelVersion*: string
    strictMode*: bool              # Whether to enforce strict compatibility

  CompatibilityResult* = object
    compatible*: bool
    score*: float                  # Compatibility score (0.0-1.0)
    reasons*: seq[string]          # Reasons for compatibility/incompatibility
    warnings*: seq[string]        # Compatibility warnings

  CompatibilityRule* = object
    name*: string
    description*: string
    weight*: float                 # Rule weight in scoring
    check*: proc(binary: CompatibilityInfo, system: SystemInfo): tuple[compatible: bool, score: float, reason: string]

  CompatibilityDetector* = object
    rules*: seq[CompatibilityRule]
    config*: CompatibilityConfig

  CompatibilityConfig* = object
    strictArchitecture*: bool      # Require exact architecture match
    allowLibcUpgrade*: bool        # Allow newer libc versions
    allowAllocatorMismatch*: bool  # Allow different allocators
    requireCpuFeatures*: bool      # Require all CPU features
    minCompatibilityScore*: float  # Minimum score for compatibility
    enableWarnings*: bool          # Generate compatibility warnings

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

proc detectCurrentSystem*(): SystemInfo =
  ## Detect current system compatibility information
  var system = SystemInfo(
    strictMode: false
  )

  # Detect architecture
  when defined(amd64) or defined(x86_64):
    system.architecture = ArchX86_64
  elif defined(arm64) or defined(aarch64):
    system.architecture = ArchAarch64
  elif defined(arm):
    system.architecture = ArchArm
  elif defined(riscv64):
    system.architecture = ArchRiscV64
  elif defined(powerpc64):
    system.architecture = ArchPpc64
  elif defined(s390x):
    system.architecture = ArchS390x
  else:
    system.architecture = ArchX86_64  # Default fallback

  # Detect libc
  when defined(linux):
    if fileExists("/lib/ld-musl-x86_64.so.1") or fileExists("/usr/lib/libc.musl-x86_64.so.1"):
      system.libc = LibcMusl
      system.libcVersion = detectMuslVersion()
    else:
      system.libc = LibcGlibc
      system.libcVersion = detectGlibcVersion()
  elif defined(android):
    system.libc = LibcBionic
    system.libcVersion = detectBionicVersion()
  else:
    system.libc = LibcGlibc
    system.libcVersion = "2.31"  # Conservative default

  # Detect allocator
  system.allocator = detectAllocator()
  system.allocatorVersion = detectAllocatorVersion(system.allocator)

  # Detect CPU features
  system.availableCpuFeatures = detectCpuFeatures()

  # Detect ABI version
  system.abiVersion = detectAbiVersion()

  # Detect kernel version
  system.kernelVersion = detectKernelVersion()

  return system

proc detectMuslVersion*(): string =
  ## Detect musl libc version
  try:
    # Try to read version from musl
    if fileExists("/usr/lib/libc.musl-x86_64.so.1"):
      # TODO: Implement actual version detection
      return "1.2.4"
    else:
      return "1.2.0"  # Conservative default
  except:
    return "1.2.0"

proc detectGlibcVersion*(): string =
  ## Detect glibc version
  try:
    # Try to get version from ldd
    let output = execProcess("ldd --version")
    for line in output.splitLines():
      if "ldd" in line and "GLIBC" in line:
        let parts = line.split()
        if parts.len > 0:
          return parts[^1]
    return "2.31"  # Conservative default
  except:
    return "2.31"

proc detectBionicVersion*(): string =
  ## Detect bionic libc version
  try:
    # TODO: Implement bionic version detection
    return "29"  # Android API level
  except:
    return "21"

proc detectAllocator*(): AllocatorType =
  ## Detect current memory allocator
  try:
    # Check for jemalloc
    if fileExists("/usr/lib/libjemalloc.so") or fileExists("/usr/local/lib/libjemalloc.so"):
      return AllocJemalloc

    # Check for tcmalloc
    if fileExists("/usr/lib/libtcmalloc.so") or fileExists("/usr/local/lib/libtcmalloc.so"):
      return AllocTcmalloc

    # Check for mimalloc
    if fileExists("/usr/lib/libmimalloc.so") or fileExists("/usr/local/lib/libmimalloc.so"):
      return AllocMimalloc

    return AllocDefault
  except:
    return AllocDefault

proc detectAllocatorVersion*(allocator: AllocatorType): string =
  ## Detect allocator version
  case allocator:
  of AllocJemalloc:
    return "5.3.0"  # TODO: Implement actual detection
  of AllocTcmalloc:
    return "2.10"
  of AllocMimalloc:
    return "2.1.2"
  of AllocDefault:
    return "system"

proc detectCpuFeatures*(): seq[string] =
  ## Detect available CPU features
  var features: seq[string] = @[]

  try:
    when defined(linux):
      if fileExists("/proc/cpuinfo"):
        let cpuinfo = readFile("/proc/cpuinfo")
        for line in cpuinfo.splitLines():
          if line.startsWith("flags") or line.startsWith("Features"):
            let flagsStr = line.split(":")[1].strip()
            features = flagsStr.split()
            break

    # Add common features if not detected
    if features.len == 0:
      features = @["sse2", "sse4_1", "sse4_2"]  # Conservative defaults

    return features.deduplicate()
  except:
    return @["sse2"]  # Minimal fallback

proc detectAbiVersion*(): string =
  ## Detect ABI version
  try:
    # TODO: Implement actual ABI version detection
    return "1.0"
  except:
    return "1.0"

proc detectKernelVersion*(): string =
  ## Detect kernel version
  try:
    when defined(linux):
      let output = execProcess("uname -r")
      return output.strip()
    else:
      return "unknown"
  except:
    return "unknown"

# =============================================================================
# Compatibility Rules
# =============================================================================

proc createArchitectureRule*(): CompatibilityRule =
  ## Rule for architecture compatibility
  CompatibilityRule(
    name: "architecture",
    description: "CPU architecture compatibility",
    weight: 1.0,
    check: proc(binary: CompatibilityInfo, system: SystemInfo): tuple[compatible: bool, score: float, reason: string] =
      if binary.architecture == system.architecture:
        return (true, 1.0, "Architecture match")
      else:
        # Check for compatible architectures
        case binary.architecture:
        of ArchX86_64:
          if system.architecture == ArchX86_64:
            return (true, 1.0, "Architecture match")
          else:
            return (false, 0.0, fmt"Architecture mismatch: {binary.architecture} vs {system.architecture}")
        of ArchAarch64:
          if system.architecture in [ArchAarch64, ArchArm]:
            return (true, 0.8, "Compatible ARM architecture")
          else:
            return (false, 0.0, fmt"Architecture mismatch: {binary.architecture} vs {system.architecture}")
        else:
          return (false, 0.0, fmt"Architecture mismatch: {binary.architecture} vs {system.architecture}")
  )

proc createLibcRule*(): CompatibilityRule =
  ## Rule for libc compatibility
  CompatibilityRule(
    name: "libc",
    description: "C library compatibility",
    weight: 0.9,
    check: proc(binary: CompatibilityInfo, system: SystemInfo): tuple[compatible: bool, score: float, reason: string] =
      if binary.libc == system.libc:
        # Check version compatibility
        let binaryVersion = parseVersion(binary.libcVersion)
        let systemVersion = parseVersion(system.libcVersion)

        if compareVersions(systemVersion, binaryVersion) >= 0:
          return (true, 1.0, "libc version compatible")
        else:
          return (false, 0.0, fmt"libc version too old: need {binary.libcVersion}, have {system.libcVersion}")
      else:
        # Different libc types
        case (binary.libc, system.libc):
        of (LibcMusl, LibcGlibc):
          return (false, 0.0, "musl binary incompatible with glibc system")
        of (LibcGlibc, LibcMusl):
          return (false, 0.0, "glibc binary incompatible with musl system")
        else:
          return (false, 0.0, fmt"libc mismatch: {binary.libc} vs {system.libc}")
  )

proc createAllocatorRule*(): CompatibilityRule =
  ## Rule for allocator compatibility
  CompatibilityRule(
    name: "allocator",
    description: "Memory allocator compatibility",
    weight: 0.3,
    check: proc(binary: CompatibilityInfo, system: SystemInfo): tuple[compatible: bool, score: float, reason: string] =
      if binary.allocator == system.allocator:
        return (true, 1.0, "Allocator match")
      elif binary.allocator == AllocDefault:
        return (true, 0.9, "Default allocator is compatible")
      elif system.allocator == AllocDefault:
        return (true, 0.8, "System default allocator can use any binary")
      else:
        return (true, 0.6, fmt"Allocator mismatch but compatible: {binary.allocator} vs {system.allocator}")
  )

proc createCpuFeaturesRule*(): CompatibilityRule =
  ## Rule for CPU features compatibility
  CompatibilityRule(
    name: "cpu_features",
    description: "CPU features compatibility",
    weight: 0.7,
    check: proc(binary: CompatibilityInfo, system: SystemInfo): tuple[compatible: bool, score: float, reason: string] =
      let requiredFeatures = binary.cpuFeatures.toHashSet()
      let availableFeatures = system.availableCpuFeatures.toHashSet()

      let missingFeatures = requiredFeatures - availableFeatures

      if missingFeatures.len == 0:
        return (true, 1.0, "All CPU features available")
      else:
        let criticalFeatures = ["sse2", "sse4_1", "sse4_2", "avx", "avx2"].toHashSet()
        let missingCritical = missingFeatures * criticalFeatures

        if missingCritical.len > 0:
          return (false, 0.0, fmt"Missing critical CPU features: {missingCritical.toSeq().join(\", \")}")
        else:
          let score = 1.0 - (missingFeatures.len.float / requiredFeatures.len.float) * 0.5
          return (true, score, fmt"Missing optional CPU features: {missingFeatures.toSeq().join(\", \")}")
  )

proc createAbiRule*(): CompatibilityRule =
  ## Rule for ABI compatibility
  CompatibilityRule(
    name: "abi",
    description: "ABI version compatibility",
    weight: 0.8,
    check: proc(binary: CompatibilityInfo, system: SystemInfo): tuple[compatible: bool, score: float, reason: string] =
      if binary.abiVersion == system.abiVersion:
        return (true, 1.0, "ABI version match")
      else:
        let binaryAbi = parseVersion(binary.abiVersion)
        let systemAbi = parseVersion(system.abiVersion)

        if binaryAbi.major == systemAbi.major:
          if binaryAbi.minor <= systemAbi.minor:
            return (true, 0.9, "ABI version compatible")
          else:
            return (false, 0.0, fmt"ABI version too new: need {binary.abiVersion}, have {system.abiVersion}")
        else:
          return (false, 0.0, fmt"ABI major version mismatch: {binary.abiVersion} vs {system.abiVersion}")
  )

# =============================================================================
# Compatibility Detector
# =============================================================================

proc newCompatibilityDetector*(): CompatibilityDetector =
  ## Create a new compatibility detector with default rules
  var detector = CompatibilityDetector(
    rules: @[],
    config: CompatibilityConfig(
      strictArchitecture: true,
      allowLibcUpgrade: true,
      allowAllocatorMismatch: true,
      requireCpuFeatures: true,
      minCompatibilityScore: 0.7,
      enableWarnings: true
    )
  )

  # Add default rules
  detector.rules.add(createArchitectureRule())
  detector.rules.add(createLibcRule())
  detector.rules.add(createAllocatorRule())
  detector.rules.add(createCpuFeaturesRule())
  detector.rules.add(createAbiRule())

  return detector

proc checkCompatibility*(detector: CompatibilityDetector, binary: CompatibilityInfo,
                        system: SystemInfo): CompatibilityResult =
  ## Check compatibility between binary and system
  var result = CompatibilityResult(
    compatible: true,
    score: 0.0,
    reasons: @[],
    warnings: @[]
  )

  var totalWeight = 0.0
  var weightedScore = 0.0

  # Apply all rules
  for rule in detector.rules:
    let (ruleCompatible, ruleScore, ruleReason) = rule.check(binary, system)

    totalWeight += rule.weight
    weightedScore += ruleScore * rule.weight

    if not ruleCompatible:
      result.compatible = false
      result.reasons.add(fmt"{rule.name}: {ruleReason}")
    elif ruleScore < 1.0 and detector.config.enableWarnings:
      result.warnings.add(fmt"{rule.name}: {ruleReason}")

  # Calculate overall score
  if totalWeight > 0:
    result.score = weightedScore / totalWeight
  else:
    result.score = 0.0

  # Apply minimum score threshold
  if result.score < detector.config.minCompatibilityScore:
    result.compatible = false
    result.reasons.add(fmt"Compatibility score {result.score:.3f} below minimum {detector.config.minCompatibilityScore:.3f}")

  return result

proc calculateCompatibilityScore*(detector: CompatibilityDetector, binary: CompatibilityInfo,
                                 system: SystemInfo): float =
  ## Calculate compatibility score without full check
  let result = detector.checkCompatibility(binary, system)
  return result.score

# =============================================================================
# Platform String Formatting
# =============================================================================

proc formatPlatformString*(compatibility: CompatibilityInfo): string =
  ## Format compatibility info as platform string
  let features = if compatibility.cpuFeatures.len > 0:
    compatibility.cpuFeatures.sorted().join(",")
  else:
    "baseline"

  return fmt"{compatibility.architecture}-{compatibility.libc}-{compatibility.libcVersion}-{compatibility.allocator}-{features}"

proc parsePlatformString*(platformStr: string): CompatibilityInfo =
  ## Parse platform string into compatibility info
  let parts = platformStr.split("-")
  if parts.len >= 4:
    let features = if parts.len > 4: parts[4].split(",") else: @[]

    return CompatibilityInfo(
      architecture: parseEnum[Architecture](parts[0]),
      libc: parseEnum[LibcType](parts[1]),
      libcVersion: parts[2],
      allocator: parseEnum[AllocatorType](parts[3]),
      allocatorVersion: "",
      cpuFeatures: features,
      abiVersion: "1.0",
      kernelVersion: "",
      additionalTags: @[]
    )
  else:
    # Return default compatibility info
    return CompatibilityInfo(
      architecture: ArchX86_64,
      libc: LibcGlibc,
      libcVersion: "2.31",
      allocator: AllocDefault,
      allocatorVersion: "system",
      cpuFeatures: @[],
      abiVersion: "1.0",
      kernelVersion: "",
      additionalTags: @[]
    )

# =============================================================================
# Version Parsing Utilities
# =============================================================================

type
  Version* = object
    major*: int
    minor*: int
    patch*: int

proc parseVersion*(versionStr: string): Version =
  ## Parse version string into components
  try:
    let parts = versionStr.split(".")
    if parts.len >= 2:
      return Version(
        major: parts[0].parseInt(),
        minor: parts[1].parseInt(),
        patch: if parts.len > 2: parts[2].parseInt() else: 0
      )
    else:
      return Version(major: 0, minor: 0, patch: 0)
  except:
    return Version(major: 0, minor: 0, patch: 0)

proc compareVersions*(a: Version, b: Version): int =
  ## Compare two versions (-1: a < b, 0: a == b, 1: a > b)
  if a.major != b.major:
    return cmp(a.major, b.major)
  elif a.minor != b.minor:
    return cmp(a.minor, b.minor)
  else:
    return cmp(a.patch, b.patch)

# =============================================================================
# Export main functions
# =============================================================================

export Architecture, LibcType, AllocatorType, CompatibilityInfo, SystemInfo
export CompatibilityResult, CompatibilityRule, CompatibilityDetector, CompatibilityConfig
export detectCurrentSystem, detectMuslVersion, detectGlibcVersion, detectAllocator
export detectCpuFeatures, detectAbiVersion, detectKernelVersion
export createArchitectureRule, createLibcRule, createAllocatorRule
export createCpuFeaturesRule, createAbiRule
export newCompatibilityDetector, checkCompatibility, calculateCompatibilityScore
export formatPlatformString, parsePlatformString
export Version, parseVersion, compareVersions