# nimpak/install.nim
# Package installation orchestrator with atomic operations

import std/[tables, sequtils, strformat]
import ../nip/types, dependency, transactions, filesystem, cas

type
  InstallStep* = object
    package*: PackageId
    fragment*: Fragment
    stepNumber*: int
    totalSteps*: int

  InstallPlan* = object
    steps*: seq[InstallStep]
    transaction*: Transaction
    rollbackData*: seq[RollbackInfo]

  InstallProgress* = object
    currentStep*: int
    totalSteps*: int
    currentPackage*: PackageId
    status*: InstallStatus

  InstallStatus* = enum
    Planning, Installing, Completed, Failed, RolledBack

  InstallError* = object of NimPakError
    failedPackage*: PackageId
    failedStep*: int
    rollbackSuccess*: bool

# Public API
proc installPackages*(packages: seq[PackageId], fragments: Table[PackageId, Fragment],
                     fsManager: FilesystemManager, casManager: CasManager): Result[void, InstallError] =
  ## Main installation orchestrator (6.2.1, 6.2.2, 6.2.3, 6.2.4)

  # Create installation plan
  let planResult = createInstallPlan(packages, fragments)
  if planResult.isErr:
    return err(InstallError(
      code: DependencyConflict,
      msg: "Failed to create installation plan: " & planResult.error.msg
    ))

  var plan = planResult.get()

  # Begin atomic transaction
  plan.transaction = beginTransaction()

  # Execute installation steps
  let executeResult = executeInstallPlan(plan, fsManager, casManager)
  if executeResult.isErr:
    # Rollback on failure
    let rollbackResult = rollbackTransaction(plan.transaction)
    return err(InstallError(
      code: executeResult.error.code,
      msg: executeResult.error.msg,
      failedPackage: executeResult.error.failedPackage,
      failedStep: executeResult.error.failedStep,
      rollbackSuccess: rollbackResult.isOk
    ))

  # Commit transaction
  let commitResult = commitTransaction(plan.transaction)
  if commitResult.isErr:
    return err(InstallError(
      code: TransactionFailed,
      msg: "Failed to commit installation: " & commitResult.error
    ))

  ok()

proc createInstallPlan(packages: seq[PackageId], fragments: Table[PackageId, Fragment]): Result[InstallPlan, DependencyError] =
  ## Create ordered installation plan from dependency resolution (6.2.1)
  var allPackages: seq[PackageId] = @[]
  var processedPackages = initHashSet[PackageId]()

  # Resolve dependencies for each requested package
  for pkg in packages:
    let resolveResult = resolveDependencies(pkg, fragments)
    if resolveResult.isErr:
      return err(resolveResult.error)

    let installOrder = resolveResult.get()
    for orderedPkg in installOrder.packages:
      if orderedPkg notin processedPackages:
        allPackages.add(orderedPkg)
        processedPackages.incl(orderedPkg)

  # Create installation steps
  var steps: seq[InstallStep] = @[]
  for i, pkg in allPackages.pairs:
    if pkg in fragments:
      steps.add(InstallStep(
        package: pkg,
        fragment: fragments[pkg],
        stepNumber: i + 1,
        totalSteps: allPackages.len
      ))

  ok(InstallPlan(
    steps: steps,
    transaction: Transaction(), # Will be initialized later
    rollbackData: @[]
  ))

proc executeInstallPlan(plan: var InstallPlan, fsManager: FilesystemManager,
                       casManager: CasManager): Result[void, InstallError] =
  ## Execute installation plan with progress tracking (6.2.3, 6.2.5)

  for step in plan.steps:
    echo fmt"Installing {step.package.name} ({step.stepNumber}/{step.totalSteps})"

    # Install individual package
    let installResult = installSinglePackage(step, fsManager, casManager, plan.transaction)
    if installResult.isErr:
      return err(InstallError(
        code: installResult.error.code,
        msg: fmt"Failed to install {step.package.name}: {installResult.error.msg}",
        failedPackage: step.package,
        failedStep: step.stepNumber
      ))

  ok()

proc installSinglePackage(step: InstallStep, fsManager: FilesystemManager,
                         casManager: CasManager, transaction: var Transaction): Result[void, NimPakError] =
  ## Install a single package with atomic operations
  let pkg = step.package
  let fragment = step.fragment

  # Create package directory structure
  let programDir = fmt"/Programs/{pkg.name}/{pkg.version}"
  let createDirOp = Operation(
    kind: CreateDir,
    target: programDir,
    data: %*{"permissions": "755"}
  )
  transaction.addOperation(createDirOp)

  # Install package files from CAS or source
  let installResult = installPackageFiles(fragment, programDir, casManager)
  if installResult.isErr:
    return installResult

  # Create symlinks in /System/Index
  let symlinkResult = createPackageSymlinks(fragment, programDir, fsManager, transaction)
  if symlinkResult.isErr:
    return symlinkResult

  ok()

proc installPackageFiles(fragment: Fragment, targetDir: string, casManager: CasManager): Result[void, NimPakError] =
  ## Install package files from CAS or extract from source
  # TODO: Implement file extraction from CAS or NPK package
  # For now, create placeholder implementation
  echo fmt"Installing files for {fragment.id.name} to {targetDir}"
  ok()

proc createPackageSymlinks(fragment: Fragment, programDir: string,
                          fsManager: FilesystemManager, transaction: var Transaction): Result[void, NimPakError] =
  ## Create symlinks in /System/Index for package binaries and libraries
  # TODO: Implement symlink creation based on package manifest
  # For now, create placeholder implementation
  echo fmt"Creating symlinks for {fragment.id.name}"
  ok()

# Progress tracking utilities (6.2.5)
proc getInstallProgress*(plan: InstallPlan, currentStep: int): InstallProgress =
  ## Get current installation progress
  let current = if currentStep <= plan.steps.len: currentStep else: plan.steps.len
  let currentPkg = if current > 0 and current <= plan.steps.len:
                     plan.steps[current - 1].package
                   else:
                     PackageId(name: "", version: "", stream: Stable)

  InstallProgress(
    currentStep: current,
    totalSteps: plan.steps.len,
    currentPackage: currentPkg,
    status: if current == plan.steps.len: Completed else: Installing
  )

proc formatInstallProgress*(progress: InstallProgress): string =
  ## Format installation progress for display
  let percentage = if progress.totalSteps > 0:
                     (progress.currentStep * 100) div progress.totalSteps
                   else: 0

  fmt"[{percentage:3}%] Installing {progress.currentPackage.name} ({progress.currentStep}/{progress.totalSteps})"

# Parallel installation support (6.2.6 - future enhancement)
proc installPackagesParallel*(packages: seq[PackageId], fragments: Table[PackageId, Fragment],
                             fsManager: FilesystemManager, casManager: CasManager): Result[void, InstallError] =
  ## Parallel installation of independent package subtrees (future enhancement)
  # TODO: Implement parallel installation using spawn for independent subtrees
  # For now, fall back to sequential installation
  installPackages(packages, fragments, fsManager, casManager)

# Utility functions
proc validateInstallPlan*(plan: InstallPlan): Result[void, InstallError] =
  ## Validate installation plan before execution
  if plan.steps.len == 0:
    return err(InstallError(
      code: InvalidOperation,
      msg: "Installation plan is empty"
    ))

  # Check for duplicate packages
  var seen = initHashSet[PackageId]()
  for step in plan.steps:
    if step.package in seen:
      return err(InstallError(
        code: DependencyConflict,
        msg: fmt"Duplicate package in plan: {step.package.name}"
      ))
    seen.incl(step.package)

  ok()

proc getInstallSummary*(plan: InstallPlan): string =
  ## Generate installation summary
  result = fmt"Installation Plan Summary:\n"
  result.add(fmt"Total packages: {plan.steps.len}\n")
  result.add("Installation order:\n")

  for step in plan.steps:
    result.add(fmt"  {step.stepNumber}. {step.package.name} {step.package.version}\n")