## Tests for NPK Package Format Handler
##
## This module tests the NPK package format creation, validation, and conversion
## capabilities with CAS integration and digital signature support.

import unittest, os, times, json, options
import ../src/nimpak/[types, packages, cas]

suite "NPK Package Format Handler":
  setup:
    let testDir = getTempDir() / "nimpak_test_npk"
    let casDir = testDir / "cas"
    let sourceDir = testDir / "source"
    let outputDir = testDir / "output"

    # Clean up and create test directories
    if dirExists(testDir):
      removeDir(testDir)
    createDir(testDir)
    createDir(casDir)
    createDir(sourceDir)
    createDir(outputDir)

    # Initialize CAS manager
    let cas = initCasManager(casDir, casDir)

    # Create test files in source directory
    writeFile(sourceDir / "test.txt", "Hello, NimPak!")
    writeFile(sourceDir / "config.conf", "setting=value\nother=123")
    createDir(sourceDir / "subdir")
    writeFile(sourceDir / "subdir" / "nested.dat", "nested file content")

  teardown:
    let testDir = getTempDir() / "nimpak_test_npk"
    if dirExists(testDir):
      removeDir(testDir)

  test "create NPK package from Fragment and source directory":
    leagment = Fragment(
      id: PackageId(name: "test-package", version: "1.0.0", stream: Stable),
      source: Source(
        url: "https://example.com/test-package-1.0.0.tar.gz",
        hash: "blake2b-test123",
        hashAlgorithm: "blake2b",
        sourceMethod: Http,
        timestamp: now()
      ),
      dependencies: @[],
      buildSystem: Custom,
      metadata: PackageMetadata(
        description: "Test package for NPK format",
        license: "MIT",
        maintainer: "test@example.com",
        tags: @["test"],
        runtime: RuntimeProfile(
          libc: Musl,
          allocator: Jemalloc,
          systemdAware: false,
          reproducible: true,
          tags: @["cli"]
        )
      ),
      acul: AculCompliance(
        required: false,
        membership: "",
        attribution: "",
        buildLog: ""
      )
    )

    let result = createNpkPackage(fragment, sourceDir, cas)
    check result.isOk

    let npk = result.get()
    check npk.metadata.id.name == "test-package"
    check npk.metadata.id.version == "1.0.0"
    check npk.files.len == 3  # test.txt, config.conf, subdir/nested.dat
    check npk.manifest.totalSize > 0
    check npk.manifest.merkleRoot.len > 0
    check npk.manifest.merkleRoot.startsWith("blake2b-")

  test "validate NPK package metadata and structure":
    let fragment = Fragment(
      id: PackageId(name: "valid-package", version: "2.0.0", stream: Testing),
      source: Source(
        url: "https://example.com/valid-package.tar.gz",
        hash: "blake2b-valid123",
        hashAlgorithm: "blake2b",
        sourceMethod: Git,
        timestamp: now()
      ),
      dependencies: @[PackageId(name: "dep1", version: "1.0", stream: Stable)],
      buildSystem: CMake,
      metadata: PackageMetadata(
        description: "Valid test package",
        license: "ACUL",
        maintainer: "maintainer@example.com",
        tags: @["valid", "test"],
        runtime: RuntimeProfile(
          libc: Glibc,
          allocator: System,
          systemdAware: true,
          reproducible: true,
          tags: @["system"]
        )
      ),
      acul: AculCompliance(
        required: true,
        membership: "NexusOS-Community",
        attribution: "Original work",
        buildLog: "Build completed successfully"
      )
    )

    let createResult = createNpkPackage(fragment, sourceDir, cas)
    check createResult.isOk

    let npk = createResult.get()
    let validation = validateNpkPackage(npk)

    check validation.valid == true
    check validation.errors.len == 0
    # May have warnings about non-standard hash algorithms, but should be valid

  test "serialize NPK package to KDL format":
    let fragment = Fragment(
      id: PackageId(name: "kdl-test", version: "1.5.0", stream: Stable),
      source: Source(
        url: "https://example.com/kdl-test.tar.gz",
        hash: "blake2b-kdltest123",
        hashAlgorithm: "blake2b",
        sourceMethod: Http,
        timestamp: now()
      ),
      dependencies: @[],
      buildSystem: Meson,
      metadata: PackageMetadata(
        description: "KDL serialization test",
        license: "MIT",
        maintainer: "test@kdl.com",
        tags: @["kdl"],
        runtime: RuntimeProfile(
          libc: Musl,
          allocator: Jemalloc,
          systemdAware: false,
          reproducible: true,
          tags: @["serialization"]
        )
      ),
      acul: AculCompliance(
        required: false,
        membership: "",
        attribution: "",
        buildLog: ""
      )
    )

    let createResult = createNpkPackage(fragment, sourceDir, cas)
    check createResult.isOk

    let npk = createResult.get()
    let kdlContent = serializeToKdl(npk)

    # Check that KDL content contains expected sections
    check kdlContent.contains("package \"kdl-test\"")
    check kdlContent.contains("version \"1.5.0\"")
    check kdlContent.contains("stream \"Stable\"")
    check kdlContent.contains("source {")
    check kdlContent.contains("integrity {")
    check kdlContent.contains("runtime {")
    check kdlContent.contains("acul {")
    check kdlContent.contains("manifest {")
    check kdlContent.contains("files {")
    check kdlContent.contains("algorithm \"BLAKE2b\"")

  test "create and validate .npk.zst archive":
    let fragment = Fragment(
      id: PackageId(name: "archive-test", version: "3.0.0", stream: Dev),
      source: Source(
        url: "https://example.com/archive-test.tar.gz",
        hash: "blake2b-archive123",
        hashAlgorithm: "blake2b",
        sourceMethod: Local,
        timestamp: now()
      ),
      dependencies: @[],
      buildSystem: Autotools,
      metadata: PackageMetadata(
        description: "Archive format test",
        license: "EUPL-1.2",
        maintainer: "archive@test.com",
        tags: @["archive"],
        runtime: RuntimeProfile(
          libc: Musl,
          allocator: Internal,
          systemdAware: false,
          reproducible: true,
          tags: @["compression"]
        )
      ),
      acul: AculCompliance(
        required: true,
        membership: "NexusOS",
        attribution: "Test archive",
        buildLog: "Archive test build"
      )
    )

    let createResult = createNpkPackage(fragment, sourceDir, cas)
    check createResult.isOk

    let npk = createResult.get()
    let archivePath = outputDir / "test-archive"

    # Test .npk.zst format (default)
    let archiveResult = createNpkArchive(npk, archivePath, NpkZst)
    check archiveResult.isOk

    let finalPath = archivePath & ".npk.zst"
    check fileExists(finalPath)

    # Verify archive is not empty
    let archiveInfo = getFileInfo(finalPath)
    check archiveInfo.size > 0

  test "create uncompressed .npk.tar archive for debugging":
    let fragment = Fragment(
      id: PackageId(name: "debug-test", version: "1.0.0", stream: Dev),
      source: Source(
        url: "file:///tmp/debug-test",
        hash: "blake2b-debug123",
        hashAlgorithm: "blake2b",
        sourceMethod: Local,
        timestamp: now()
      ),
      dependencies: @[],
      buildSystem: Custom,
      metadata: PackageMetadata(
        description: "Debug archive test",
        license: "MIT",
        maintainer: "debug@test.com",
        tags: @["debug"],
        runtime: RuntimeProfile(
          libc: None,
          allocator: System,
          systemdAware: false,
          reproducible: true,
          tags: @["debug"]
        )
      ),
      acul: AculCompliance(
        required: false,
        membership: "",
        attribution: "",
        buildLog: ""
      )
    )

    let createResult = createNpkPackage(fragment, sourceDir, cas)
    check createResult.isOk

    let npk = createResult.get()
    let archivePath = outputDir / "debug-archive"

    # Test .npk.tar format (uncompressed)
    let archiveResult = createNpkArchive(npk, archivePath, NpkTar)
    check archiveResult.isOk

    let finalPath = archivePath & ".npk.tar"
    check fileExists(finalPath)

  test "extract NPK package using CAS":
    let fragment = Fragment(
      id: PackageId(name: "extract-test", version: "2.5.0", stream: Stable),
      source: Source(
        url: "https://example.com/extract-test.tar.gz",
        hash: "blake2b-extract123",
        hashAlgorithm: "blake2b",
        sourceMethod: Http,
        timestamp: now()
      ),
      dependencies: @[],
      buildSystem: Nim,
      metadata: PackageMetadata(
        description: "Extraction test package",
        license: "MIT",
        maintainer: "extract@test.com",
        tags: @["extract"],
        runtime: RuntimeProfile(
          libc: Musl,
          allocator: Jemalloc,
          systemdAware: false,
          reproducible: true,
          tags: @["extraction"]
        )
      ),
      acul: AculCompliance(
        required: false,
        membership: "",
        attribution: "",
        buildLog: ""
      )
    )

    let createResult = createNpkPackage(fragment, sourceDir, cas)
    check createResult.isOk

    let npk = createResult.get()
    let extractDir = outputDir / "extracted"

    let extractResult = extractNpkPackage(npk, extractDir, cas)
    check extractResult.isOk

    # Verify extracted files exist
    check fileExists(extractDir / "test.txt")
    check fileExists(extractDir / "config.conf")
    check fileExists(extractDir / "subdir" / "nested.dat")

    # Verify file contents
    check readFile(extractDir / "test.txt") == "Hello, NimPak!"
    check readFile(extractDir / "config.conf") == "setting=value\nother=123"
    check readFile(extractDir / "subdir" / "nested.dat") == "nested file content"

  test "validate NPK package with missing required fields":
    # Create an invalid fragment with missing required fields
    let invalidFragment = Fragment(
      id: PackageId(name: "", version: "", stream: Stable),  # Empty name and version
      source: Source(
        url: "",  # Empty URL
        hash: "",  # Empty hash
        hashAlgorithm: "blake2b",
        sourceMethod: Http,
        timestamp: now()
      ),
      dependencies: @[],
      buildSystem: Custom,
      metadata: PackageMetadata(
        description: "Invalid test package",
        license: "MIT",
        maintainer: "invalid@test.com",
        tags: @[],
        runtime: RuntimeProfile(
          libc: Musl,
          allocator: System,
          systemdAware: false,
          reproducible: false,
          tags: @[]
        )
      ),
      acul: AculCompliance(
        required: false,
        membership: "",
        attribution: "",
        buildLog: ""
      )
    )

    let createResult = createNpkPackage(invalidFragment, sourceDir, cas)
    check createResult.isOk  # Creation should succeed even with invalid metadata

    let npk = createResult.get()
    let validation = validateNpkPackage(npk)

    check validation.valid == false
    check validation.errors.len > 0

    # Check for specific validation errors
    var hasNameError = false
    var hasVersionError = false
    var hasUrlError = false
    var hasHashError = false

    for error in validation.errors:
      if error.field == "metadata.id.name":
        hasNameError = true
      elif error.field == "metadata.id.version":
        hasVersionError = true
      elif error.field == "metadata.source.url":
        hasUrlError = true
      elif error.field == "metadata.source.hash":
        hasHashError = true

    check hasNameError
    check hasVersionError
    check hasUrlError
    check hasHashError

  test "get human-readable NPK package information":
    let fragment = Fragment(
      id: PackageId(name: "info-test", version: "4.2.1", stream: LTS),
      source: Source(
        url: "https://example.com/info-test.tar.gz",
        hash: "blake2b-info123",
        hashAlgorithm: "blake2b",
        sourceMethod: Git,
        timestamp: now()
      ),
      dependencies: @[],
      buildSystem: Cargo,
      metadata: PackageMetadata(
        description: "Package info test",
        license: "Apache-2.0",
        maintainer: "info@test.com",
        tags: @["info"],
        runtime: RuntimeProfile(
          libc: Glibc,
          allocator: Jemalloc,
          systemdAware: true,
          reproducible: true,
          tags: @["information"]
        )
      ),
      acul: AculCompliance(
        required: true,
        membership: "NexusOS-Pro",
        attribution: "Info test package",
        buildLog: "Info build completed"
      )
    )

    let createResult = createNpkPackage(fragment, sourceDir, cas)
    check createResult.isOk

    let npk = createResult.get()
    let info = getNpkInfo(npk)

    check info.contains("NPK Package: info-test v4.2.1")
    check info.contains("Stream: LTS")
    check info.contains("Files: 3")
    check info.contains("Total Size:")
    check info.contains("Created:")
    check info.contains("Merkle Root: blake2b-")
    check info.contains("Signed: No")

when isMainModule:
  # Run the tests
  echo "Running NPK Package Format Handler tests..."