## update_checker.nim
## Automatic update checking for NIP, recipes, and tools

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

type
  UpdateChannel* = enum
    Stable = "stable"
    Beta = "beta"
    Nightly = "nightly"

  UpdateFrequency* = enum
    Never = "never"
    Daily = "daily"
    Weekly = "weekly"
    Monthly = "monthly"

  UpdateConfig* = object
    enabled*: bool
    channel*: UpdateChannel
    frequency*: UpdateFrequency
    lastCheck*: Time
    notifyRecipes*: bool
    notifyTools*: bool
    notifyNip*: bool

  UpdateInfo* = object
    component*: string        # "recipes", "nix", "gentoo", "nip"
    currentVersion*: string
    latestVersion*: string
    updateAvailable*: bool
    changelog*: string
    downloadUrl*: string

  UpdateChecker* = ref object
    config*: UpdateConfig
    configPath*: string
    cacheDir*: string

const
  DefaultUpdateUrl = "https://updates.nip.example.com/v1"
  DefaultConfigPath = ".config/nip/update-config.json"

proc getConfigPath*(): string =
  ## Get update config path
  let xdgConfig = getEnv("XDG_CONFIG_HOME", getHomeDir() / ".config")
  result = xdgConfig / "nip" / "update-config.json"

proc loadConfig*(path: string = ""): UpdateConfig =
  ## Load update configuration
  result = UpdateConfig()
  result.enabled = true
  result.channel = Stable
  result.frequency = Weekly
  result.lastCheck = fromUnix(0)
  result.notifyRecipes = true
  result.notifyTools = true
  result.notifyNip = true

  var configPath = path
  if configPath.len == 0:
    configPath = getConfigPath()

  if not fileExists(configPath):
    return

  try:
    let data = readFile(configPath)
    let config = parseJson(data)

    if config.hasKey("enabled"):
      result.enabled = config["enabled"].getBool()

    if config.hasKey("channel"):
      let channelStr = config["channel"].getStr()
      case channelStr
      of "stable": result.channel = Stable
      of "beta": result.channel = Beta
      of "nightly": result.channel = Nightly
      else: discard

    if config.hasKey("frequency"):
      let freqStr = config["frequency"].getStr()
      case freqStr
      of "never": result.frequency = Never
      of "daily": result.frequency = Daily
      of "weekly": result.frequency = Weekly
      of "monthly": result.frequency = Monthly
      else: discard

    if config.hasKey("lastCheck"):
      result.lastCheck = fromUnix(config["lastCheck"].getInt())

    if config.hasKey("notifyRecipes"):
      result.notifyRecipes = config["notifyRecipes"].getBool()

    if config.hasKey("notifyTools"):
      result.notifyTools = config["notifyTools"].getBool()

    if config.hasKey("notifyNip"):
      result.notifyNip = config["notifyNip"].getBool()

  except:
    echo "Warning: Failed to load update config: ", getCurrentExceptionMsg()

proc saveConfig*(config: UpdateConfig, path: string = "") =
  ## Save update configuration
  var configPath = path
  if configPath.len == 0:
    configPath = getConfigPath()

  # Create config directory
  createDir(configPath.parentDir())

  var configJson = newJObject()
  configJson["enabled"] = %config.enabled
  configJson["channel"] = %($config.channel)
  configJson["frequency"] = %($config.frequency)
  configJson["lastCheck"] = %config.lastCheck.toUnix()
  configJson["notifyRecipes"] = %config.notifyRecipes
  configJson["notifyTools"] = %config.notifyTools
  configJson["notifyNip"] = %config.notifyNip

  writeFile(configPath, $configJson)

proc newUpdateChecker*(config: UpdateConfig = loadConfig()): UpdateChecker =
  ## Create a new update checker
  result = UpdateChecker()
  result.config = config
  result.configPath = getConfigPath()

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

proc shouldCheck*(uc: UpdateChecker): bool =
  ## Check if we should check for updates based on frequency
  if not uc.config.enabled:
    return false

  if uc.config.frequency == Never:
    return false

  let now = getTime()
  let timeSinceLastCheck = now - uc.config.lastCheck

  case uc.config.frequency
  of Never:
    return false
  of Daily:
    return timeSinceLastCheck.inDays >= 1
  of Weekly:
    return timeSinceLastCheck.inDays >= 7
  of Monthly:
    return timeSinceLastCheck.inDays >= 30

proc checkRecipeUpdates*(uc: UpdateChecker): Option[UpdateInfo] =
  ## Check for recipe repository updates
  if not uc.config.notifyRecipes:
    return none(UpdateInfo)

  try:
    # Check Git repository for updates
    let recipesDir = getEnv("XDG_DATA_HOME", getHomeDir() / ".local/share") / "nip" / "recipes"

    if not dirExists(recipesDir / ".git"):
      return none(UpdateInfo)

    # Get current commit
    let currentCommit = execProcess("git -C " & recipesDir & " rev-parse HEAD").strip()

    # Fetch latest
    discard execProcess("git -C " & recipesDir & " fetch origin main 2>&1")

    # Get latest commit
    let latestCommit = execProcess("git -C " & recipesDir & " rev-parse origin/main").strip()

    if currentCommit != latestCommit:
      # Get changelog
      let changelog = execProcess("git -C " & recipesDir & " log --oneline " & currentCommit & ".." & latestCommit).strip()

      var info = UpdateInfo()
      info.component = "recipes"
      info.currentVersion = currentCommit[0..7]
      info.latestVersion = latestCommit[0..7]
      info.updateAvailable = true
      info.changelog = changelog
      return some(info)

  except:
    discard

  return none(UpdateInfo)

proc checkToolUpdates*(uc: UpdateChecker, toolName: string): Option[UpdateInfo] =
  ## Check for tool updates (Nix, Gentoo, PKGSRC)
  if not uc.config.notifyTools:
    return none(UpdateInfo)

  # For now, tools are updated via recipes
  # This could be extended to check tool-specific update mechanisms
  return none(UpdateInfo)

proc checkNipUpdates*(uc: UpdateChecker): Option[UpdateInfo] =
  ## Check for NIP updates
  if not uc.config.notifyNip:
    return none(UpdateInfo)

  try:
    let client = newHttpClient()
    let url = DefaultUpdateUrl & "/nip/latest?channel=" & $uc.config.channel

    let response = client.get(url)

    if response.code == Http200:
      let data = parseJson(response.body)

      if data.hasKey("version"):
        let latestVersion = data["version"].getStr()
        let currentVersion = "0.1.0"  # TODO: Get from build info

        if latestVersion != currentVersion:
          var info = UpdateInfo()
          info.component = "nip"
          info.currentVersion = currentVersion
          info.latestVersion = latestVersion
          info.updateAvailable = true

          if data.hasKey("changelog"):
            info.changelog = data["changelog"].getStr()

          if data.hasKey("downloadUrl"):
            info.downloadUrl = data["downloadUrl"].getStr()

          return some(info)

  except:
    discard

  return none(UpdateInfo)

proc checkAllUpdates*(uc: UpdateChecker): seq[UpdateInfo] =
  ## Check for all available updates
  result = @[]

  # Check recipes
  let recipeUpdate = uc.checkRecipeUpdates()
  if recipeUpdate.isSome:
    result.add(recipeUpdate.get())

  # Check NIP
  let nipUpdate = uc.checkNipUpdates()
  if nipUpdate.isSome:
    result.add(nipUpdate.get())

  # Update last check time
  uc.config.lastCheck = getTime()
  saveConfig(uc.config)

proc formatUpdateNotification*(info: UpdateInfo): string =
  ## Format update notification for display
  result = "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
  result.add("📦 Update Available: " & info.component & "\n")
  result.add("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n")
  result.add("Current Version: " & info.currentVersion & "\n")
  result.add("Latest Version:  " & info.latestVersion & "\n")

  if info.changelog.len > 0:
    result.add("\nChangelog:\n")
    result.add(info.changelog & "\n")

  result.add("\nTo update, run:\n")
  case info.component
  of "recipes":
    result.add("  nip update recipes\n")
  of "nip":
    result.add("  nip update self\n")
  else:
    result.add("  nip update " & info.component & "\n")

proc showUpdateNotifications*(updates: seq[UpdateInfo], quiet: bool = false) =
  ## Show update notifications to user
  if updates.len == 0:
    if not quiet:
      echo "✅ All components are up to date"
    return

  echo ""
  for update in updates:
    echo formatUpdateNotification(update)
    echo ""

  if updates.len > 1:
    echo "To update all components:"
    echo "  nip update --all"
    echo ""
