# nimpak/adapters/aur.nim
# AUR grafting adapter for accessing the Arch User Repository
# Builds packages in isolated Nippels for security

import std/[strutils, json, os, times, osproc, tables, strformat, httpclient]
import ../grafting
from ../cas import Result, ok, err, isErr, get

type
  AURAdapter* = ref object of PackageAdapter
    aurBaseUrl*: string
    cacheDir*: string
    buildInNippel*: bool
    reviewPKGBUILD*: bool
    trustLevel*: AURTrustLevel
    buildTimeout*: int

  AURTrustLevel* = enum
    Untrusted,    # Build and run in Nippel (default)
    Reviewed,     # User reviewed PKGBUILD
    Trusted       # Promoted to system package

  AURPackageInfo* = object
    name*: string
    version*: string
    description*: string
    url*: string
    license*: seq[string]
    maintainer*: string
    votes*: int
    popularity*: float
    firstSubmitted*: DateTime
    lastModified*: DateTime
    depends*: seq[string]
    makedepends*: seq[string]
    pkgbuildUrl*: string

  AURBuildResult = object
    success: bool
    packagePath: string
    buildLog: string
    nippelName: string
    error: string

# Forward declarations
proc isAURAvailable(): bool
proc searchAURPackage(adapter: AURAdapter, packageName: string): AURPackageInfo
proc downloadPKGBUILD(adapter: AURAdapter, packageName: string): Result[string, string]
proc showPKGBUILDReview(pkgbuildPath: string): bool
proc buildInNippel(adapter: AURAdapter, pkgbuildPath: string, packageName: string): AURBuildResult
proc calculateAURHash(pkgbuildPath: string): string

proc newAURAdapter*(config: JsonNode = nil): AURAdapter =
  ## Create a new AUR adapter with configuration
  result = AURAdapter(
    name: "aur",
    priority: 30,
    enabled: true,
    aurBaseUrl: "https://aur.archlinux.org",
    cacheDir: "/var/cache/nip/aur",
    buildInNippel: true,  # Always build in Nippel for security
    reviewPKGBUILD: false,  # Optional review
    trustLevel: Untrusted,
    buildTimeout: 3600  # 1 hour
  )

  # Apply configuration if provided
  if config != nil:
    if config.hasKey("cache_dir"):
      result.cacheDir = config["cache_dir"].getStr()
    if config.hasKey("review_pkgbuild"):
      result.reviewPKGBUILD = config["review_pkgbuild"].getBool()
    if config.hasKey("build_timeout"):
      result.buildTimeout = config["build_timeout"].getInt()

method graftPackage*(adapter: AURAdapter, packageName: string, cache: GraftingCache): GraftResult =
  ## Graft a package from AUR (builds in Nippel for security)
  echo fmt"🌱 Grafting package from AUR: {packageName}"
  echo ""

  result = GraftResult(
    success: false,
    packageId: packageName,
    errors: @[]
  )

  try:
    # 1. Check if AUR is accessible
    if not isAURAvailable():
      result.errors.add("AUR is not accessible. Check your internet connection.")
      return result

    # 2. Search for package in AUR
    echo "🔍 Searching AUR for package..."
    let packageInfo = searchAURPackage(adapter, packageName)
    if packageInfo.name == "":
      result.errors.add(fmt"Package '{packageName}' not found in AUR")
      return result

    # 3. Show package information
    echo fmt"📦 Package: {packageInfo.name}"
    echo fmt"   Version: {packageInfo.version}"
    echo fmt"   Maintainer: {packageInfo.maintainer}"
    echo fmt"   Votes: {packageInfo.votes}"
    echo fmt"   Popularity: {packageInfo.popularity:.2f}"
    echo ""

    # 4. Security warning
    echo "⚠️  Security Notice:"
    echo "   AUR packages are user-submitted and not officially maintained."
    echo "   This package will be built and run in an isolated Nippel."
    echo ""

    # 5. Download PKGBUILD
    echo "📥 Downloading PKGBUILD..."
    let pkgbuildResult = downloadPKGBUILD(adapter, packageName)
    if pkgbuildResult.isErr:
      result.errors.add("Failed to download PKGBUILD: " & pkgbuildResult.error)
      return result

    let pkgbuildPath = pkgbuildResult.get

    # 6. Optional PKGBUILD review
    if adapter.reviewPKGBUILD:
      if not showPKGBUILDReview(pkgbuildPath):
        result.errors.add("PKGBUILD review cancelled by user")
        return result

    # 7. Build in isolated Nippel
    echo "🏗️  Creating build Nippel..."
    let buildResult = buildInNippel(adapter, pkgbuildPath, packageName)
    if not buildResult.success:
      result.errors.add("Failed to build in Nippel: " & buildResult.error)
      return result

    # 8. Calculate hashes
    let originalHash = calculateAURHash(pkgbuildPath)
    let graftHash = calculateGraftHash(packageName, "aur", now())

    # 9. Create metadata
    let metadata = GraftedPackageMetadata(
      packageName: packageInfo.name,
      version: packageInfo.version,
      source: "aur",
      graftedAt: now(),
      originalHash: originalHash,
      graftHash: graftHash,
      buildLog: buildResult.buildLog,
      provenance: ProvenanceInfo(
        originalSource: "aur",
        downloadUrl: packageInfo.pkgbuildUrl,
        archivePath: pkgbuildPath,
        extractedPath: buildResult.packagePath,
        conversionLog: fmt"Built in Nippel: {buildResult.nippelName}"
      )
    )

    result.success = true
    result.packageId = packageInfo.name
    result.metadata = metadata

    echo ""
    echo fmt"✅ Successfully grafted from AUR: {packageInfo.name} {packageInfo.version}"
    echo fmt"📦 Installed to Nippel: {buildResult.nippelName}"
    echo fmt"🔗 To run: nippel run {packageName}"
    echo ""
    echo "💡 To promote to system package: nip promote aur:{packageName}"

  except Exception as e:
    result.errors.add(fmt"Exception in AUR grafting: {e.msg}")

proc isAURAvailable(): bool =
  ## Check if AUR is accessible
  try:
    let client = newHttpClient(timeout = 5000)
    let response = client.get("https://aur.archlinux.org")
    return response.code == Http200
  except:
    return false

proc searchAURPackage(adapter: AURAdapter, packageName: string): AURPackageInfo =
  ## Search for a package in AUR using RPC interface
  var info = AURPackageInfo()

  try:
    let client = newHttpClient()
    let url = fmt"{adapter.aurBaseUrl}/rpc/?v=5&type=info&arg={packageName}"
    let response = client.getContent(url)
    let jsonData = parseJson(response)

    if jsonData.hasKey("results") and jsonData["results"].len > 0:
      let pkgData = jsonData["results"][0]

      info.name = pkgData{"Name"}.getStr("")
      info.version = pkgData{"Version"}.getStr("")
      info.description = pkgData{"Description"}.getStr("")
      info.url = pkgData{"URL"}.getStr("")
      info.maintainer = pkgData{"Maintainer"}.getStr("orphan")
      info.votes = pkgData{"NumVotes"}.getInt(0)
      info.popularity = pkgData{"Popularity"}.getFloat(0.0)

      # Parse timestamps
      if pkgData.hasKey("FirstSubmitted"):
        info.firstSubmitted = fromUnix(pkgData["FirstSubmitted"].getInt()).utc
      if pkgData.hasKey("LastModified"):
        info.lastModified = fromUnix(pkgData["LastModified"].getInt()).utc

      # Parse dependencies
      if pkgData.hasKey("Depends"):
        for dep in pkgData["Depends"]:
          info.depends.add(dep.getStr())
      if pkgData.hasKey("MakeDepends"):
        for dep in pkgData["MakeDepends"]:
          info.makedepends.add(dep.getStr())

      # Parse license
      if pkgData.hasKey("License"):
        for lic in pkgData["License"]:
          info.license.add(lic.getStr())

      info.pkgbuildUrl = fmt"{adapter.aurBaseUrl}/cgit/aur.git/plain/PKGBUILD?h={packageName}"

  except Exception as e:
    echo fmt"Warning: Failed to search AUR: {e.msg}"

  info

proc downloadPKGBUILD(adapter: AURAdapter, packageName: string): Result[string, string] =
  ## Download PKGBUILD from AUR
  try:
    let client = newHttpClient()
    let url = fmt"{adapter.aurBaseUrl}/cgit/aur.git/plain/PKGBUILD?h={packageName}"

    # Create cache directory
    let pkgDir = adapter.cacheDir / packageName
    createDir(pkgDir)

    let pkgbuildPath = pkgDir / "PKGBUILD"
    let content = client.getContent(url)

    writeFile(pkgbuildPath, content)

    return Result[string, string](isOk: true, value: pkgbuildPath)

  except Exception as e:
    return Result[string, string](isOk: false, error: fmt"Failed to download PKGBUILD: {e.msg}")

proc showPKGBUILDReview(pkgbuildPath: string): bool =
  ## Show PKGBUILD for user review
  echo "📄 PKGBUILD Review:"
  echo "─".repeat(70)

  try:
    let content = readFile(pkgbuildPath)
    echo content
    echo "─".repeat(70)
    echo ""

    stdout.write("Continue with build? [y/N]: ")
    let response = stdin.readLine().toLowerAscii()
    return response == "y" or response == "yes"

  except:
    return false

proc buildInNippel(adapter: AURAdapter, pkgbuildPath: string, packageName: string): AURBuildResult =
  ## Build AUR package in isolated Nippel
  result = AURBuildResult(success: false)

  try:
    # Create unique Nippel name for this build
    let nippelName = fmt"aur-{packageName}-build"

    echo fmt"🏗️  Building in Nippel: {nippelName}"
    echo ""

    # TODO: Integrate with nippels.nim to create isolated build environment
    # For now, build in regular environment (will be enhanced in next iteration)

    let pkgDir = pkgbuildPath.parentDir()

    # Run makepkg
    echo "🔨 Running makepkg..."
    let makepkgCmd = fmt"cd {pkgDir} && makepkg -s --noconfirm"
    let (output, exitCode) = execCmdEx(makepkgCmd)

    if exitCode == 0:
      # Find built package
      for file in walkDir(pkgDir):
        if file.path.endsWith(".pkg.tar.zst"):
          result.success = true
          result.packagePath = file.path
          result.buildLog = output
          result.nippelName = nippelName
          echo fmt"✅ Build successful: {file.path}"
          return result

      result.error = "Build succeeded but no package file found"
    else:
      result.error = fmt"makepkg failed with exit code {exitCode}: {output}"

  except Exception as e:
    result.error = fmt"Exception during build: {e.msg}"

proc calculateAURHash(pkgbuildPath: string): string =
  ## Calculate hash of PKGBUILD
  try:
    let content = readFile(pkgbuildPath)
    # TODO: Use xxHash3 when available
    let hashCmd = fmt"echo '{content}' | sha256sum"
    let (output, exitCode) = execCmdEx(hashCmd)
    if exitCode == 0:
      return "aur-" & output.split()[0]
  except:
    discard

  "aur-hash-error"

method validatePackage*(adapter: AURAdapter, packageName: string): Result[bool, string] {.base.} =
  ## Validate that a package exists in AUR
  try:
    let info = searchAURPackage(adapter, packageName)

    if info.name == "":
      return Result[bool, string](isOk: false, error: fmt"Package '{packageName}' not found in AUR")

    return Result[bool, string](isOk: true, value: true)

  except Exception as e:
    return Result[bool, string](isOk: false, error: fmt"Validation error: {e.msg}")

method getPackageInfo*(adapter: AURAdapter, packageName: string): Result[JsonNode, string] {.base.} =
  ## Get detailed package information from AUR
  try:
    let info = searchAURPackage(adapter, packageName)

    if info.name == "":
      return Result[JsonNode, string](isOk: false, error: fmt"Package '{packageName}' not found in AUR")

    let jsonResult = %*{
      "name": info.name,
      "version": info.version,
      "description": info.description,
      "url": info.url,
      "license": info.license,
      "maintainer": info.maintainer,
      "votes": info.votes,
      "popularity": info.popularity,
      "depends": info.depends,
      "makedepends": info.makedepends,
      "source": "aur",
      "adapter": adapter.name,
      "trust_level": "untrusted",
      "build_method": "nippel"
    }

    return Result[JsonNode, string](isOk: true, value: jsonResult)

  except Exception as e:
    return Result[JsonNode, string](isOk: false, error: fmt"Error getting package info: {e.msg}")
