## build_commands.nim
## CLI commands for building packages from source with variants
## SECURITY: Fixed all command injection vulnerabilities

import std/[strformat, strutils, tables, os, posix, osproc, times, options]
import ../graft_coordinator, ../install_manager, ../config, ../logger
import ../variants
import ../build/[types, coordinator, adapter, nix_adapter, pkgsrc_adapter, gentoo_adapter, variant_mapper, cache, bootstrap, binary_cache, remote_cache]

# Global coordinators
var globalGraftCoordinator: GraftCoordinator = nil
var globalBuildCoordinator: BuildCoordinator = nil

# SECURITY: Package name validation
proc isValidPackageName*(name: string): bool =
  ## Validate package name contains only safe characters
  ## Prevents command injection and path traversal
  if name.len == 0 or name.len > 255:
    return false

  # Allow: alphanumeric, dash, underscore, dot (for Nix packages like nixpkgs.firefox)
  for c in name:
    if c notin {'a'..'z', 'A'..'Z', '0'..'9', '-', '_', '.'}:
      return false

  # Prevent path traversal
  if ".." in name or name.startsWith("/") or name.startsWith("\\"):
    return false

  return true

# SECURITY: Validate Nix override keys
proc isValidNixKey*(key: string): bool =
  ## Validate Nix override key contains only safe characters
  if key.len == 0 or key.len > 100:
    return false

  for c in key:
    if c notin {'a'..'z', 'A'..'Z', '0'..'9', '-', '_'}:
      return false

  return true

proc initBuildCommands*(verbose: bool = false) =
  ## Initialize the build command system
  let logLevel = if verbose: Debug else: Info
  let logPath = if getuid() == 0: "/var/log/nip.log" else: getEnv("XDG_CACHE_HOME", getHomeDir() / ".cache") / "nip" / "nip.log"
  initGlobalLogger(logPath, logLevel, verbose)
  logInfo("NIP Build started")

  # Load proper config (XDG-compliant, user-aware)
  let nipConfig = config.loadConfig()

  # Convert NipConfig to InstallConfig for coordinator
  let installConfig = InstallConfig(
    programsDir: nipConfig.programsDir,
    linksDir: nipConfig.linksDir,
    cacheDir: nipConfig.cacheDir,
    dbFile: nipConfig.dbFile,
    autoSymlink: nipConfig.autoSymlink,
    checkConflicts: nipConfig.checkConflicts,
    verbose: verbose
  )

  globalGraftCoordinator = newGraftCoordinator(installConfig, verbose)

  # Create build configuration
  let buildConfig = BuildConfig(
    cacheDir: nipConfig.cacheDir,
    buildLogsDir: nipConfig.cacheDir / "logs",
    keepWork: false,
    rebuild: false,
    noInstall: false,
    timeout: initDuration(hours = 2),
    jobs: 4,
    verbose: verbose
  )

  # Create build coordinator
  globalBuildCoordinator = newBuildCoordinator(buildConfig, globalGraftCoordinator)

  # Register adapters
  let nixAdapter = newNixAdapter()
  globalBuildCoordinator.registerAdapter(nixAdapter)

  let pkgsrcAdapter = newPkgsrcAdapter()
  globalBuildCoordinator.registerAdapter(pkgsrcAdapter)

  let gentooAdapter = newGentooAdapter()
  globalBuildCoordinator.registerAdapter(gentooAdapter)

proc ensureInitialized() =
  ## Ensure coordinators are initialized
  if globalGraftCoordinator == nil or globalBuildCoordinator == nil:
    initBuildCommands()

proc mapVariantsToSourceFlags(packageName: string, variants: Table[string, seq[string]], source: string): tuple[flags: seq[string], unmapped: seq[string]] =
  ## Map NIP variant flags to source-specific flags using the mapping system
  let mapper = newVariantMapper()

  # Try to load custom mappings
  let configDir = getEnv("XDG_CONFIG_HOME", getHomeDir() / ".config")
  let customMappingFile = configDir / "nip" / "variant-mappings.json"
  mapper.loadCustomMappings(customMappingFile)

  result = mapper.mapAllVariants(packageName, variants, source)

proc buildCommand*(packageName: string, variantFlags: seq[string], source: string = "auto", verbose: bool = false): int =
  ## Build a package from source with variant flags
  ## source: "auto", "nix", "pkgsrc", "gentoo"
  ## SECURITY: All inputs validated before use in commands

  # SECURITY: Validate package name first
  if not isValidPackageName(packageName):
    echo "❌ Invalid package name: contains unsafe characters"
    echo "   Package names must contain only: a-z, A-Z, 0-9, -, _, ."
    return 1

  ensureInitialized()
  if verbose:
    globalGraftCoordinator.verbose = true
    globalBuildCoordinator.verbose = true

  # Header
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "🔨 NIP Build - Source Building with Variants"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo ""
  echo fmt"📦 Package:  {packageName}"

  # Determine source
  var selectedSource = source.toLower()
  if selectedSource == "auto":
    # Priority: nix > pkgsrc > gentoo
    if dirExists("/nix") or isToolInstalled(bttNix):
      selectedSource = "nix"
      echo "🔍 Source:   Nix (auto-detected)"
    elif dirExists("/usr/pkgsrc") or isToolInstalled(bttPkgsrc):
      selectedSource = "pkgsrc"
      echo "🔍 Source:   PKGSRC (auto-detected)"
    elif fileExists("/usr/bin/emerge") or isToolInstalled(bttGentoo):
      selectedSource = "gentoo"
      echo "🔍 Source:   Gentoo (auto-detected)"
    else:
      # No source available - offer bootstrap
      echo ""
      echo "❌ No supported source build system found"
      echo ""
      echo "NIP can help you set up a build system."
      echo "Which would you like to try first?"
      echo ""
      echo "1. Nix (recommended) - 100,000+ packages"
      echo "2. PKGSRC - 27,000+ packages, portable"
      echo "3. Gentoo - 20,000+ packages, highly customizable"
      echo ""
      stdout.write("Choose (1-3) or 'q' to quit: ")
      stdout.flushFile()

      let choice = stdin.readLine().strip()
      case choice
      of "1":
        if not handleMissingTool(bttNix):
          return 1
        selectedSource = "nix"
      of "2":
        if not handleMissingTool(bttPkgsrc):
          return 1
        selectedSource = "pkgsrc"
      of "3":
        if not handleMissingTool(bttGentoo):
          return 1
        selectedSource = "gentoo"
      else:
        echo ""
        echo "❌ Build cancelled"
        return 1
  else:
    echo fmt"🔍 Source:   {selectedSource} (manual)"

    # Check if selected source is available, offer bootstrap if not
    let toolType = case selectedSource
      of "nix": bttNix
      of "pkgsrc": bttPkgsrc
      of "gentoo": bttGentoo
      else: bttNix  # fallback

    if not isSystemToolAvailable(toolType) and not isToolInstalled(toolType):
      if not handleMissingTool(toolType):
        return 1

  echo ""

  # Parse variant flags
  var variants = initTable[string, seq[string]]()
  var hasInvalidFlags = false

  if variantFlags.len > 0:
    echo "🎯 Variants:"
    for flag in variantFlags:
      try:
        let parsed = parseVariantFlag(flag)
        if not variants.hasKey(parsed.domain):
          variants[parsed.domain] = @[]
        variants[parsed.domain].add(parsed.value)
        echo fmt"   • {parsed.domain} = {parsed.value}"
      except ValueError as e:
        echo fmt"   ⚠️  Invalid: {flag} ({e.msg})"
        hasInvalidFlags = true
  else:
    echo "ℹ️  Variants: None (using defaults)"

  echo ""

  if hasInvalidFlags:
    echo "⚠️  Some variant flags were invalid and will be ignored"
    echo ""

  # Map variants to source-specific flags
  echo "🔄 Translating variants to source-specific flags..."
  let (sourceFlags, unmapped) = mapVariantsToSourceFlags(packageName, variants, selectedSource)

  if sourceFlags.len > 0:
    echo ""
    echo fmt"🔧 {selectedSource.toUpper()} Build Configuration:"
    for flag in sourceFlags:
      echo fmt"   • {flag}"
  else:
    echo ""
    echo "ℹ️  No source-specific flags (using defaults)"

  if unmapped.len > 0:
    echo ""
    echo "⚠️  Unmapped Variants (will be ignored):"
    for flag in unmapped:
      echo fmt"   • {flag}"
    echo ""
    echo "💡 Tip: Add custom mappings in ~/.config/nip/variant-mappings.json"

  echo ""

  # Check binary cache first (local then remote)
  let bcm = newBinaryCacheManager()
  let variantFp = calculateVariantFingerprint(
    useFlags = if variants.hasKey("use"): variants["use"] else: @[],
    cflags = if variants.hasKey("cflags"): variants["cflags"].join(" ") else: "",
    ldflags = if variants.hasKey("ldflags"): variants["ldflags"].join(" ") else: ""
  )

  # Try local cache first
  var cacheEntry = bcm.lookup(packageName, "", variantFp)

  # If not in local cache, try remote cache
  if cacheEntry.isNone:
    let remoteConfig = remote_cache.loadConfig()
    if remoteConfig.enabled:
      echo "🌐 Checking remote cache..."
      let remoteClient = newRemoteCacheClient(remoteConfig, bcm)

      if remoteClient.checkAvailability():
        let downloaded = remoteClient.download(packageName, "", variantFp)
        if downloaded:
          echo "✅ Downloaded from remote cache"
          # Now check local cache again
          cacheEntry = bcm.lookup(packageName, "", variantFp)
      else:
        echo "⚠️  Remote cache unavailable"

  if cacheEntry.isSome:
    let entry = cacheEntry.get()
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "⚡ Cache Hit!"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
    echo fmt"📦 Package:  {entry.packageName}-{entry.version}"
    echo fmt"💾 Cached:   {binary_cache.formatSize(entry.size)}"
    echo fmt"🕐 Age:      {(getTime() - entry.buildTime).inDays} days"
    echo ""
    echo "✅ Using cached artifact (instant build!)"
    echo ""
    echo "💡 To rebuild from source: nip build --no-cache ", packageName
    return 0

  # Generate build expression based on source
  if selectedSource == "nix":
    # SECURITY: All validation happens in the adapter
    try:
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      echo "🔨 Starting Build Process"
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      echo ""

      # Create build request
      let request = BuildRequest(
        packageName: packageName,
        version: "",
        variantFlags: variants,
        sourceFlags: sourceFlags,
        cacheDir: globalBuildCoordinator.config.cacheDir,
        verbose: verbose
      )

      # Get Nix adapter
      let nixAdapter = globalBuildCoordinator.getAdapter("nix")
      if nixAdapter.isNone:
        echo "❌ Error: Nix adapter not available"
        return 1

      # Track build time
      let buildStartTime = getTime()
      echo "⏱️  Build started at: " & now().format("HH:mm:ss")
      echo ""
      echo "🔨 Building from source (this may take a while)..."
      if not verbose:
        echo "   💡 Use --verbose for detailed output"
      echo ""

      # Build package
      let buildResult = nixAdapter.get().buildPackage(request)
      let buildEndTime = getTime()
      let buildDuration = (buildEndTime - buildStartTime).inSeconds

      if not buildResult.success:
        echo ""
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "❌ Build Failed"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo ""
        echo "Errors:"
        for error in buildResult.errors:
          echo fmt"  • {error}"
        echo ""
        if buildResult.buildLog != "":
          echo "💡 Check build log for details"
        echo ""
        echo fmt"⏱️  Build duration: {buildDuration} seconds"
        return 1

      echo ""
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      echo "✅ Build Successful!"
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      echo ""
      echo fmt"📦 Artifact:  {buildResult.artifactPath}"
      echo fmt"⏱️  Duration:  {buildDuration} seconds"
      if buildResult.variantFingerprint != "":
        echo fmt"🔑 Variant:   {buildResult.variantFingerprint[0..15]}..."
      echo ""

      # Store in cache for future builds
      if fileExists(buildResult.artifactPath):
        echo "💾 Caching artifact for future builds..."
        let cached = bcm.store(
          packageName,
          buildResult.version,
          variantFp,
          buildResult.artifactPath,
          initDuration(seconds = buildDuration.int)
        )
        if cached:
          echo "✅ Artifact cached locally"

          # Upload to remote cache if enabled
          let remoteConfig = remote_cache.loadConfig()
          if remoteConfig.enabled:
            echo "🌐 Uploading to remote cache..."
            let remoteClient = newRemoteCacheClient(remoteConfig, bcm)

            if remoteClient.checkAvailability():
              let uploaded = remoteClient.upload(
                packageName,
                buildResult.version,
                variantFp,
                buildResult.artifactPath
              )
              if uploaded:
                echo "✅ Uploaded to remote cache (available for team)"
              else:
                echo "⚠️  Remote upload failed (local cache still available)"
            else:
              echo "⚠️  Remote cache unavailable"
        else:
          echo "⚠️  Failed to cache artifact (build still successful)"
        echo ""

      # Install the built package
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      echo "📥 Installing Package"
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      echo ""

      let (installSuccess, installPath, installErrors) = globalBuildCoordinator.installBuildArtifact(buildResult)

      if not installSuccess:
        echo "❌ Installation Failed"
        echo ""
        echo "Errors:"
        for error in installErrors:
          echo fmt"  • {error}"
        echo ""
        return 1

      echo "✅ Installation Complete!"
      echo ""
      echo fmt"📂 Location:  {installPath}"
      echo ""
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      echo "🎉 Success!"
      echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
      echo ""
      echo fmt"Package '{packageName}' is now installed and ready to use!"
      echo ""

      return 0

    except ValueError as e:
      echo fmt"❌ Security error: {e.msg}"
      return 1

  elif selectedSource == "pkgsrc":
    echo "TODO: PKGSRC build not yet implemented"
    return 1

  elif selectedSource == "gentoo":
    echo "TODO: Gentoo build not yet implemented"
    return 1

  else:
    echo fmt"❌ Unknown source: {selectedSource}"
    return 1


proc cacheStatsCommand*(): int =
  ## Show build cache statistics
  ensureInitialized()

  let stats = getCacheStats(globalBuildCoordinator.buildCache)

  echo "📊 Build Cache Statistics"
  echo ""
  echo fmt"Cached builds: {stats.count}"
  echo fmt"Total size: {stats.totalSize} bytes ({stats.totalSize div 1024} KB)"
  let buildsDir = globalBuildCoordinator.config.cacheDir / "builds"
  echo fmt"Cache directory: {buildsDir}"
  echo ""

  return 0

proc cacheClearCommand*(): int =
  ## Clear the build cache
  ensureInitialized()

  echo "🗑️  Clearing build cache..."
  let removed = clearCache(globalBuildCoordinator.buildCache)
  echo fmt"✅ Removed {removed} cached builds"

  return 0

proc cacheCleanCommand*(): int =
  ## Clean old builds from cache
  ensureInitialized()

  echo "🧹 Cleaning old builds from cache..."
  let removed = cleanOldBuilds(globalBuildCoordinator.buildCache)
  echo fmt"✅ Removed {removed} old cached builds"

  return 0

proc listSourcesCommand*(packageName: string = ""): int =
  ## List available package sources
  ## SECURITY: Validates package name before use in commands

  # SECURITY: Validate package name if provided
  if packageName != "" and not isValidPackageName(packageName):
    echo "❌ Invalid package name: contains unsafe characters"
    echo "   Package names must contain only: a-z, A-Z, 0-9, -, _, ."
    return 1

  echo "📚 Available Package Sources (by priority):"
  echo ""

  var priority = 1

  # Check Nix
  if dirExists("/nix"):
    echo fmt"{priority}. 🔵 Nix (nixpkgs)"
    echo "   Status: ✅ Available"
    echo "   Packages: ~100,000+"
    echo "   Build: From source with overrides"
    if packageName != "":
      # SECURITY: Use execCmdEx with proper shell quoting
      let (output, exitCode) = execCmdEx("nix-env -qaA " & quoteShell(fmt"nixpkgs.{packageName}"))
      if exitCode == 0 and output.len > 0:
        echo fmt"   Package '{packageName}': ✅ Found"
      else:
        echo fmt"   Package '{packageName}': ❌ Not found"
    echo ""
    priority.inc
  else:
    echo fmt"{priority}. 🔵 Nix (nixpkgs)"
    echo "   Status: ❌ Not installed"
    echo "   Install: https://nixos.org/download.html"
    echo ""
    priority.inc

  # Check PKGSRC
  if dirExists("/usr/pkgsrc"):
    echo fmt"{priority}. 🟢 PKGSRC (NetBSD)"
    echo "   Status: ✅ Available"
    echo "   Packages: ~27,000+"
    echo "   Build: Always from source with PKG_OPTIONS"
    if packageName != "":
      # SECURITY: Use execCmdEx with proper shell quoting
      let (output, exitCode) = execCmdEx("find /usr/pkgsrc -name " & quoteShell(packageName) & " -type d")
      if exitCode == 0 and output.strip().len > 0:
        let firstLine = output.splitLines()[0]
        echo fmt"   Package '{packageName}': ✅ Found at {firstLine}"
      else:
        echo fmt"   Package '{packageName}': ❌ Not found"
    echo ""
    priority.inc
  else:
    echo fmt"{priority}. 🟢 PKGSRC (NetBSD)"
    echo "   Status: ❌ Not installed"
    echo "   Install: https://www.pkgsrc.org/"
    echo ""
    priority.inc

  # Check Gentoo
  if fileExists("/usr/bin/emerge"):
    echo fmt"{priority}. 🟣 Gentoo Portage"
    echo "   Status: ✅ Available"
    echo "   Packages: ~20,000+"
    echo "   Build: From source with USE flags"
    if packageName != "":
      # SECURITY: Use execCmdEx with proper shell quoting
      let (output, exitCode) = execCmdEx("emerge --search " & quoteShell(packageName))
      if exitCode == 0 and "Latest version available" in output:
        echo fmt"   Package '{packageName}': ✅ Found"
      else:
        echo fmt"   Package '{packageName}': ❌ Not found"
    echo ""
    priority.inc
  else:
    echo fmt"{priority}. 🟣 Gentoo Portage"
    echo "   Status: ❌ Not installed"
    echo "   Install: https://www.gentoo.org/"
    echo ""
    priority.inc

  echo "💡 Use: nip build <package> --source=<nix|pkgsrc|gentoo>"
  echo "💡 Or let NIP auto-detect: nip build <package>"

  return 0
