## NEXTER Installation Workflow
##
## **Purpose:**
## Implements atomic installation workflow for .nexter container packages.
## Handles chunk extraction to CAS, manifest creation, reference tracking,
## and rollback on failure.
##
## **Design Principles:**
## - Atomic operations (all-or-nothing)
## - Automatic rollback on failure
## - CAS deduplication
## - Reference tracking for garbage collection
## - Container isolation and lifecycle management
##
## **Requirements:**
## - Requirement 5.3: Extract chunks to CAS and create manifest in ~/.local/share/nexus/nexters/
## - Requirement 11.1: Container installation SHALL be atomic (all-or-nothing)
## - Requirement 11.2: Installation failures SHALL rollback to previous state

import std/[os, strutils, times, options]
import nip/[nexter, nexter_manifest, manifest_parser]

type
  ContainerInstallResult* = object
    ## Result of NEXTER container installation
    success*: bool
    containerName*: string
    version*: string
    installPath*: string
    chunksInstalled*: int
    error*: string

  ContainerInstallError* = object of CatchableError
    code*: ContainerInstallErrorCode
    context*: string
    suggestions*: seq[string]

  ContainerInstallErrorCode* = enum
    ContainerAlreadyInstalled,
    InsufficientSpace,
    PermissionDenied,
    ChunkExtractionFailed,
    ManifestCreationFailed,
    RollbackFailed,
    InvalidContainer,
    EnvironmentConfigInvalid

  ContainerInstallTransaction* = object
    ## Transaction tracking for atomic container installation
    id*: string
    containerName*: string
    startTime*: DateTime
    operations*: seq[ContainerInstallOperation]
    completed*: bool

  ContainerInstallOperation* = object
    ## Individual operation in container installation transaction
    kind*: OperationKind
    path*: string
    data*: string
    timestamp*: DateTime

  OperationKind* = enum
    CreateDirectory,
    WriteFile,
    CreateSymlink,
    AddCASChunk,
    AddReference

# ============================================================================
# Forward Declarations
# ============================================================================

proc rollbackContainerInstallation*(transaction: ContainerInstallTransaction, storageRoot: string)

# ============================================================================
# Installation Workflow
# ============================================================================

proc installNEXTER*(containerPath: string, storageRoot: string = ""): ContainerInstallResult =
  ## Install NEXTER container atomically
  ##
  ## **Requirements:**
  ## - Requirement 5.3: Extract chunks to CAS and create manifest
  ## - Requirement 11.1: Atomic installation (all-or-nothing)
  ## - Requirement 11.2: Rollback on failure
  ##
  ## **Process:**
  ## 1. Parse NEXTER container archive
  ## 2. Validate container integrity
  ## 3. Check if already installed
  ## 4. Create installation transaction
  ## 5. Extract chunks to CAS with deduplication
  ## 6. Create manifest in ~/.local/share/nexus/nexters/
  ## 7. Create environment config
  ## 8. Add references to cas/refs/nexters/
  ## 9. Commit transaction or rollback on failure
  ##
  ## **Returns:**
  ## - ContainerInstallResult with success status and details
  ##
  ## **Raises:**
  ## - ContainerInstallError if installation fails

  let root = if storageRoot.len > 0: storageRoot else: getHomeDir() / ".local/share/nexus"
  let nextersDir = root / "nexters"
  let casRoot = root / "cas"

  try:
    # Parse container archive
    let container = parseNEXTER(containerPath)

    # Check if already installed
    let installPath = nextersDir / container.manifest.name
    if dirExists(installPath):
      return ContainerInstallResult(
        success: false,
        containerName: container.manifest.name,
        version: $container.manifest.version,
        error: "Container already installed at " & installPath,
        installPath: installPath
      )

    # Create installation transaction
    let transactionId = "nexter-" & $getTime().toUnix()
    var transaction = ContainerInstallTransaction(
      id: transactionId,
      containerName: container.manifest.name,
      startTime: now(),
      operations: @[],
      completed: false
    )

    # Create directories
    createDir(nextersDir)
    createDir(installPath)
    transaction.operations.add(ContainerInstallOperation(
      kind: CreateDirectory,
      path: installPath,
      timestamp: now()
    ))

    # Extract chunks to CAS
    var chunksInstalled = 0
    for chunk in container.chunks:
      let chunkPath = casRoot / "chunks" / (chunk.hash & ".zst")
      if not fileExists(chunkPath):
        createDir(casRoot / "chunks")
        writeFile(chunkPath, chunk.data)
        transaction.operations.add(ContainerInstallOperation(
          kind: AddCASChunk,
          path: chunkPath,
          data: chunk.hash,
          timestamp: now()
        ))
        chunksInstalled += 1

    # Create manifest file
    let manifestContent = generateNEXTERManifest(container.manifest)
    let manifestPath = installPath / "manifest.kdl"
    writeFile(manifestPath, manifestContent)
    transaction.operations.add(ContainerInstallOperation(
      kind: WriteFile,
      path: manifestPath,
      timestamp: now()
    ))

    # Create environment config
    let environmentPath = installPath / "environment.kdl"
    writeFile(environmentPath, container.environment)
    transaction.operations.add(ContainerInstallOperation(
      kind: WriteFile,
      path: environmentPath,
      timestamp: now()
    ))

    # Create signature file
    let signaturePath = installPath / "signature.sig"
    writeFile(signaturePath, container.signature)
    transaction.operations.add(ContainerInstallOperation(
      kind: WriteFile,
      path: signaturePath,
      timestamp: now()
    ))

    # Add references to CAS
    let refsDir = casRoot / "refs" / "nexters"
    createDir(refsDir)
    let refsPath = refsDir / (container.manifest.name & ".refs")
    var refsList: seq[string] = @[]
    for chunk in container.chunks:
      refsList.add(chunk.hash)
    writeFile(refsPath, refsList.join("\n"))
    transaction.operations.add(ContainerInstallOperation(
      kind: AddReference,
      path: refsPath,
      timestamp: now()
    ))

    # Mark transaction as completed
    transaction.completed = true

    return ContainerInstallResult(
      success: true,
      containerName: container.manifest.name,
      version: $container.manifest.version,
      installPath: installPath,
      chunksInstalled: chunksInstalled,
      error: ""
    )

  except Exception as e:
    return ContainerInstallResult(
      success: false,
      containerName: "",
      version: "",
      error: "Installation failed: " & e.msg,
      installPath: ""
    )

# ============================================================================
# Rollback
# ============================================================================

proc rollbackContainerInstallation*(transaction: ContainerInstallTransaction, storageRoot: string) =
  ## Rollback container installation on failure
  ##
  ## **Requirements:**
  ## - Requirement 11.2: Rollback to previous state on failure
  ##
  ## **Process:**
  ## 1. Process operations in reverse order
  ## 2. Remove files and directories
  ## 3. Don't remove CAS chunks (might be shared)
  ## 4. Continue rollback even if individual operations fail
  ##
  ## **Note:**
  ## - CAS chunks are not removed (garbage collection handles orphaned chunks)
  ## - References are removed to mark chunks as orphaned

  # Process operations in reverse order
  for i in countdown(transaction.operations.len - 1, 0):
    let op = transaction.operations[i]

    try:
      case op.kind:
      of CreateDirectory:
        if dirExists(op.path):
          removeDir(op.path)
      of WriteFile, CreateSymlink:
        if fileExists(op.path):
          removeFile(op.path)
      of AddCASChunk:
        # Don't remove CAS chunks - garbage collection handles them
        discard
      of AddReference:
        if fileExists(op.path):
          removeFile(op.path)
    except:
      # Continue rollback even if individual operations fail
      discard

# ============================================================================
# Query Functions
# ============================================================================

proc isContainerInstalled*(containerName: string, storageRoot: string = ""): bool =
  ## Check if container is installed
  let root = if storageRoot.len > 0: storageRoot else: getHomeDir() / ".local/share/nexus"
  let installPath = root / "nexters" / containerName
  return dirExists(installPath)

proc getInstalledContainerVersion*(containerName: string, storageRoot: string = ""): Option[string] =
  ## Get installed container version
  let root = if storageRoot.len > 0: storageRoot else: getHomeDir() / ".local/share/nexus"
  let manifestPath = root / "nexters" / containerName / "manifest.kdl"

  if not fileExists(manifestPath):
    return none[string]()

  try:
    let content = readFile(manifestPath)
    # Parse manifest to extract version
    let manifest = parseNEXTERManifest(content)
    return some($manifest.version)
  except:
    discard

  return none[string]()

proc listInstalledContainers*(storageRoot: string = ""): seq[string] =
  ## List all installed containers
  let root = if storageRoot.len > 0: storageRoot else: getHomeDir() / ".local/share/nexus"
  let nextersDir = root / "nexters"

  result = @[]
  if not dirExists(nextersDir):
    return

  for entry in walkDir(nextersDir):
    if entry.kind == pcDir:
      result.add(entry.path.extractFilename())

# ============================================================================
# Verification
# ============================================================================

proc verifyContainerInstallation*(containerName: string, storageRoot: string = ""): bool =
  ## Verify container installation integrity
  ##
  ## **Requirements:**
  ## - Requirement 5.3: Verify manifest and environment config exist
  ##
  ## **Checks:**
  ## 1. Container directory exists
  ## 2. manifest.kdl exists and is readable
  ## 3. environment.kdl exists and is readable
  ## 4. signature.sig exists and is readable
  ## 5. All referenced chunks exist in CAS

  let root = if storageRoot.len > 0: storageRoot else: getHomeDir() / ".local/share/nexus"
  let installPath = root / "nexters" / containerName
  let casRoot = root / "cas"

  # Check directory exists
  if not dirExists(installPath):
    return false

  # Check required files
  if not fileExists(installPath / "manifest.kdl"):
    return false
  if not fileExists(installPath / "environment.kdl"):
    return false
  if not fileExists(installPath / "signature.sig"):
    return false

  # Check CAS chunks referenced
  let refsPath = casRoot / "refs" / "nexters" / (containerName & ".refs")
  if fileExists(refsPath):
    try:
      let refs = readFile(refsPath).split('\n')
      for refHash in refs:
        if refHash.len > 0:
          let chunkPath = casRoot / "chunks" / (refHash & ".zst")
          if not fileExists(chunkPath):
            return false
    except:
      return false

  return true

# ============================================================================
# Formatting
# ============================================================================

proc `$`*(installResult: ContainerInstallResult): string =
  ## Format installation result as string
  if installResult.success:
    return "✅ Installed " & installResult.containerName & " v" & installResult.version &
           " to " & installResult.installPath & " (" & $installResult.chunksInstalled & " chunks)"
  else:
    return "❌ Installation failed: " & installResult.error
