## Tests for NPK Installation Workflow
##
## **Feature:** 01-nip-unified-storage-and-formats
## **Task:** 13. Implement NPK installation workflow
## **Requirements:** 3.5, 11.1, 11.2
##
## **Test Strategy:**
## - Test atomic installation workflow
## - Test chunk extraction to CAS with deduplication
## - Test manifest creation
## - Test reference tracking
## - Test rollback on failure
## - Test installation verification

import std/[unittest, os, times, json, options, strutils]
import nip/[npk, npk_manifest, npk_installer, manifest_parser, cas]

# ============================================================================
# Test Fixtures
# ============================================================================

proc createTestPackage(name: string, version: string, testDir: string): string =
  ## Create a test NPK package and return its path
  let manifest = NPKManifest(
    name: name,
    version: parseSemanticVersion(version),
    buildDate: now(),

    metadata: PackageInfo(
      description: "Test package for installation",
      license: "MIT"
    ),

    provenance: ProvenanceInfo(
      source: "https://example.com/test.tar.gz",
      sourceHash: "xxh3-test123",
      buildTimestamp: now()
    ),

    buildConfig: BuildConfiguration(
      compilerVersion: "gcc-13.2.0",
      targetArchitecture: "x86_64",
      libc: "musl",
      allocator: "default",
      buildSystem: "cmake"
    ),

    casChunks: @[
      ChunkReference(
        hash: "xxh3-chunk1",
        size: 1024,
        chunkType: Binary,
        path: "bin/test"
      ),
      ChunkReference(
        hash: "xxh3-chunk2",
        size: 2048,
        chunkType: Library,
        path: "lib/libtest.so"
      )
    ],

    install: InstallPaths(
      programsPath: "/Programs/" & name & "/" & version,
      binPath: "/Programs/" & name & "/" & version & "/bin",
      libPath: "/Programs/" & name & "/" & version & "/lib",
      sharePath: "/Programs/" & name & "/" & version & "/share",
      etcPath: "/Programs/" & name & "/" & version & "/etc"
    ),

    system: SystemIntegration(),

    buildHash: "xxh3-build123",

    signature: SignatureInfo(
      algorithm: "ed25519",
      keyId: "test-key",
      signature: "test-signature"
    )
  )

  let chunks = @[
    ChunkData(
      hash: "xxh3-chunk1",
      data: "test chunk data 1",
      size: 17,
      chunkType: Binary
    ),
    ChunkData(
      hash: "xxh3-chunk2",
      data: "test chunk data 2 - larger content",
      size: 35,
      chunkType: Library
    )
  ]

  let metadata = %* {
    "package": name,
    "version": version,
    "created": "2025-11-20T14:00:00Z"
  }

  let signature = "test-signature-data"
  let pkgPath = testDir / (name & ".npk")

  discard createNPK(manifest, chunks, metadata, signature, pkgPath)
  return pkgPath

# ============================================================================
# Installation Tests
# ============================================================================

suite "NPK Installation Workflow (Task 13)":

  setup:
    let testDir = getTempDir() / "npk-install-test-" & $getTime().toUnix()
    let storageRoot = testDir / "storage"
    createDir(testDir)
    createDir(storageRoot)

  teardown:
    if dirExists(testDir):
      removeDir(testDir)

  test "Install NPK package successfully":
    ## **Requirement 3.5:** Extract chunks to CAS and create manifest
    ## **Requirement 11.1:** Atomic installation

    let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
    let result = installNPK(pkgPath, storageRoot)

    # Verify installation succeeded
    check result.success
    check result.packageName == "test-pkg"
    check result.version == "1.0.0"
    check result.chunksInstalled == 2
    check result.error == ""

    # Verify manifest was created
    let manifestPath = storageRoot / "npks" / "test-pkg.kdl"
    check fileExists(manifestPath)

    # Verify references were created
    let refsPath = storageRoot / "cas" / "refs" / "npks" / "test-pkg.refs"
    check fileExists(refsPath)

    # Verify chunks were extracted to CAS
    let casDir = storageRoot / "cas" / "chunks"
    check dirExists(casDir)

  test "Install creates correct directory structure":
    ## Verify all required directories are created

    let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
    discard installNPK(pkgPath, storageRoot)

    # Check directory structure
    check dirExists(storageRoot / "npks")
    check dirExists(storageRoot / "cas")
    check dirExists(storageRoot / "cas" / "chunks")
    check dirExists(storageRoot / "cas" / "refs")
    check dirExists(storageRoot / "cas" / "refs" / "npks")

  test "Install fails for already installed package":
    ## Verify duplicate installation is prevented

    let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)

    # First installation should succeed
    let result1 = installNPK(pkgPath, storageRoot)
    check result1.success

    # Second installation should fail
    let result2 = installNPK(pkgPath, storageRoot)
    check not result2.success
    check "already installed" in result2.error.toLowerAscii()

  test "Install creates valid manifest file":
    ## Verify manifest can be parsed after installation

    let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
    discard installNPK(pkgPath, storageRoot)

    let manifestPath = storageRoot / "npks" / "test-pkg.kdl"
    let manifestKdl = readFile(manifestPath)

    # Parse manifest to verify it's valid
    let manifest = parseNPKManifest(manifestKdl)
    check manifest.name == "test-pkg"
    check manifest.version.major == 1
    check manifest.version.minor == 0
    check manifest.version.patch == 0

  test "Install creates valid references file":
    ## Verify references file contains correct information

    let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
    discard installNPK(pkgPath, storageRoot)

    let refsPath = storageRoot / "cas" / "refs" / "npks" / "test-pkg.refs"
    let refsContent = readFile(refsPath)

    check "test-pkg" in refsContent
    check "1.0.0" in refsContent
    check "chunks:" in refsContent

# ============================================================================
# Deduplication Tests
# ============================================================================

suite "NPK Installation Deduplication (Task 13)":

  setup:
    let testDir = getTempDir() / "npk-dedup-test-" & $getTime().toUnix()
    let storageRoot = testDir / "storage"
    createDir(testDir)
    createDir(storageRoot)

  teardown:
    if dirExists(testDir):
      removeDir(testDir)

  test "Multiple packages share chunks via CAS":
    ## Verify CAS deduplication works across packages

    # Create two packages with overlapping chunks
    let pkg1Path = createTestPackage("pkg1", "1.0.0", testDir)
    let pkg2Path = createTestPackage("pkg2", "1.0.0", testDir)

    # Install both packages
    let result1 = installNPK(pkg1Path, storageRoot)
    let result2 = installNPK(pkg2Path, storageRoot)

    check result1.success
    check result2.success

    # Both packages should be installed
    check isInstalled("pkg1", storageRoot)
    check isInstalled("pkg2", storageRoot)

# ============================================================================
# Query Function Tests
# ============================================================================

suite "NPK Installation Queries (Task 13)":

  setup:
    let testDir = getTempDir() / "npk-query-test-" & $getTime().toUnix()
    let storageRoot = testDir / "storage"
    createDir(testDir)
    createDir(storageRoot)

  teardown:
    if dirExists(testDir):
      removeDir(testDir)

  test "Check if package is installed":
    ## Test isInstalled() function

    check not isInstalled("test-pkg", storageRoot)

    let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
    discard installNPK(pkgPath, storageRoot)

    check isInstalled("test-pkg", storageRoot)

  test "Get installed package version":
    ## Test getInstalledVersion() function

    let version1 = getInstalledVersion("test-pkg", storageRoot)
    check version1.isNone

    let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
    discard installNPK(pkgPath, storageRoot)

    let version2 = getInstalledVersion("test-pkg", storageRoot)
    check version2.isSome
    check version2.get() == "1.0.0"

  test "List installed packages":
    ## Test listInstalledPackages() function

    let list1 = listInstalledPackages(storageRoot)
    check list1.len == 0

    let pkg1Path = createTestPackage("pkg1", "1.0.0", testDir)
    let pkg2Path = createTestPackage("pkg2", "2.0.0", testDir)

    discard installNPK(pkg1Path, storageRoot)
    discard installNPK(pkg2Path, storageRoot)

    let list2 = listInstalledPackages(storageRoot)
    check list2.len == 2
    check "pkg1" in list2
    check "pkg2" in list2

# ============================================================================
# Verification Tests
# ============================================================================

suite "NPK Installation Verification (Task 13)":

  setup:
    let testDir = getTempDir() / "npk-verify-test-" & $getTime().toUnix()
    let storageRoot = testDir / "storage"
    createDir(testDir)
    createDir(storageRoot)

  teardown:
    if dirExists(testDir):
      removeDir(testDir)

  test "Verify valid installation":
    ## Test verifyInstallation() function

    let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
    discard installNPK(pkgPath, storageRoot)

    let isValid = verifyInstallation("test-pkg", storageRoot)
    check isValid

  test "Verify fails for non-existent package":
    ## Verify detection of missing package

    let isValid = verifyInstallation("nonexistent", storageRoot)
    check not isValid

  test "Verify fails for corrupted installation":
    ## Verify detection of corrupted installation

    let pkgPath = createTestPackage("test-pkg", "1.0.0", testDir)
    discard installNPK(pkgPath, storageRoot)

    # Remove the references file to simulate corruption
    let refsPath = storageRoot / "cas" / "refs" / "npks" / "test-pkg.refs"
    removeFile(refsPath)

    # Verification should fail
    let isValid = verifyInstallation("test-pkg", storageRoot)
    check not isValid

# ============================================================================
# Rollback Tests
# ============================================================================

suite "NPK Installation Rollback (Task 13)":

  setup:
    let testDir = getTempDir() / "npk-rollback-test-" & $getTime().toUnix()
    let storageRoot = testDir / "storage"
    createDir(testDir)
    createDir(storageRoot)

  teardown:
    if dirExists(testDir):
      removeDir(testDir)

  test "Rollback on installation failure":
    ## **Requirement 11.2:** Rollback to previous state on failure

    # Create invalid package path
    let invalidPath = testDir / "nonexistent.npk"

    # Installation should fail
    let result = installNPK(invalidPath, storageRoot)
    check not result.success

    # Verify no artifacts left behind
    let manifestPath = storageRoot / "npks" / "test-pkg.kdl"
    check not fileExists(manifestPath)

# ============================================================================
# Utility Function Tests
# ============================================================================

suite "NPK Installation Utilities (Task 13)":

  test "Format install result - success":
    let result = InstallResult(
      success: true,
      packageName: "test-pkg",
      version: "1.0.0",
      installPath: "/path/to/manifest.kdl",
      chunksInstalled: 5,
      error: ""
    )

    let str = $result
    check str.contains("Successfully installed")
    check str.contains("test-pkg")
    check str.contains("1.0.0")
    check str.contains("5")

  test "Format install result - failure":
    let result = InstallResult(
      success: false,
      packageName: "test-pkg",
      version: "",
      installPath: "",
      chunksInstalled: 0,
      error: "Package not found"
    )

    let str = $result
    check str.contains("Failed")
    check str.contains("test-pkg")
    check str.contains("Package not found")
