## NEXTER Archive Handler Tests
##
## Tests for the NEXTER archive handler that creates and parses .nexter containers.
## This verifies that containers can be packaged and extracted correctly.

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

# Helper to create a fully initialized NEXTER manifest
proc createTestManifest(name: string, version: string, description: string = "Test container"): NEXTERManifest =
  let buildDate = parse("2025-11-28T12:00:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'")
  return NEXTERManifest(
    name: name,
    version: parseSemanticVersion(version),
    buildDate: buildDate,
    metadata: ContainerInfo(
      description: description,
      license: "MIT"
    ),
    provenance: ProvenanceInfo(
      source: "https://example.com/source.tar.gz",
      sourceHash: "xxh3-source-hash",
      buildTimestamp: buildDate,
      builder: some("test-builder")
    ),
    buildConfig: BuildConfiguration(
      configureFlags: @[],
      compilerFlags: @[],
      compilerVersion: "gcc-13",
      targetArchitecture: "x86_64",
      libc: "musl",
      allocator: "jemalloc",
      buildSystem: "custom"
    ),
    base: BaseConfig(
      baseImage: some("alpine"),
      baseVersion: some("3.18")
    ),
    environment: initTable[string, string](),
    casChunks: @[],
    namespace: ContainerNamespace(
      isolationType: "full",
      capabilities: @[],
      mounts: @[],
      devices: @[]
    ),
    startup: StartupConfig(
      command: @["/bin/sh"],
      workingDir: "/",
      user: none(string),
      entrypoint: none(string)
    ),
    buildHash: "xxh3-build-hash",
    signature: SignatureInfo(
      algorithm: "ed25519",
      keyId: "test-key",
      signature: "test-sig"
    )
  )

suite "NEXTER Archive Handler Tests":

  setup:
    let tempDir = createTempDir("nexter_test_archive_", "")
    let archivePath = tempDir / "test-container.nexter"

  teardown:
    removeDir(tempDir)

  test "Create NEXTER archive with all components":
    ## Verify NEXTER archive can be created with manifest, environment, chunks, and signature
    
    # Create manifest
    var manifest = createTestManifest("test-container", "1.0.0")
    
    # Create environment
    let environment = "PATH=/usr/bin:/bin\nHOME=/home/user"
    
    # Create chunks
    var chunks: seq[ChunkData] = @[
      ChunkData(
        hash: "xxh3-chunk1",
        data: "chunk1 data",
        size: 11,
        chunkType: Binary
      ),
      ChunkData(
        hash: "xxh3-chunk2",
        data: "chunk2 data",
        size: 11,
        chunkType: Config
      )
    ]
    
    # Create signature
    let signature = "ed25519-signature-placeholder"
    
    # Create archive
    createNEXTER(manifest, environment, chunks, signature, archivePath)
    
    # Verify archive exists
    check fileExists(archivePath)
    check getFileSize(archivePath) > 0

  test "Parse NEXTER archive and extract components":
    ## Verify NEXTER archive can be parsed and all components extracted
    
    # Create and write archive first
    var manifest = createTestManifest("parse-test", "2.0.0")
    
    let environment = "TEST=value"
    var chunks: seq[ChunkData] = @[
      ChunkData(
        hash: "xxh3-parse-chunk",
        data: "parse test data",
        size: 15,
        chunkType: Binary
      )
    ]
    let signature = "ed25519-parse-sig"
    
    createNEXTER(manifest, environment, chunks, signature, archivePath)
    
    # Parse archive
    let container = parseNEXTER(archivePath)
    
    # Verify components
    check container.manifest.name == "parse-test"
    check container.manifest.version.major == 2
    check container.environment.contains("TEST=value")
    check container.signature == "ed25519-parse-sig"
    check container.chunks.len == 1

  test "Archive creation fails for non-existent output path":
    ## Verify error handling for invalid output paths
    
    let invalidPath = "/nonexistent/path/container.nexter"
    var manifest = createTestManifest("fail-test", "1.0.0")
    
    # This should fail gracefully
    expect OSError:
      createNEXTER(manifest, "", @[], "", invalidPath)

  test "Parse fails for non-existent archive":
    ## Verify error handling for missing archives
    
    let nonExistentPath = tempDir / "nonexistent.nexter"
    
    expect NEXTERArchiveError:
      discard parseNEXTER(nonExistentPath)

  test "Verify NEXTER archive integrity":
    ## Verify archive integrity checking works
    
    # Create valid archive
    var manifest = createTestManifest("verify-test", "1.0.0")
    
    createNEXTER(manifest, "TEST=1", @[], "sig", archivePath)
    
    # Verify archive
    check verifyNEXTER(archivePath) == true

  test "List chunks in archive":
    ## Verify chunk listing works
    
    var manifest = createTestManifest("list-test", "1.0.0")
    
    var chunks: seq[ChunkData] = @[
      ChunkData(hash: "xxh3-chunk-a", data: "a", size: 1, chunkType: Binary),
      ChunkData(hash: "xxh3-chunk-b", data: "b", size: 1, chunkType: Config),
      ChunkData(hash: "xxh3-chunk-c", data: "c", size: 1, chunkType: Data)
    ]
    
    createNEXTER(manifest, "", chunks, "", archivePath)
    
    # List chunks
    let chunkList = listChunksInArchive(archivePath)
    check chunkList.len == 3
    check "xxh3-chunk-a" in chunkList
    check "xxh3-chunk-b" in chunkList
    check "xxh3-chunk-c" in chunkList

  test "Get archive size":
    ## Verify archive size calculation
    
    var manifest = createTestManifest("size-test", "1.0.0")
    
    createNEXTER(manifest, "ENV=test", @[], "sig", archivePath)
    
    let size = getArchiveSize(archivePath)
    check size > 0

  test "Get container info from archive":
    ## Verify container info extraction
    
    var manifest = createTestManifest("info-test", "3.2.1", "Info test container")
    
    createNEXTER(manifest, "", @[], "", archivePath)
    
    let info = getContainerInfo(archivePath)
    check info.isSome
    check info.get().name == "info-test"
    check info.get().version.major == 3
    check info.get().version.minor == 2
    check info.get().version.patch == 1
    check info.get().metadata.description == "Info test container"

  test "Archive with multiple chunks":
    ## Verify archives with many chunks work correctly
    
    var manifest = createTestManifest("multi-chunk", "1.0.0")
    
    # Create 10 chunks
    var chunks: seq[ChunkData] = @[]
    for i in 0..<10:
      chunks.add(ChunkData(
        hash: "xxh3-chunk-" & $i,
        data: "chunk data " & $i,
        size: (11 + i).int64,
        chunkType: Binary
      ))
    
    createNEXTER(manifest, "", chunks, "", archivePath)
    
    # Verify all chunks present
    let chunkList = listChunksInArchive(archivePath)
    check chunkList.len == 10

  test "Archive roundtrip: create and parse":
    ## Verify create → parse roundtrip preserves data
    
    var manifest = createTestManifest("roundtrip-test", "1.5.2", "Roundtrip test")
    manifest.metadata.author = some("Roundtrip Author")
    
    let environment = "VAR1=value1\nVAR2=value2\nVAR3=value3"
    var chunks: seq[ChunkData] = @[
      ChunkData(hash: "xxh3-rt-1", data: "roundtrip data 1", size: 16, chunkType: Binary),
      ChunkData(hash: "xxh3-rt-2", data: "roundtrip data 2", size: 16, chunkType: Config)
    ]
    let signature = "ed25519-roundtrip-signature"
    
    # Create archive
    createNEXTER(manifest, environment, chunks, signature, archivePath)
    
    # Parse archive
    let container = parseNEXTER(archivePath)
    
    # Verify roundtrip
    check container.manifest.name == "roundtrip-test"
    check container.manifest.version.major == 1
    check container.manifest.version.minor == 5
    check container.manifest.version.patch == 2
    check container.manifest.metadata.description == "Roundtrip test"
    check container.manifest.metadata.author.get() == "Roundtrip Author"
    check container.environment == environment
    check container.signature == signature
    check container.chunks.len == 2

## Property-Based Tests

suite "NEXTER Archive Property Tests":

  test "Property: Archive creation is deterministic":
    ## Verify creating the same archive twice produces identical results
    
    let tempDir = createTempDir("nexter_prop_", "")
    defer: removeDir(tempDir)
    
    let path1 = tempDir / "archive1.nexter"
    let path2 = tempDir / "archive2.nexter"
    
    var manifest = createTestManifest("deterministic", "1.0.0")
    
    let environment = "FIXED=value"
    var chunks: seq[ChunkData] = @[
      ChunkData(hash: "xxh3-det-1", data: "fixed data", size: 10, chunkType: Binary)
    ]
    let signature = "fixed-signature"
    
    # Create two archives with identical data
    createNEXTER(manifest, environment, chunks, signature, path1)
    createNEXTER(manifest, environment, chunks, signature, path2)
    
    # Verify both archives are identical
    let size1 = getFileSize(path1)
    let size2 = getFileSize(path2)
    check size1 == size2

  test "Property: Archive parsing preserves all data":
    ## Verify parsing doesn't lose or corrupt data
    
    let tempDir = createTempDir("nexter_preserve_", "")
    defer: removeDir(tempDir)
    
    let archivePath = tempDir / "preserve.nexter"
    
    var manifest = createTestManifest("preserve-test", "2.3.4")
    
    let environment = "PRESERVE=yes\nDATA=intact"
    var chunks: seq[ChunkData] = @[
      ChunkData(hash: "xxh3-p-1", data: "preserved", size: 9, chunkType: Binary),
      ChunkData(hash: "xxh3-p-2", data: "data", size: 4, chunkType: Config)
    ]
    let signature = "preserved-sig"
    
    # Create and parse
    createNEXTER(manifest, environment, chunks, signature, archivePath)
    let container = parseNEXTER(archivePath)
    
    # Verify all data preserved
    check container.manifest.name == manifest.name
    check container.manifest.version == manifest.version
    check container.environment == environment
    check container.signature == signature
    check container.chunks.len == chunks.len
