## NEXTER Archive Handler
##
## **Purpose:**
## Handles .nexter (Nexus Container) archive creation and parsing.
## NEXTER containers are tar.zst archives containing manifest.kdl, environment config,
## CAS chunks, and Ed25519 signatures.
##
## **Design Principles:**
## - Lightweight container isolation
## - Content-addressable storage for deduplication
## - Atomic operations with rollback capability
## - Ed25519 signature verification
##
## **Requirements:**
## - Requirement 5.1: .nexter contains manifest.kdl, environment config, CAS chunks, Ed25519 signature
## - Requirement 8.2: Use zstd --auto for archive compression
##
## **Archive Structure:**
## ```
## container.nexter (tar.zst)
## ├── manifest.kdl          # Container metadata
## ├── environment.kdl       # Environment variables
## ├── chunks/               # CAS chunks
## │   ├── xxh3-abc123.zst
## │   ├── xxh3-def456.zst
## │   └── ...
## └── signature.sig         # Ed25519 signature
## ```

import std/[os, strutils, times, options, sequtils, osproc, logging]
import nip/cas
import nip/xxhash
import nip/nexter_manifest

type
  NEXTERContainer* = object
    ## Complete NEXTER container with all components
    manifest*: NEXTERManifest
    environment*: string
    chunks*: seq[ChunkData]
    signature*: string
    archivePath*: string

  ChunkData* = object
    ## Chunk data extracted from archive
    hash*: string
    data*: string
    size*: int64
    chunkType*: ChunkType

  NEXTERArchiveError* = object of CatchableError
    code*: NEXTERArchiveErrorCode
    context*: string
    suggestions*: seq[string]

  NEXTERArchiveErrorCode* = enum
    ArchiveNotFound,
    InvalidArchive,
    ManifestMissing,
    EnvironmentMissing,
    SignatureMissing,
    ChunkMissing,
    ExtractionFailed,
    CompressionFailed,
    InvalidFormat

# ============================================================================
# Archive Parsing
# ============================================================================

proc parseNEXTER*(path: string): NEXTERContainer =
  ## Parse .nexter archive and extract all components
  ##
  ## **Requirements:**
  ## - Requirement 5.1: Extract manifest.kdl, environment config, CAS chunks, signature
  ## - Requirement 8.2: Handle zstd --auto compressed archives
  ##
  ## **Process:**
  ## 1. Verify archive exists and is readable
  ## 2. Extract to temporary directory
  ## 3. Parse manifest.kdl
  ## 4. Load environment.kdl
  ## 5. Load chunks from chunks/ directory
  ## 6. Load signature from signature.sig
  ## 7. Verify integrity
  ##
  ## **Raises:**
  ## - NEXTERArchiveError if archive is invalid or missing components

  if not fileExists(path):
    raise newException(NEXTERArchiveError, "NEXTER archive not found: " & path)

  # Create temporary extraction directory
  let tempDir = getTempDir() / "nexter-extract-" & $getTime().toUnix()
  createDir(tempDir)

  try:
    # Extract archive using tar with zstd decompression
    # Using --auto-compress lets tar detect compression automatically
    let extractCmd = "tar --auto-compress -xf " & quoteShell(path) & " -C " & quoteShell(tempDir)
    let exitCode = execCmd(extractCmd)

    if exitCode != 0:
      raise newException(NEXTERArchiveError, "Failed to extract NEXTER archive")

    # Verify required files exist
    let manifestPath = tempDir / "manifest.kdl"
    let environmentPath = tempDir / "environment.kdl"
    let signaturePath = tempDir / "signature.sig"
    let chunksDir = tempDir / "chunks"

    if not fileExists(manifestPath):
      raise newException(NEXTERArchiveError, "Invalid archive: manifest.kdl missing")

    if not fileExists(environmentPath):
      raise newException(NEXTERArchiveError, "Invalid archive: environment.kdl missing")

    if not fileExists(signaturePath):
      raise newException(NEXTERArchiveError, "Invalid archive: signature.sig missing")

    # Parse manifest
    let manifestContent = readFile(manifestPath)
    let manifest = parseNEXTERManifest(manifestContent)

    # Load environment
    let environment = readFile(environmentPath)

    # Load signature
    let signature = readFile(signaturePath)

    # Load chunks
    var chunks: seq[ChunkData] = @[]
    if dirExists(chunksDir):
      for file in walkFiles(chunksDir / "*.zst"):
        let fileName = file.extractFilename()
        let hash = fileName.replace(".zst", "")
        let data = readFile(file)
        chunks.add(ChunkData(
          hash: hash,
          data: data,
          size: data.len.int64,
          chunkType: Binary
        ))

    return NEXTERContainer(
      manifest: manifest,
      environment: environment,
      chunks: chunks,
      signature: signature,
      archivePath: path
    )

  finally:
    # Clean up temporary directory
    if dirExists(tempDir):
      removeDir(tempDir)

# ============================================================================
# Archive Creation
# ============================================================================

proc createNEXTER*(manifest: NEXTERManifest, environment: string, chunks: seq[ChunkData], 
                   signature: string, outputPath: string) =
  ## Create .nexter archive from components
  ##
  ## **Requirements:**
  ## - Requirement 5.1: Create archive with manifest.kdl, environment config, CAS chunks, signature
  ## - Requirement 8.2: Use zstd --auto for archive compression
  ##
  ## **Process:**
  ## 1. Validate output path is writable
  ## 2. Create temporary directory
  ## 3. Write manifest.kdl
  ## 4. Write environment.kdl
  ## 5. Write chunks to chunks/ directory
  ## 6. Write signature.sig
  ## 7. Create tar.zst archive
  ## 8. Verify archive integrity
  ##
  ## **Raises:**
  ## - OSError if output directory doesn't exist or isn't writable
  ## - NEXTERArchiveError if creation fails

  # Validate output path
  let outputDir = outputPath.parentDir()
  if not dirExists(outputDir):
    raise newException(OSError, "Output directory does not exist: " & outputDir)

  let tempDir = getTempDir() / "nexter-create-" & $getTime().toUnix()
  createDir(tempDir)

  try:
    # Write manifest
    let manifestContent = generateNEXTERManifest(manifest)
    writeFile(tempDir / "manifest.kdl", manifestContent)

    # Write environment
    writeFile(tempDir / "environment.kdl", environment)

    # Write chunks
    let chunksDir = tempDir / "chunks"
    createDir(chunksDir)
    for chunk in chunks:
      let chunkPath = chunksDir / (chunk.hash & ".zst")
      writeFile(chunkPath, chunk.data)

    # Write signature
    writeFile(tempDir / "signature.sig", signature)

    # Create tar.zst archive
    let createCmd = "tar --auto-compress -cf " & quoteShell(outputPath) & " -C " & quoteShell(tempDir) & " ."
    let exitCode = execCmd(createCmd)

    if exitCode != 0:
      raise newException(NEXTERArchiveError, "Failed to create NEXTER archive")

    info("Created NEXTER archive: " & outputPath)

  finally:
    # Clean up temporary directory
    if dirExists(tempDir):
      removeDir(tempDir)

# ============================================================================
# Chunk Extraction to CAS
# ============================================================================

proc extractChunksToCAS*(container: NEXTERContainer, casRoot: string): seq[string] =
  ## Extract chunks from NEXTER container to CAS
  ##
  ## **Requirements:**
  ## - Requirement 2.1: Store chunks in CAS with xxh3 hashing
  ## - Requirement 2.2: Verify integrity using xxh3 hash
  ##
  ## **Process:**
  ## 1. For each chunk in container
  ## 2. Decompress chunk
  ## 3. Verify xxh3 hash
  ## 4. Store in CAS
  ## 5. Return list of stored hashes
  ##
  ## **Returns:**
  ## - List of stored chunk hashes

  result = @[]

  for chunk in container.chunks:
    try:
      # Decompress chunk
      let decompressed = chunk.data  # TODO: Implement zstd decompression
      
      # Verify hash
      let calculatedHash = "xxh3-" & $calculateXXH3(decompressed)
      if calculatedHash != chunk.hash:
        warn("Hash mismatch for chunk: " & chunk.hash)
        continue

      # Store in CAS
      let entry = storeObject(decompressed, casRoot)
      result.add(string(entry.hash))

    except Exception as e:
      warn("Failed to extract chunk " & chunk.hash & ": " & e.msg)

# ============================================================================
# Archive Verification
# ============================================================================

proc verifyNEXTER*(path: string): bool =
  ## Verify NEXTER archive integrity
  ##
  ## **Requirements:**
  ## - Requirement 9.2: Verify Ed25519 signature
  ## - Requirement 14.1: Verify xxh3 hashes
  ##
  ## **Checks:**
  ## 1. Archive exists and is readable
  ## 2. Archive is valid tar.zst
  ## 3. All required components present
  ## 4. Manifest is valid
  ## 5. Signature is present
  ##
  ## **Returns:**
  ## - true if archive is valid, false otherwise

  try:
    let container = parseNEXTER(path)
    
    # Verify manifest
    if container.manifest.name.len == 0:
      return false
    
    # Verify signature
    if container.signature.len == 0:
      return false
    
    # Verify chunks
    if container.chunks.len == 0:
      warn("NEXTER archive has no chunks")
    
    return true

  except Exception as e:
    warn("NEXTER verification failed: " & e.msg)
    return false

# ============================================================================
# Utility Functions
# ============================================================================

proc listChunksInArchive*(path: string): seq[string] =
  ## List all chunks in a NEXTER archive
  ##
  ## **Returns:**
  ## - List of chunk hashes

  try:
    let container = parseNEXTER(path)
    return container.chunks.mapIt(it.hash)
  except Exception as e:
    warn("Failed to list chunks: " & e.msg)
    return @[]

proc getArchiveSize*(path: string): int64 =
  ## Get size of NEXTER archive
  ##
  ## **Returns:**
  ## - Size in bytes

  if fileExists(path):
    return getFileSize(path)
  return 0

proc getContainerInfo*(path: string): Option[NEXTERManifest] =
  ## Get container information from archive
  ##
  ## **Returns:**
  ## - Container manifest if valid, none otherwise

  try:
    let container = parseNEXTER(path)
    return some(container.manifest)
  except Exception as e:
    warn("Failed to get container info: " & e.msg)
    return none(NEXTERManifest)
