## nimpak/lockfile_system.nim
## Lockfile generation and reproducibility system for NimPak
##
## This module implements the lockfile system for environment reproducibility
## and CI/CD integration, providing exact package version tracking and
## system state capture.

import std/[os, strutils, times, json, tables, sequtils, algorithm, hashes]

type
  LockfileError* = object of CatchableError
    lockfilePath*: string

  LockfileManager* = object
    lockfilePath*: string      ## Path to nip.lock file
    generationsRoot*: string   ## /System/Generations - Generation metadata
    programsRoot*: string      ## /Programs - Package installation directory
    format*: LockfileFormat    ## Output format (JSON, YAML, KDL)
    includeSource*: bool       ## Include source attribution
    includeChecksums*: bool    ## Include package checksums
    includeGeneration*: bool   ## Include generation information

  LockfileFormat* = enum
    LockfileJson,    ## JSON format (default)
    LockfileYaml,    ## YAML format
    LockfileKdl      ## KDL format

  PackageLockEntry* = object
    name*: string              ## Package name
    version*: string           ## Exact version
    stream*: string            ## Package stream (stable, testing, etc.)
    source*: PackageSource     ## Source information
    checksum*: string          ## Package checksum (BLAKE3)
    dependencies*: seq[string] ## Direct dependencies
    installedPath*: string     ## Installation path
    installedSize*: int64      ## Installed size in bytes
    installTime*: times.DateTime ## Installation timestamp

  PackageSource* = object
    sourceMethod*: string      ## Source method (grafted-pacman, native, etc.)
    url*: string              ## Source URL or identifier
    hash*: string             ## Source hash
    timestamp*: times.DateTime ## Source timestamp
    attribution*: string      ## Source attribution

  SystemLockfile* = object
    version*: string           ## Lockfile format version
    generated*: times.DateTime ## Generation timestamp
    generator*: string         ## Generator tool (nip)
    systemGeneration*: string  ## System generation ID
    architecture*: string      ## Target architecture
    packages*: seq[PackageLockEntry] ## Locked packages
    metadata*: LockfileMetadata ## Additional metadata

  LockfileMetadata* = object
    description*: string       ## Lockfile description
    environment*: string       ## Environment name (production, development, etc.)
    creator*: string          ## Creator information
    tags*: seq[string]        ## Tags for categorization
    totalSize*: int64         ## Total installed size
    packageCount*: int        ## Number of packages

# =============================================================================
# LockfileManager Creation and Configuration
# =============================================================================

proc newLockfileManager*(lockfilePath: string = "nip.lock",
                         generationsRoot: string = "/System/Generations",
                         programsRoot: string = "/Programs",
                         format: LockfileFormat = LockfileJson,
                         includeSource: bool = true,
                         includeChecksums: bool = true,
                         includeGeneration: bool = true): LockfileManager =
  ## Create a new LockfileManager with specified configuration
  LockfileManager(
    lockfilePath: lockfilePath,
    generationsRoot: generationsRoot,
    programsRoot: programsRoot,
    format: format,
    includeSource: includeSource,
    includeChecksums: includeChecksums,
    includeGeneration: includeGeneration
  )

# =============================================================================
# Package Information Gathering
# =============================================================================

proc gatherPackageInfo*(lm: LockfileManager, packageName: string, version: string): PackageLockEntry =
  ## Gather comprehensive information about an installed package
  let packageDir = lm.programsRoot / packageName / version

  var entry = PackageLockEntry(
    name: packageName,
    version: version,
    stream: "stable",  # Default - would be read from package metadata
    installedPath: packageDir,
    installedSize: 0,
    installTime: now(),
    dependencies: @[],
    checksum: "",
    source: PackageSource(
      sourceMethod: "unknown",
      url: "",
      hash: "",
      timestamp: now(),
      attribution: ""
    )
  )

  # Calculate installed size
  if dirExists(packageDir):
    proc calculateDirSize(path: string): int64 =
      var totalSize: int64 = 0
      for kind, subpath in walkDir(path):
        if kind == pcFile:
          try:
            totalSize += getFileSize(subpath)
          except:
            discard
        elif kind == pcDir:
          totalSize += calculateDirSize(subpath)
      return totalSize

    entry.installedSize = calculateDirSize(packageDir)

  # Try to read package metadata if available
  let metadataFile = packageDir / "package.json"
  if fileExists(metadataFile):
    try:
      let metadata = parseJson(readFile(metadataFile))

      if metadata.hasKey("stream"):
        entry.stream = metadata["stream"].getStr()

      if metadata.hasKey("dependencies"):
        entry.dependencies = metadata["dependencies"].getElems().mapIt(it.getStr())

      if metadata.hasKey("checksum"):
        entry.checksum = metadata["checksum"].getStr()

      if metadata.hasKey("source"):
        let sourceNode = metadata["source"]
        entry.source = PackageSource(
          sourceMethod: sourceNode.getOrDefault("method").getStr("unknown"),
          url: sourceNode.getOrDefault("url").getStr(""),
          hash: sourceNode.getOrDefault("hash").getStr(""),
          timestamp: if sourceNode.hasKey("timestamp"):
                      parse(sourceNode["timestamp"].getStr(), "yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'", utc())
                    else:
                      now(),
          attribution: sourceNode.getOrDefault("attribution").getStr("")
        )

      if metadata.hasKey("install_time"):
        entry.installTime = parse(metadata["install_time"].getStr(), "yyyy-MM-dd'T'HH:mm:ss'.'fff'Z'", utc())

    except:
      # Use defaults if metadata parsing fails
      discard

  return entry

proc scanInstalledPackages*(lm: LockfileManager): seq[PackageLockEntry] =
  ## Scan all installed packages and gather their information
  var packages: seq[PackageLockEntry] = @[]

  if not dirExists(lm.programsRoot):
    return packages

  # Scan /Programs directory for installed packages
  for kind, packagePath in walkDir(lm.programsRoot):
    if kind == pcDir:
      let packageName = extractFilename(packagePath)

      # Skip system directories
      if packageName.startsWith("."):
        continue

      # Scan versions for this package
      for versionKind, versionPath in walkDir(packagePath):
        if versionKind == pcDir:
          let version = extractFilename(versionPath)

          # Skip system directories
          if version.startsWith("."):
            continue

          let packageInfo = lm.gatherPackageInfo(packageName, version)
          packages.add(packageInfo)

  # Sort packages by name and version for consistent output
  packages.sort do (a, b: PackageLockEntry) -> int:
    let nameCompare = cmp(a.name, b.name)
    if nameCompare != 0:
      nameCompare
    else:
      cmp(a.version, b.version)

  return packages

# =============================================================================
# System State Capture
# =============================================================================

proc getCurrentGeneration*(lm: LockfileManager): string =
  ## Get the current system generation ID
  try:
    let currentGenFile = lm.generationsRoot / "current"
    if fileExists(currentGenFile):
      return readFile(currentGenFile).strip()
    else:
      return ""
  except:
    return ""

proc getSystemArchitecture*(): string =
  ## Get the system architecture
  try:
    when defined(amd64) or defined(x86_64):
      return "x86_64"
    elif defined(i386) or defined(x86):
      return "i386"
    elif defined(arm64) or defined(aarch64):
      return "aarch64"
    elif defined(arm):
      return "arm"
    else:
      return "unknown"
  except:
    return "unknown"

proc createSystemLockfile*(lm: LockfileManager, description: string = "",
                          environment: string = "", creator: string = "",
                          tags: seq[string] = @[]): SystemLockfile =
  ## Create a complete system lockfile with all installed packages
  let packages = lm.scanInstalledPackages()
  let totalSize = packages.mapIt(it.installedSize).foldl(a + b, 0'i64)

  let metadata = LockfileMetadata(
    description: if description.len > 0: description else: "System lockfile generated by nip",
    environment: if environment.len > 0: environment else: "default",
    creator: if creator.len > 0: creator else: "nip",
    tags: if tags.len > 0: tags else: @["system", "lockfile"],
    totalSize: totalSize,
    packageCount: packages.len
  )

  SystemLockfile(
    version: "1.0",
    generated: now(),
    generator: "nip",
    systemGeneration: lm.getCurrentGeneration(),
    architecture: getSystemArchitecture(),
    packages: packages,
    metadata: metadata
  )

# =============================================================================
# Lockfile Serialization
# =============================================================================

proc serializeLockfileToJson*(lockfile: SystemLockfile, pretty: bool = true): string =
  ## Serialize lockfile to JSON format
  let jsonNode = %*{
    "lockfile": {
      "version": lockfile.version,
      "generated": $lockfile.generated,
      "generator": lockfile.generator,
      "system_generation": lockfile.systemGeneration,
      "architecture": lockfile.architecture
    },
    "metadata": {
      "description": lockfile.metadata.description,
      "environment": lockfile.metadata.environment,
      "creator": lockfile.metadata.creator,
      "tags": lockfile.metadata.tags,
      "total_size": lockfile.metadata.totalSize,
      "package_count": lockfile.metadata.packageCount
    },
    "packages": lockfile.packages.mapIt(%*{
      "name": it.name,
      "version": it.version,
      "stream": it.stream,
      "checksum": it.checksum,
      "installed_path": it.installedPath,
      "installed_size": it.installedSize,
      "install_time": $it.installTime,
      "dependencies": it.dependencies,
      "source": {
        "method": it.source.sourceMethod,
        "url": it.source.url,
        "hash": it.source.hash,
        "timestamp": $it.source.timestamp,
        "attribution": it.source.attribution
      }
    })
  }

  if pretty:
    return jsonNode.pretty()
  else:
    return $jsonNode

proc serializeLockfileToKdl*(lockfile: SystemLockfile): string =
  ## Serialize lockfile to KDL format
  result = "// NimPak System Lockfile\n"
  result.add("// Generated: " & $lockfile.generated & "\n\n")

  result.add("lockfile {\n")
  result.add("  version \"" & lockfile.version & "\"\n")
  result.add("  generated \"" & $lockfile.generated & "\"\n")
  result.add("  generator \"" & lockfile.generator & "\"\n")
  result.add("  system_generation \"" & lockfile.systemGeneration & "\"\n")
  result.add("  architecture \"" & lockfile.architecture & "\"\n")
  result.add("}\n\n")

  result.add("metadata {\n")
  result.add("  description \"" & lockfile.metadata.description & "\"\n")
  result.add("  environment \"" & lockfile.metadata.environment & "\"\n")
  result.add("  creator \"" & lockfile.metadata.creator & "\"\n")
  result.add("  tags")
  for tag in lockfile.metadata.tags:
    result.add(" \"" & tag & "\"")
  result.add("\n")
  result.add("  total_size " & $lockfile.metadata.totalSize & "\n")
  result.add("  package_count " & $lockfile.metadata.packageCount & "\n")
  result.add("}\n\n")

  for pkg in lockfile.packages:
    result.add("package \"" & pkg.name & "\" {\n")
    result.add("  version \"" & pkg.version & "\"\n")
    result.add("  stream \"" & pkg.stream & "\"\n")
    result.add("  checksum \"" & pkg.checksum & "\"\n")
    result.add("  installed_path \"" & pkg.installedPath & "\"\n")
    result.add("  installed_size " & $pkg.installedSize & "\n")
    result.add("  install_time \"" & $pkg.installTime & "\"\n")

    if pkg.dependencies.len > 0:
      result.add("  dependencies")
      for dep in pkg.dependencies:
        result.add(" \"" & dep & "\"")
      result.add("\n")

    result.add("  source {\n")
    result.add("    method \"" & pkg.source.sourceMethod & "\"\n")
    result.add("    url \"" & pkg.source.url & "\"\n")
    result.add("    hash \"" & pkg.source.hash & "\"\n")
    result.add("    timestamp \"" & $pkg.source.timestamp & "\"\n")
    result.add("    attribution \"" & pkg.source.attribution & "\"\n")
    result.add("  }\n")
    result.add("}\n\n")

# =============================================================================
# Lockfile Generation and Saving
# =============================================================================

proc generateLockfile*(lm: LockfileManager, description: string = "",
                      environment: string = "", creator: string = "",
                      tags: seq[string] = @[]): bool =
  ## Generate and save a lockfile for the current system state
  try:
    echo "🔒 Generating system lockfile..."

    # Create the lockfile
    let lockfile = lm.createSystemLockfile(description, environment, creator, tags)

    echo "📊 Lockfile statistics:"
    echo "   Packages: ", lockfile.metadata.packageCount
    echo "   Total size: ", lockfile.metadata.totalSize, " bytes"
    echo "   Generation: ", lockfile.systemGeneration
    echo "   Architecture: ", lockfile.architecture

    # Serialize based on format
    let content = case lm.format:
      of LockfileJson:
        serializeLockfileToJson(lockfile, pretty = true)
      of LockfileKdl:
        serializeLockfileToKdl(lockfile)
      of LockfileYaml:
        # YAML serialization would be implemented here
        # For now, fall back to JSON
        serializeLockfileToJson(lockfile, pretty = true)

    # Ensure parent directory exists
    let parentDir = parentDir(lm.lockfilePath)
    if parentDir.len > 0 and not dirExists(parentDir):
      createDir(parentDir)

    # Write the lockfile
    writeFile(lm.lockfilePath, content)

    echo "✅ Lockfile saved to: ", lm.lockfilePath
    return true

  except Exception as e:
    echo "❌ Failed to generate lockfile: ", e.msg
    return false

proc validateLockfile*(lm: LockfileManager): bool =
  ## Validate an existing lockfile against current system state
  try:
    if not fileExists(lm.lockfilePath):
      echo "❌ Lockfile not found: ", lm.lockfilePath
      return false

    echo "🔍 Validating lockfile: ", lm.lockfilePath

    # Load lockfile
    let content = readFile(lm.lockfilePath)
    let lockfileJson = parseJson(content)

    # Get current system state
    let currentPackages = lm.scanInstalledPackages()
    let currentGeneration = lm.getCurrentGeneration()

    # Validate generation
    let lockfileGeneration = lockfileJson["lockfile"]["system_generation"].getStr()
    if lockfileGeneration != currentGeneration:
      echo "⚠️  Generation mismatch:"
      echo "   Lockfile: ", lockfileGeneration
      echo "   Current: ", currentGeneration

    # Validate packages
    let lockfilePackages = lockfileJson["packages"].getElems()
    var missingPackages: seq[string] = @[]
    var extraPackages: seq[string] = @[]
    var versionMismatches: seq[string] = @[]

    # Create lookup tables
    var lockfilePackageMap = initTable[string, JsonNode]()
    for pkg in lockfilePackages:
      let key = pkg["name"].getStr() & "-" & pkg["version"].getStr()
      lockfilePackageMap[key] = pkg

    var currentPackageMap = initTable[string, PackageLockEntry]()
    for pkg in currentPackages:
      let key = pkg.name & "-" & pkg.version
      currentPackageMap[key] = pkg

    # Check for missing packages
    for key, lockfilePkg in lockfilePackageMap:
      if key notin currentPackageMap:
        missingPackages.add(lockfilePkg["name"].getStr() & "-" & lockfilePkg["version"].getStr())

    # Check for extra packages
    for key, currentPkg in currentPackageMap:
      if key notin lockfilePackageMap:
        extraPackages.add(currentPkg.name & "-" & currentPkg.version)

    # Report validation results
    if missingPackages.len == 0 and extraPackages.len == 0:
      echo "✅ Lockfile validation passed - system matches lockfile exactly"
      return true
    else:
      echo "❌ Lockfile validation failed:"

      if missingPackages.len > 0:
        echo "   Missing packages (", missingPackages.len, "):"
        for pkg in missingPackages:
          echo "     - ", pkg

      if extraPackages.len > 0:
        echo "   Extra packages (", extraPackages.len, "):"
        for pkg in extraPackages:
          echo "     + ", pkg

      return false

  except Exception as e:
    echo "❌ Failed to validate lockfile: ", e.msg
    return false

# =============================================================================
# Lockfile Comparison and Diff
# =============================================================================

proc compareLockfiles*(lockfile1Path: string, lockfile2Path: string): bool =
  ## Compare two lockfiles and show differences
  try:
    if not fileExists(lockfile1Path):
      echo "❌ Lockfile not found: ", lockfile1Path
      return false

    if not fileExists(lockfile2Path):
      echo "❌ Lockfile not found: ", lockfile2Path
      return false

    echo "🔍 Comparing lockfiles:"
    echo "   File 1: ", lockfile1Path
    echo "   File 2: ", lockfile2Path

    let content1 = readFile(lockfile1Path)
    let content2 = readFile(lockfile2Path)

    let lockfile1 = parseJson(content1)
    let lockfile2 = parseJson(content2)

    # Compare metadata
    let gen1 = lockfile1["lockfile"]["system_generation"].getStr()
    let gen2 = lockfile2["lockfile"]["system_generation"].getStr()

    if gen1 != gen2:
      echo "📊 Generation difference:"
      echo "   File 1: ", gen1
      echo "   File 2: ", gen2

    # Compare packages
    let packages1 = lockfile1["packages"].getElems()
    let packages2 = lockfile2["packages"].getElems()

    var packages1Map = initTable[string, JsonNode]()
    var packages2Map = initTable[string, JsonNode]()

    for pkg in packages1:
      let key = pkg["name"].getStr() & "-" & pkg["version"].getStr()
      packages1Map[key] = pkg

    for pkg in packages2:
      let key = pkg["name"].getStr() & "-" & pkg["version"].getStr()
      packages2Map[key] = pkg

    var onlyIn1: seq[string] = @[]
    var onlyIn2: seq[string] = @[]
    var common: seq[string] = @[]

    for key in packages1Map.keys:
      if key in packages2Map:
        common.add(key)
      else:
        onlyIn1.add(key)

    for key in packages2Map.keys:
      if key notin packages1Map:
        onlyIn2.add(key)

    echo "📊 Package comparison:"
    echo "   Common packages: ", common.len
    echo "   Only in file 1: ", onlyIn1.len
    echo "   Only in file 2: ", onlyIn2.len

    if onlyIn1.len > 0:
      echo "   Packages only in ", extractFilename(lockfile1Path), ":"
      for pkg in onlyIn1:
        echo "     - ", pkg

    if onlyIn2.len > 0:
      echo "   Packages only in ", extractFilename(lockfile2Path), ":"
      for pkg in onlyIn2:
        echo "     + ", pkg

    let identical = onlyIn1.len == 0 and onlyIn2.len == 0
    if identical:
      echo "✅ Lockfiles are identical"
    else:
      echo "❌ Lockfiles differ"

    return identical

  except Exception as e:
    echo "❌ Failed to compare lockfiles: ", e.msg
    return false

# =============================================================================
# Lockfile Restoration System
# =============================================================================

proc restoreFromLockfile*(lm: LockfileManager, lockfilePath: string,
                          dryRun: bool = false): bool =
  ## Restore system state from a lockfile
  try:
    if not fileExists(lockfilePath):
      echo "❌ Lockfile not found: ", lockfilePath
      return false

    echo "🔄 Restoring system from lockfile: ", lockfilePath

    # Load lockfile
    let content = readFile(lockfilePath)
    let lockfileJson = parseJson(content)

    # Get target state
    let targetGeneration = lockfileJson["lockfile"]["system_generation"].getStr()
    let targetArchitecture = lockfileJson["lockfile"]["architecture"].getStr()
    let targetPackages = lockfileJson["packages"].getElems()

    # Verify architecture compatibility
    let currentArch = getSystemArchitecture()
    if targetArchitecture != currentArch:
      echo "⚠️  Architecture mismatch:"
      echo "   Target: ", targetArchitecture
      echo "   Current: ", cu
      echo "   Proceeding anyway..."

  echo "📊 Restoration plan:"
    echo "   Target generation: ", targetGeneration
    echo "   Target packages: ", targetPackages.len

    # Get current system state
    let currentPackages = lm.scanInstalledPackages()

    # Create restoration plan
    var packagesToInstall: seq[string] = @[]
    var packagesToRemove: seq[string] = @[]
    var packagesToUpdate: seq[string] = @[]

    # Build lookup tables
    var targetPackageMap = initTable[string, JsonNode]()
    for pkg in targetPackages:
      let key = pkg["name"].getStr()
      targetPackageMap[key] = pkg

    var currentPackageMap = initTable[string, PackageLockEntry]()
    for pkg in currentPackages:
      currentPackageMap[pkg.name] = pkg

    # Determine required actions
    for packageName, targetPkg in targetPackageMap:
      let targetVersion = targetPkg["version"].getStr()

      if packageName in currentPackageMap:
        let currentVersion = currentPackageMap[packageName].version
        if currentVersion != targetVersion:
          packagesToUpdate.add(packageName & "-" & currentVersion & " → " & targetVersion)
      else:
        packagesToInstall.add(packageName & "-" & targetVersion)

    for packageName, currentPkg in currentPackageMap:
      if packageName notin targetPackageMap:
        packagesToRemove.add(packageName & "-" & currentPkg.version)

    # Display restoration plan
    ifagesToInstall.len > 0:
      echo "📦 Packages to install (", packagesToInstall.len, "):"
      for pkg in packagesToInstall:
        echo "   + ", pkg

    if packagesToUpdate.len > 0:
      echo "🔄 Packages to update (", packagesToUpdate.len, "):"
      for pkg in packagesToUpdate:
        echo "   ↗ ", pkg

    if packagesToRemove.len > 0:
      echo "🗑️  Packages to remove (", packagesToRemove.len, "):"
      for pkg in packagesToRemove:
        echo "   - ", pkg

    if packagesToInstall.len == 0 and packagesToUpdate.len == 0 and packagesToRemove.len == 0:
      echo "✅ System already matches lockfile - no changes needed"
      return true

    if dryRun:
      echo "🔍 DRY RUN: Would perform the above changes"
      return true

    # TODO: Implement actual package installation/removal/update
    # This would integrate with the package management system
    echo "⚠️  Actual package operations not yet implemented"
    echo "   This would require integration with the package installation system"

    return true

  except Exception as e:
    echo "❌ Failed to restore from lockfile: ", e.msg
    return false

proc showLockfileDrift*(lm: LockfileManager, lockfilePath: string): bool =
  ## Show drift between current system state and lockfile
  try:
    if not fileExists(lockfilePath):
      echo "❌ Lockfile not found: ", lockfilePath
      return false

    echo "🔍 Analyzing system drift from lockfile: ", lockfilePath

    # Load lockfile
    let content = readFile(lockfilePath)
    let lockfileJson = parseJson(content)

    # Get lockfile metadata
    let lockfileGenerated = lockfileJson["lockfile"]["generated"].getStr()
    let lockfileGeneration = lockfileJson["lockfile"]["system_generation"].getStr()
    let lockfilePackages = lockfileJson["packages"].getElems()

    # Get current system state
    let currentPackages = lm.scanInstalledPackages()
    let currentGeneration = lm.getCurrentGeneration()

    echo "📊 Drift Analysis:"
    echo "   Lockfile generated: ", lockfileGenerated
    echo "   Lockfile generation: ", lockfileGeneration
    echo "   Current generation: ", currentGeneration

    # Analyze generation drift
    if lockfileGeneration != currentGeneration:
      echo "⚠️  Generation drift detected:"
      echo "     Expected: ", lockfileGeneration
      echo "     Current: ", currentGeneration
    else:
      echo "✅ Generation matches lockfile"

    # Analyze package drift
    var lockfilePackageMap = initTable[string, JsonNode]()
    for pkg in lockfilePackages:
      let key = pkg["name"].getStr() & "-" & pkg["version"].getStr()
      lockfilePackageMap[key] = pkg

    var currentPackageMap = initTable[string, PackageLockEntry]()
    for pkgentPackages:
      let key = pkg.name & "-" & pkg.version
      currentPackageMap[key] = pkg

    var driftDetected = false
    var missingPackages: seq[string] = @[]
    var extraPackages: seq[string] = @[]
    var modifiedPackages: seq[string] = @[]

    # Check for missing packages
    for key, lockfilePkg in lockfilePackageMap:
      if key notin currentPackageMap:
        missingPackages.add(key)
        driftDetected = true

    # Check for extra packages
    for key, currentPkg in currentPackageMap:
      if key notin lockfilePackageMap:
        extraPackages.add(key)
        driftDetected = true

    # Check for modified packages (same name-version but different checksums)
    for key, lockfilePkg in lockfilePackageMap:
      if key in currentPackageMap:
        let lockfileChecksum = lockfilePkg["checksum"].getStr()
        let currentChecksum = currentPackageMap[key].checksum

        if lockfileChecksum.len > 0 and currentChecksum.len > 0 and lockfileChecksum != currentChecksum:
          modifiedPackages.add(key & " (checksum mismatch)")
          driftDetected = true

    # Report drift results
    if not driftDetected:
      echo "✅ No package drift detected - system matches lockfile exactly"
    else:
      echo "⚠️  Package drift detected:"

      if missingPackages.len > 0:
        echo "   Missing packages (", missingPackages.len, "):"
        for pkg in missingPackages:
          echo "     - ", pkg

      if extraPackages.len > 0:
        echo "   Extra packages (", extraPackages.len, "):"
        for pkg in extraPackages:
          echo "     + ", pkg

      if modifiedPackages.len > 0:
        echo "   Modified packages (", modifiedPackages.len, "):"
        for pkg in modifiedPackages:
          echo "     ~ ", pkg

    return not driftDetected

  except Exception as e:
    echo "❌ Failed to analyze drift: ", e.msg
    return false

proc mergeLockfiles*(lockfile1Path: string, lockfile2Path: string,
                    outputPath: string, strategy: string = "union"): bool =
  ## Merge two lockfiles using specified strategy
  try:
    if not fileExists(lockfile1Path):
      echo "❌ Lockfile 1 not found: ", lockfile1Path
      return false

    if not fileExists(lockfile2Path):
      echo "❌ Lockfile 2 not found: ", lockfile2Path
      return false

    echo "🔄 Merging lockfiles:"
    echo "   Base: ", lockfile1Path
    echo "   Merge: ", lockfile2Path
    echo "   Output: ", outputPath
    echo "   Strategy: ", strategy

    # Load both lockfiles
    let content1 = readFile(lockfile1Path)
    let content2 = readFile(lockfile2Path)
    let lockfile1 = parseJson(content1)
    let lockfile2 = parseJson(content2)

    # Create merged lockfile structure
    var mergedLockfile = lockfile1.copy()

    # Update metadata
    mergedLockfile["lockfile"]["generated"] = %($now())
    mergedLockfile["lockfile"]["generator"] = %"nip-
p
    # Get package lists
    let packages1 = lockfile1["packages"].getElems()
    let packages2 = lockfile2["packages"].getElems()

    # Build package maps
    var packages1Map = initTable[string, JsonNode]()
    var packages2Map = initTable[string, JsonNode]()

    for pkg in packages1:
      let key = pkg["name"].getStr()
      packages1Map[key] = pkg

    for pkg in packages2:
      let key = pkg["name"].getStr()
      packages2Map[key] = pkg

    # Merge packages based on strategy
    var mergedPackages: seq[JsonNode] = @[]

    case strategy:
    of "union":
      # Include all packages from both lockfiles, prefer lockfile2 for conflicts
      var allPackageNames: seq[string] = @[]
      for name in packages1Map.keys:
        allPackageNames.add(name)
      for name in packages2Map.keys:
        if name notin allPackageNames:
          allPackageNames.add(name)

      for name in allPackageNames:
        if name in packages2Map:
          mergedPackages.add(packages2Map[name])
        else:
          mergedPackages.add(packages1Map[name])

    of "intersection":
      # Include only packages present in both lockfiles, prefer lockfile2 versions
      for name in packages1Map.keys:
        if name in packages2Map:
          mergedPackages.add(packages2Map[name])

    of "base-only":
      # Include only packages from lockfile1
      mergedPackages = packages1

    of "merge-only":
      # Include only packages from lockfile2
      mergedPackages = packages2

    else:
      echo "❌ Unknown merge strategy: ", strategy
      return false

    # Update merged lockfile
    mergedLockfile["packages"] = %mergedPackages
    mergedLockfile["metadata"]["package_count"] = %mergedPackages.len
    mergedLockfile["metadata"]["description"] = %("Merged lockfile using " & strategy & " strategy")

    # Calculate total size
    var totalSize: int64 = 0
    for pkg in mergedPackages:
      totalSize += pkg["installed_size"].getInt()
    mergedLockfile["metadata"]["total_size"] = %totalSize

    # Write merged lockfile
    let parentDir = parentDir(outputPath)
    if parentDir.len > 0 and not dirExists(parentDir):
      createDir(parentDir)

    writeFile(outputPath, mergedLockfile.pretty())

    echo "✅ Lockfiles merged successfully"
    echo "   Merged packages: ", mergedPackages.len
    echo "   Total size: ", totalSize, " bytes"

    return true

  except Exception as e:
    echo "❌ Failed to merge lockfiles: ", e.msg
    return false

proc updateLockfile*(lm: LockfileManager, lockfilePath: string,
                    packageUpdates: seq[string] = @[]): bool =
  ## Update an existing lockfile with current system state or specific package changes
  try:
    if not fileExists(lockfilePath):
      echo "❌ Lockfile not found: ", lockfilePath
      return false

    echo "🔄 Updating lockfile: ", lockfilePath

    # Load existing lockfile
    let content = readFile(lockfilePath)
    let existingLockfile = parseJson(content)

    # Get current system state
    let currentPackages = lm.scanInstalledPackages()
    let currentGeneration = lm.getCurrentGeneration()

    # Create updated lockfile
    var updatedLockfile = existingLroc printLoc()

    # Update metadata
    updatedLockfile["lockfile"]["generated"] = %($now())
    updatedLockfile["lockfile"]["system_generation"] = %currentGeneration

    # Update packages
    if packageUpdates.len == 0:
      # Full update - replace all packages with current state
      let newPackages = currentPackages.mapIt(%*{
        "name": it.name,
        "version": it.version,
        "stream": it.stream,
        "checksum": it.checksum,
        "installed_path": it.installedPath,
        "installed_size": it.installedSize,
        "install_time": $it.installTime,
        "dependencies": it.dependencies,
        "source": {
          "method": it.source.sourceMethod,
          "url": it.source.url,
          "hash": it.source.hash,
          "timestamp": $it.source.timestamp,
          "attribution": it.source.attribution
        }
      })

      updatedLockfile["packages"] = %newPackages
      updatedLockfile["metadata"]["package_count"] = %newPackages.len

      let totalSize = currentPackages.mapIt(it.installedSize).foldl(a + b, 0'i64)
      updatedLockfile["metadata"]["total_size"] = %totalSize

      echo "✅ Full lockfile update completed"
      echo "   Updated packages: ", newPackages.len
      echo "   Total size: ", totalSize, " bytes"
    else:
      # Selective update - update only specified packages
      var existingPackages = existingLockfile["packages"].getElems()
      var updatedCount = 0

      # Build current package lookup
      var currentPackageMap = initTable[string, PackageLockEntry]()
      for pkg in currentPackages:
        currentPackageMap[pkg.name] = pkg

      # Update specified packages
      for i, pkg in existingPackages.mpairs:
        let packageName = pkg["name"].getStr()

        if packageName in packageUpdates and packageName in currentPackageMap:
          let currentPkg = currentPackageMap[packageName]

          # Update package information
          pkg["version"] = %currentPkg.version
          pkg["stream"] = %currentPkg.stream
          pkg["checksum"] = %currentPkg.checksum
          pkg["installed_size"] = %currentPkg.installedSize
          pkg["install_time"] = %($currentPkg.installTime)
          pkg["dependencies"] = %currentPkg.dependencies

          # Update source information
          pkg["source"]["method"] = %currentPkg.source.sourceMethod
          pkg["source"]["url"] = %currentPkg.source.url
          pkg["source"]["hash"] = %currentPkg.source.hash
          pkg["source"]["timestamp"] = %($currentPkg.source.timestamp)
          pkg["source"]["attribution"] = %currentPkg.source.attribution

          updatedCount += 1

      updatedLockfile["packages"] = %existingPackages

      echo "✅ Selective lockfile update completed"
      echo "   Updated packages: ", updatedCount, "/", packageUpdates.len

    # Write updated lockfile
    writeFile(lockfilePath, updatedLockfile.pretty())

    return true

  except Exception as e:
    echo "❌ Failed to update lockfilkfi", e.msg
    return false

# =============================================================================
# Advanced Diff Functionality
# =============================================================================

proc detailedLockfileDiff*(lockfile1Path: string, lockfile2Path: string): bool =
  ## Show detailed differences between two lockfiles
  try:
    if not fileExists(lockfile1Path):
      echo "❌ Lockfile 1 not found: ", lockfile1Path
      return false

    if not fileExists(lockfile2Path):
      echo "❌ Lockfile 2 not found: ", lockfile2Path
      return false

    echo "🔍 Detailed lockfile comparison:"
    echo "   File 1: ", lockfile1Path
    echo "   File 2: ", lockfile2Path

    let content1 = readFile(lockfile1Path)
    let content2 = readFile(lockfile2Path)
    let lockfile1 = parseJson(content1)
    let lockfile2 = parseJson(content2)

    # Compare metadata
    echo "\n📊 Metadata Comparison:"

    let gen1 = lockfile1["lockfile"]["system_generation"].getStr()
    let gen2 = lockfile2["lockfile"]["system_generation"].getStr()
    let arch1 = lockfile1["lockfile"]["architecture"].getStr()
    let arch2 = lockfile2["lockfile"]["architecture"].getStr()
    let generated1 = lockfile1["lockfile"]["generated"].getStr()
    let generated2 = lockfile2["lockfile"]["generated"].getStr()

    if gen1 != gen2:
      echo "   Generation: ", gen1, " → ", gen2
    else:
      echo "   Generation: ", gen1, " (same)"

    if arch1 != arch2:
      echo "   Architecture: ", arch1, " → ", arch2
    else:
      echo "   Architecture: ", arch1, " (same)"

    echo "   Generated: ", generated1, " → ", generated2

    # Compare packages in detail
    echo "\n📦 Package Comparison:"

    let packages1 = lockfile1["packages"].getElems()
    let packages2 = lockfile2["packages"].getElems()

    var packages1Map = initTable[string, JsonNode]()
    var packages2Map = initTable[string, JsonNode]()

    for pkg in packages1:
      let key = pkg["name"].getStr()
      packages1Map[key] = pkg

    for pkg in packages2:
      let key = pkg["name"].getStr()
      packages2Map[key] = pkg

    # Analyze changes
    var added: seq[string] = @[]
    var removed: seq[string] = @[]
    var modified: seq[string] = @[]
    var unchanged: seq[string] = @[]

    # Find added packages
    for name in packages2Map.keys:
      if nale notin packages1Map:
        let pkg = packages2Map[name]
        added.add(name & "-" & pkg["version"].getStr())

    # Find removed packages
    for name in packages1Map.keys:
      if name notin packages2Map:
        let pkg = packages1Map[name]
        removed.add(name & "-" & pkg["version"].getStr())

    # Find modified and unchanged packages
    for name in packages1Map.keys:
      if name in packages2Map:
        let pkg1 = packages1Map[name]
        let pkg2 = packages2Map[name]

        let version1 = pkg1["version"].getStr()
        let version2 = pkg2["version"].getStr()
        let checksum1 = pkg1["checksum"].getStr()
        let checksum2 = pkg2["checksum"].getStr()
        let stream1 = pkg1["stream"].getStr()
        let stream2 = pkg2["stream"].getStr()

        if version1 != version2 or checksum1 != checksum2 or stream1 != stream2:
          var changes: seq[string] = @[]
          if version1 != version2:
            changes.add("version: " & version1 & " → " & version2)
          if stream1 != stream2:
            changes.add("stream: " & stream1 & " → " & stream2)
          if checksum1 != checksum2:
            changes.add("checksum: " & checksum1[0..7] & "... → " & checksum2[0..7] & "...")

          modified.add(name & " (" & changes.join(", ") & ")")
        else:
          unchanged.add(name & "-" & version1)

    # Display results
    echo "   Summary:"
    echo "     Added: ", added.len
    echo "     Removed: ", removed.len
    echo "     Modified: ", modified.len
    echo "     Unchanged: ", unchanged.len

    if added.len > 0:
      echo "\n   ➕ Added packages:"
      for pkg in added:
        echo "     + ", pkg

    if removed.len > 0:
      echo "\n   ➖ Removed packages:"
      for pkg in removed:
        echo "     - ", pkg

    if modified.len > 0:
      echo "\n   🔄 Modified packages:"
      for pkg in modified:
        echo "     ~ ", pkg

    let identical = added.len == 0 and removed.len == 0 and modified.len == 0
    if identical:
      echo "\n✅ Lockfiles are functionally identical"
    else:
      echo "\n❌ Lockfiles differ significantly"

    return identical

  except Exception as e:
    echo "❌ Failed to compare lockfiles: ", e.msg
    return false

# =============================================================================
# CLI Integration Functions
# =============================================================================Info*(lockfilePath: string) =
  ## Print information about a lockfile
  try:
    if not fileExists(lockfilePath):
      echo "❌ Lockfile not found: ", lockfilePath
      return

    let content = readFile(lockfilePath)
    let lockfile = parseJson(content)

    echo "=== Lockfile Information ==="
    echo "File: ", lockfilePath
    echo "Version: ", lockfile["lockfile"]["version"].getStr()
    echo "Generated: ", lockfile["lockfile"]["generated"].getStr()
    echo "Generator: ", lockfile["lockfile"]["generator"].getStr()
    echo "System Generation: ", lockfile["lockfile"]["system_generation"].getStr()
    echo "Architecture: ", lockfile["lockfile"]["architecture"].getStr()

    echo "\n=== Metadata ==="
    let metadata = lockfile["metadata"]
    echo "Description: ", metadata["description"].getStr()
    echo "Environment: ", metadata["environment"].getStr()
    echo "Creator: ", metadata["creator"].getStr()
    echo "Tags: ", metadata["tags"].getElems().mapIt(it.getStr()).join(", ")
    echo "Total Size: ", metadata["total_size"].getInt(), " bytes"
    echo "Package Count: ", metadata["package_count"].getInt()

    echo "\n=== Packages ==="
    let packages = lockfile["packages"].getElems()
    for pkg in packages:
      let name = pkg["name"].getStr()
      let version = pkg["version"].getStr()
      let stream = pkg["stream"].getStr()
      let size = pkg["installed_size"].getInt()
      echo "  ", name, "-", version, " (", stream, ") - ", size, " bytes"

  except Exception as e:
    echo "❌ Failed to read lockfile: ", e.msg

proc generateLockfileCommand*(lockfilePath: string = "nip.lock",
                             format: string = "json",
                             description: string = "",
                             environment: string = "",
                             creator: string = "",
                             tags: seq[string] = @[]): bool =
  ## CLI command to generate a lockfile
  let lockfileFormat = case format.toLowerAscii():
    of "json": LockfileJson
    of "kdl": LockfileKdl
    of "yaml": LockfileYaml
    else: LockfileJson

  let lm = newLockfileManager(
    lockfilePath = lockfilePath,
    format = lockfileFormat
  )

  return lm.generateLockfile(description, environment, creator, tags)proc r
estoreLockfileCommand*(lockfilePath: string = "nip.lock",
                            dryRun: bool = false): bool =
  ## CLI command to restore from a lockfile
  let lm = newLockfileManager()
  return lm.restoreFromLockfile(lockfilePath, dryRun)

proc validateLockfileCommand*(lockfilePath: string = "nip.lock"): bool =
  ## CLI command to validate a lockfile
  let lm = newLockfileManager(lockfilePath = lockfilePath)
  return lm.validateLockfile()

proc diffLockfileCommand*(lockfile1Path: string, lockfile2Path: string,
                         detailed: bool = false): bool =
  ## CLI command to compare two lockfiles
  if detailed:
    return detailedLockfileDiff(lockfile1Path, lockfile2Path)
  else:
    return compareLockfiles(lockfile1Path, lockfile2Path)

proc driftLockfileCommand*(lockfilePath: string = "nip.lock"): bool =
  ## CLI command to show system drift from lockfile
  let lm = newLockfileManager()
  return lm.showLockfileDrift(lockfilePath)

proc mergeLockfileCommand*(lockfile1Path: string, lockfile2Path: string,
                          outputPath: string, strategy: string = "union"): bool =
  ## CLI command to merge two lockfiles
  return mergeLockfiles(lockfile1Path, lockfile2Path, outputPath, strategy)

proc updateLockfileCommand*(lockfilePath: string = "nip.lock",
                           packages: seq[string] = @[]): bool =
  ## CLI command to update an existing lockfile
  let lm = newLockfileManager()
  return lm.updateLockfile(lockfilePath, packages)