## update_manager.nim
## Manages updates for recipes, tools, and NIP itself

import std/[os, osproc, strutils, times, json, options]
import update_checker

type
  UpdateResult* = object
    success*: bool
    component*: string
    oldVersion*: string
    newVersion*: string
    errors*: seq[string]
    backupPath*: string

  UpdateManager* = ref object
    backupDir*: string
    verbose*: bool

proc newUpdateManager*(verbose: bool = false): UpdateManager =
  ## Create a new update manager
  result = UpdateManager()
  result.verbose = verbose

  let xdgCache = getEnv("XDG_CACHE_HOME", getHomeDir() / ".cache")
  result.backupDir = xdgCache / "nip" / "backups"
  createDir(result.backupDir)

proc createBackup*(um: UpdateManager, component: string, sourcePath: string): string =
  ## Create backup before update
  let timestamp = now().format("yyyyMMdd-HHmmss")
  let backupName = component & "-" & timestamp

  try:
    if dirExists(sourcePath):
      let backupPath = um.backupDir / backupName
      copyDir(sourcePath, backupPath)
      if um.verbose:
        echo "✅ Backup created: ", backupPath
      return backupPath
    elif fileExists(sourcePath):
      # For files, add extension
      let backupPath = um.backupDir / (backupName & sourcePath.splitFile().ext)
      copyFile(sourcePath, backupPath)
      if um.verbose:
        echo "✅ Backup created: ", backupPath
      return backupPath
    else:
      return ""

  except:
    echo "⚠️  Failed to create backup: ", getCurrentExceptionMsg()
    return ""

proc restoreBackup*(um: UpdateManager, backupPath: string, targetPath: string): bool =
  ## Restore from backup
  try:
    if dirExists(backupPath):
      removeDir(targetPath)
      copyDir(backupPath, targetPath)
    elif fileExists(backupPath):
      removeFile(targetPath)
      copyFile(backupPath, targetPath)
    else:
      return false

    echo "✅ Restored from backup: ", backupPath
    return true
  except:
    echo "❌ Failed to restore backup: ", getCurrentExceptionMsg()
    return false

proc updateRecipes*(um: UpdateManager): UpdateResult =
  ## Update recipe repository
  result = UpdateResult()
  result.component = "recipes"

  let recipesDir = getEnv("XDG_DATA_HOME", getHomeDir() / ".local/share") / "nip" / "recipes"

  if not dirExists(recipesDir / ".git"):
    result.success = false
    result.errors.add("Recipe repository not found")
    return

  try:
    # Get current version
    let currentCommit = execProcess("git -C " & recipesDir & " rev-parse HEAD").strip()
    result.oldVersion = currentCommit[0..7]

    # Create backup
    result.backupPath = um.createBackup("recipes", recipesDir)

    # Pull updates
    echo "📥 Fetching recipe updates..."
    let fetchOutput = execProcess("git -C " & recipesDir & " fetch origin main 2>&1")

    if um.verbose:
      echo fetchOutput

    echo "🔄 Updating recipes..."
    let pullOutput = execProcess("git -C " & recipesDir & " pull origin main 2>&1")

    if um.verbose:
      echo pullOutput

    # Get new version
    let newCommit = execProcess("git -C " & recipesDir & " rev-parse HEAD").strip()
    result.newVersion = newCommit[0..7]

    if currentCommit == newCommit:
      echo "✅ Recipes already up to date"
      result.success = true
      return

    # Show changelog
    echo ""
    echo "📝 Changes:"
    let changelog = execProcess("git -C " & recipesDir & " log --oneline " & currentCommit & ".." & newCommit)
    echo changelog

    result.success = true
    echo "✅ Recipes updated successfully"

  except:
    result.success = false
    result.errors.add(getCurrentExceptionMsg())
    echo "❌ Failed to update recipes: ", getCurrentExceptionMsg()

    # Restore backup on failure
    if result.backupPath.len > 0:
      echo "🔄 Restoring from backup..."
      discard um.restoreBackup(result.backupPath, recipesDir)

proc updateTool*(um: UpdateManager, toolName: string): UpdateResult =
  ## Update a specific tool (Nix, Gentoo, PKGSRC)
  result = UpdateResult()
  result.component = toolName

  let toolsDir = getEnv("XDG_DATA_HOME", getHomeDir() / ".local/share") / "nip" / "build-tools"
  let toolDir = toolsDir / toolName

  if not dirExists(toolDir):
    result.success = false
    result.errors.add("Tool not installed: " & toolName)
    return

  # For now, tools are updated via recipes
  # This could be extended to support tool-specific update mechanisms
  echo "ℹ️  Tool updates are managed via recipes"
  echo "   Run: nip update recipes"

  result.success = true

proc updateNip*(um: UpdateManager, downloadUrl: string = ""): UpdateResult =
  ## Update NIP itself
  result = UpdateResult()
  result.component = "nip"
  result.oldVersion = "0.1.0"  # TODO: Get from build info

  if downloadUrl.len == 0:
    result.success = false
    result.errors.add("No download URL provided")
    return

  try:
    # Get current binary path
    let currentBinary = getAppFilename()
    result.backupPath = um.createBackup("nip", currentBinary)

    # Download new version
    echo "📥 Downloading NIP update..."
    let tempPath = getTempDir() / "nip-update"

    let downloadCmd = "curl -L -o " & tempPath & " " & downloadUrl
    let downloadOutput = execProcess(downloadCmd)

    if um.verbose:
      echo downloadOutput

    # Make executable
    discard execProcess("chmod +x " & tempPath)

    # Replace current binary
    echo "🔄 Installing update..."
    copyFile(tempPath, currentBinary)
    removeFile(tempPath)

    result.newVersion = "0.2.0"  # TODO: Get from downloaded binary
    result.success = true

    echo "✅ NIP updated successfully"
    echo "   Please restart NIP to use the new version"

  except:
    result.success = false
    result.errors.add(getCurrentExceptionMsg())
    echo "❌ Failed to update NIP: ", getCurrentExceptionMsg()

    # Restore backup on failure
    if result.backupPath.len > 0:
      echo "🔄 Restoring from backup..."
      discard um.restoreBackup(result.backupPath, getAppFilename())

proc updateAll*(um: UpdateManager): seq[UpdateResult] =
  ## Update all components
  result = @[]

  echo "🔄 Updating all components..."
  echo ""

  # Update recipes
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  echo "📦 Updating Recipes"
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  result.add(um.updateRecipes())
  echo ""

  # Check for NIP updates
  let checker = newUpdateChecker()
  let nipUpdate = checker.checkNipUpdates()

  if nipUpdate.isSome:
    let info = nipUpdate.get()
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo "📦 Updating NIP"
    echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    result.add(um.updateNip(info.downloadUrl))
    echo ""

proc listBackups*(um: UpdateManager): seq[tuple[name: string, path: string, date: Time]] =
  ## List available backups
  result = @[]

  if not dirExists(um.backupDir):
    return

  for kind, path in walkDir(um.backupDir):
    if kind == pcDir or kind == pcFile:
      let name = path.extractFilename()
      let info = getFileInfo(path)
      result.add((name, path, info.lastWriteTime))

proc cleanOldBackups*(um: UpdateManager, keepDays: int = 30): int =
  ## Clean backups older than specified days
  result = 0
  let now = getTime()

  for backup in um.listBackups():
    let age = now - backup.date
    if age.inDays > keepDays:
      try:
        if dirExists(backup.path):
          removeDir(backup.path)
        else:
          removeFile(backup.path)
        result.inc
        if um.verbose:
          echo "🗑️  Removed old backup: ", backup.name
      except:
        echo "⚠️  Failed to remove backup: ", backup.name

proc rollback*(um: UpdateManager, component: string): bool =
  ## Rollback to most recent backup
  let backups = um.listBackups()

  # Find most recent backup for component
  var latestBackup: tuple[name: string, path: string, date: Time]
  var found = false

  for backup in backups:
    if backup.name.startsWith(component & "-"):
      if not found or backup.date > latestBackup.date:
        latestBackup = backup
        found = true

  if not found:
    echo "❌ No backup found for: ", component
    return false

  # Determine target path
  var targetPath: string
  case component
  of "recipes":
    targetPath = getEnv("XDG_DATA_HOME", getHomeDir() / ".local/share") / "nip" / "recipes"
  of "nip":
    targetPath = getAppFilename()
  else:
    targetPath = getEnv("XDG_DATA_HOME", getHomeDir() / ".local/share") / "nip" / "build-tools" / component

  echo "🔄 Rolling back ", component, " to backup from ", latestBackup.date.format("yyyy-MM-dd HH:mm:ss")

  return um.restoreBackup(latestBackup.path, targetPath)
