## NEXTER Container Installation Tests
##
## Tests for the NEXTER container installation workflow.
## Verifies atomic installation, rollback, and verification.

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

# Helper to create a test container
proc createTestContainer(name: string, version: string): NEXTERContainer =
  let buildDate = parse("2025-11-28T12:00:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'")
  return NEXTERContainer(
    manifest: NEXTERManifest(
      name: name,
      version: parseSemanticVersion(version),
      buildDate: buildDate,
      metadata: ContainerInfo(
        description: "Test container",
        license: "MIT"
      ),
      provenance: ProvenanceInfo(
        source: "https://example.com/source.tar.gz",
        sourceHash: "xxh3-source-hash",
        buildTimestamp: buildDate
      ),
      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"
      )
    ),
    environment: "PATH=/usr/bin:/bin",
    chunks: @[
      ChunkData(
        hash: "xxh3-chunk1",
        data: "chunk1 data",
        size: 11,
        chunkType: Binary
      )
    ],
    signature: "ed25519-signature",
    archivePath: ""
  )

suite "NEXTER Container Installation Tests":

  setup:
    let tempDir = createTempDir("nexter_install_test_", "")
    let storageRoot = tempDir / "storage"
    createDir(storageRoot)

  teardown:
    removeDir(tempDir)

  test "Install NEXTER container successfully":
    ## Verify container can be installed atomically
    
    let container = createTestContainer("test-container", "1.0.0")
    let archivePath = storageRoot / "test-container.nexter"
    
    # Create archive
    createNEXTER(container.manifest, container.environment, container.chunks, 
                 container.signature, archivePath)
    
    # Install container
    let result = installNEXTER(archivePath, storageRoot)
    
    check result.success
    check result.containerName == "test-container"
    check result.version == "1.0.0"
    check result.chunksInstalled >= 0
    check result.error == ""

  test "Installation creates correct directory structure":
    ## Verify installation creates all required directories and files
    
    let container = createTestContainer("struct-test", "2.0.0")
    let archivePath = storageRoot / "struct-test.nexter"
    
    createNEXTER(container.manifest, container.environment, container.chunks,
                 container.signature, archivePath)
    
    let result = installNEXTER(archivePath, storageRoot)
    
    check result.success
    check dirExists(storageRoot / "nexters" / "struct-test")
    check fileExists(storageRoot / "nexters" / "struct-test" / "manifest.kdl")
    check fileExists(storageRoot / "nexters" / "struct-test" / "environment.kdl")
    check fileExists(storageRoot / "nexters" / "struct-test" / "signature.sig")

  test "Installation fails for already installed container":
    ## Verify error when container already installed
    
    let container = createTestContainer("dup-test", "1.0.0")
    let archivePath = storageRoot / "dup-test.nexter"
    
    createNEXTER(container.manifest, container.environment, container.chunks,
                 container.signature, archivePath)
    
    # First installation should succeed
    let result1 = installNEXTER(archivePath, storageRoot)
    check result1.success
    
    # Second installation should fail
    let result2 = installNEXTER(archivePath, storageRoot)
    check not result2.success
    check "already installed" in result2.error.toLowerAscii()

  test "Check if container is installed":
    ## Verify isContainerInstalled() works correctly
    
    let container = createTestContainer("check-test", "1.0.0")
    let archivePath = storageRoot / "check-test.nexter"
    
    # Before installation
    check not isContainerInstalled("check-test", storageRoot)
    
    # Install
    createNEXTER(container.manifest, container.environment, container.chunks,
                 container.signature, archivePath)
    discard installNEXTER(archivePath, storageRoot)
    
    # After installation
    check isContainerInstalled("check-test", storageRoot)

  test "Get installed container version":
    ## Verify getInstalledContainerVersion() returns correct version
    
    let container = createTestContainer("version-test", "3.2.1")
    let archivePath = storageRoot / "version-test.nexter"
    
    createNEXTER(container.manifest, container.environment, container.chunks,
                 container.signature, archivePath)
    discard installNEXTER(archivePath, storageRoot)
    
    let version = getInstalledContainerVersion("version-test", storageRoot)
    check version.isSome
    check version.get() == "3.2.1"

  test "List installed containers":
    ## Verify listInstalledContainers() returns all installed containers
    
    # Install multiple containers
    for i in 1..3:
      let container = createTestContainer("list-test-" & $i, "1.0.0")
      let archivePath = storageRoot / ("list-test-" & $i & ".nexter")
      createNEXTER(container.manifest, container.environment, container.chunks,
                   container.signature, archivePath)
      discard installNEXTER(archivePath, storageRoot)
    
    let containers = listInstalledContainers(storageRoot)
    check containers.len == 3
    check "list-test-1" in containers
    check "list-test-2" in containers
    check "list-test-3" in containers

  test "Verify valid container installation":
    ## Verify verifyContainerInstallation() detects valid installations
    
    let container = createTestContainer("verify-test", "1.0.0")
    let archivePath = storageRoot / "verify-test.nexter"
    
    createNEXTER(container.manifest, container.environment, container.chunks,
                 container.signature, archivePath)
    discard installNEXTER(archivePath, storageRoot)
    
    check verifyContainerInstallation("verify-test", storageRoot)

  test "Verify fails for non-existent container":
    ## Verify verifyContainerInstallation() fails for missing containers
    
    check not verifyContainerInstallation("nonexistent", storageRoot)

  test "Format installation result - success":
    ## Verify formatting of successful installation result
    
    let result = ContainerInstallResult(
      success: true,
      containerName: "test",
      version: "1.0.0",
      installPath: "/path/to/test",
      chunksInstalled: 5,
      error: ""
    )
    
    let formatted = $result
    check "✅" in formatted
    check "test" in formatted
    check "1.0.0" in formatted

  test "Format installation result - failure":
    ## Verify formatting of failed installation result
    
    let result = ContainerInstallResult(
      success: false,
      containerName: "",
      version: "",
      installPath: "",
      chunksInstalled: 0,
      error: "Test error"
    )
    
    let formatted = $result
    check "❌" in formatted
    check "Test error" in formatted

suite "NEXTER Container Installation Property Tests":

  test "Property: Successful installation has ALL artifacts":
    ## Verify successful installation creates all required artifacts
    
    let tempDir = createTempDir("nexter_prop_", "")
    defer: removeDir(tempDir)
    
    let container = createTestContainer("prop-test-1", "1.0.0")
    let archivePath = tempDir / "prop-test-1.nexter"
    
    createNEXTER(container.manifest, container.environment, container.chunks,
                 container.signature, archivePath)
    
    let result = installNEXTER(archivePath, tempDir / "storage")
    
    if result.success:
      check dirExists(tempDir / "storage" / "nexters" / "prop-test-1")
      check fileExists(tempDir / "storage" / "nexters" / "prop-test-1" / "manifest.kdl")
      check fileExists(tempDir / "storage" / "nexters" / "prop-test-1" / "environment.kdl")
      check fileExists(tempDir / "storage" / "nexters" / "prop-test-1" / "signature.sig")

  test "Property: Failed installation has NO artifacts (complete rollback)":
    ## Verify failed installation leaves no partial state
    
    let tempDir = createTempDir("nexter_prop_", "")
    defer: removeDir(tempDir)
    
    # Try to install to non-existent parent directory
    let result = installNEXTER("/nonexistent/path/container.nexter", tempDir / "storage")
    
    check not result.success
    # Verify no partial state was created
    check not dirExists(tempDir / "storage" / "nexters")

  test "Property: Multiple containers share CAS chunks":
    ## Verify CAS deduplication works across multiple containers
    
    let tempDir = createTempDir("nexter_prop_", "")
    defer: removeDir(tempDir)
    let storageRoot = tempDir / "storage"
    createDir(storageRoot)
    
    # Create two containers with same chunk
    let sharedChunk = ChunkData(
      hash: "xxh3-shared",
      data: "shared chunk data",
      size: 16,
      chunkType: Binary
    )
    
    var container1 = createTestContainer("shared-1", "1.0.0")
    container1.chunks = @[sharedChunk]
    
    var container2 = createTestContainer("shared-2", "1.0.0")
    container2.chunks = @[sharedChunk]
    
    let archive1 = storageRoot / "shared-1.nexter"
    let archive2 = storageRoot / "shared-2.nexter"
    
    createNEXTER(container1.manifest, container1.environment, container1.chunks,
                 container1.signature, archive1)
    createNEXTER(container2.manifest, container2.environment, container2.chunks,
                 container2.signature, archive2)
    
    let result1 = installNEXTER(archive1, storageRoot)
    let result2 = installNEXTER(archive2, storageRoot)
    
    check result1.success
    check result2.success
    
    # Verify both containers reference the same chunk
    let refsPath1 = storageRoot / "cas" / "refs" / "nexters" / "shared-1.refs"
    let refsPath2 = storageRoot / "cas" / "refs" / "nexters" / "shared-2.refs"
    
    if fileExists(refsPath1) and fileExists(refsPath2):
      let refs1 = readFile(refsPath1).split('\n')
      let refs2 = readFile(refsPath2).split('\n')
      check refs1[0] == refs2[0]  # Same chunk hash

  test "Property: Installation preserves manifest integrity":
    ## Verify manifest is preserved exactly through installation
    
    let tempDir = createTempDir("nexter_prop_", "")
    defer: removeDir(tempDir)
    
    let container = createTestContainer("integrity-test", "2.5.3")
    let archivePath = tempDir / "integrity-test.nexter"
    
    createNEXTER(container.manifest, container.environment, container.chunks,
                 container.signature, archivePath)
    
    let result = installNEXTER(archivePath, tempDir / "storage")
    
    if result.success:
      let manifestPath = tempDir / "storage" / "nexters" / "integrity-test" / "manifest.kdl"
      check fileExists(manifestPath)
      let manifestContent = readFile(manifestPath)
      check "integrity-test" in manifestContent
      check "2.5.3" in manifestContent
