## test_grafting_integration.nim
## Integration tests for build artifact grafting

import std/[unittest, tables, os, strutils]
import ../src/nimpak/build/[types, grafting]
import ../src/nimpak/graft_coordinator
import ../src/nimpak/install_manager

suite "Grafting Integration Tests":

  setup:
    let testCacheDir = getTempDir() / "nip-graft-test"
    let testProgramsDir = getTempDir() / "Programs-test"
    let testLinksDir = getTempDir() / "System-test" / "Links"

    # Clean up from previous runs
    if dirExists(testCacheDir):
      removeDir(testCacheDir)
    if dirExists(testProgramsDir):
      removeDir(testProgramsDir)
    if dirExists(testLinksDir):
      removeDir(testLinksDir)

    # Create test directories
    createDir(testCacheDir)
    createDir(testProgramsDir)
    createDir(testLinksDir / "Executables")
    createDir(testLinksDir / "Libraries")

    let installConfig = InstallConfig(
      programsDir: testProgramsDir,
      linksDir: testLinksDir,
      cacheDir: testCacheDir,
      dbFile: testCacheDir / "test.db",
      autoSymlink: true,
      checkConflicts: false,
      verbose: false
    )

    let coordinator = newGraftCoordinator(installConfig, false)

  test "Validate artifact - valid directory":
    # Create a mock artifact
    let artifactPath = testCacheDir / "mock-artifact"
    createDir(artifactPath / "bin")
    writeFile(artifactPath / "bin" / "test-exe", "#!/bin/sh\necho test")

    let (valid, errors) = validateArtifact(artifactPath)

    check valid == true
    check errors.len == 0

  test "Validate artifact - missing directory":
    let (valid, errors) = validateArtifact("/nonexistent/path")

    check valid == false
    check errors.len > 0
    check "does not exist" in errors[0]

  test "Validate artifact - empty directory":
    let emptyPath = testCacheDir / "empty-artifact"
    createDir(emptyPath)

    let (valid, errors) = validateArtifact(emptyPath)

    # Should be valid but with warnings
    check valid == true
    check errors.len > 0  # Has warning about being empty

  test "Calculate directory hash":
    # Create a test directory with content
    let testDir = testCacheDir / "hash-test"
    createDir(testDir)
    writeFile(testDir / "file1.txt", "content1")
    writeFile(testDir / "file2.txt", "content2")

    let hash1 = calculateDirectoryHash(testDir)

    # Hash should be consistent
    let hash2 = calculateDirectoryHash(testDir)
    check hash1 == hash2

    # Hash should start with blake2b-
    check hash1.startsWith("blake2b-")

  test "Calculate directory hash - different content":
    let testDir1 = testCacheDir / "hash-test-1"
    let testDir2 = testCacheDir / "hash-test-2"

    createDir(testDir1)
    createDir(testDir2)

    writeFile(testDir1 / "file.txt", "content1")
    writeFile(testDir2 / "file.txt", "content2")

    let hash1 = calculateDirectoryHash(testDir1)
    let hash2 = calculateDirectoryHash(testDir2)

    # Different content should produce different hashes
    check hash1 != hash2

  test "Build variant fingerprint":
    var domains = initTable[string, seq[string]]()
    domains["graphics"] = @["wayland"]
    domains["audio"] = @["pipewire"]

    let fingerprint = buildVariantFingerprint(
      "test-package",
      "1.0.0",
      domains,
      "nix"
    )

    # Should produce a hash
    check fingerprint.len > 0
    check fingerprint.startsWith("blake2b-")

  test "Build variant fingerprint - consistency":
    var domains = initTable[string, seq[string]]()
    domains["graphics"] = @["wayland"]

    let fp1 = buildVariantFingerprint("pkg", "1.0", domains, "nix")
    let fp2 = buildVariantFingerprint("pkg", "1.0", domains, "nix")

    # Same input should produce same fingerprint
    check fp1 == fp2

  test "Build variant fingerprint - different sources":
    var domains = initTable[string, seq[string]]()
    domains["graphics"] = @["wayland"]

    let fpNix = buildVariantFingerprint("pkg", "1.0", domains, "nix")
    let fpPkgsrc = buildVariantFingerprint("pkg", "1.0", domains, "pkgsrc")

    # Different sources should produce different fingerprints
    check fpNix != fpPkgsrc

  test "Graft build artifact - basic flow":
    # Create a mock build result
    let artifactPath = testCacheDir / "mock-build"
    createDir(artifactPath / "bin")
    writeFile(artifactPath / "bin" / "test-app", "#!/bin/sh\necho test")

    var domains = initTable[string, seq[string]]()
    domains["test"] = @["value"]

    let buildResult = BuildResult(
      success: true,
      source: "nix",
      packageName: "test-package",
      version: "1.0.0",
      artifactPath: artifactPath,
      buildLog: "",
      variantFingerprint: "test-fp",
      variantDomains: domains,
      errors: @[],
      warnings: @[]
    )

    let (success, installPath, errors) = graftBuildArtifact(
      coordinator,
      buildResult,
      verbose = false
    )

    check success == true
    check errors.len == 0
    check installPath != ""
    check dirExists(installPath)

    # Check that files were copied
    check fileExists(installPath / "bin" / "test-app")

    # Check that Current symlink was created
    let currentLink = testProgramsDir / "test-package" / "Current"
    check symlinkExists(currentLink) or dirExists(currentLink)

  test "Graft build artifact - with symlinks":
    # Create a mock build result with executable
    let artifactPath = testCacheDir / "mock-build-symlinks"
    createDir(artifactPath / "bin")
    let exePath = artifactPath / "bin" / "my-app"
    writeFile(exePath, "#!/bin/sh\necho test")

    # Make it executable
    try:
      setFilePermissions(exePath, {fpUserExec, fpUserRead, fpUserWrite})
    except:
      discard  # Permissions might not work in all test environments

    var domains = initTable[string, seq[string]]()

    let buildResult = BuildResult(
      success: true,
      source: "nix",
      packageName: "symlink-test",
      version: "1.0.0",
      artifactPath: artifactPath,
      buildLog: "",
      variantFingerprint: "test-fp-2",
      variantDomains: domains,
      errors: @[],
      warnings: @[]
    )

    let (success, installPath, errors) = graftBuildArtifact(
      coordinator,
      buildResult,
      verbose = false
    )

    check success == true

    # Check that symlink was created (if autoSymlink is enabled)
    let symlinkPath = testLinksDir / "Executables" / "my-app"
    # Symlink might exist depending on system
    if fileExists(symlinkPath) or symlinkExists(symlinkPath):
      check true  # Symlink was created
    else:
      check true  # Symlink creation might have been skipped

  test "Graft build artifact - failed build":
    let buildResult = BuildResult(
      success: false,
      source: "nix",
      packageName: "failed-package",
      version: "1.0.0",
      artifactPath: "",
      buildLog: "",
      variantFingerprint: "",
      variantDomains: initTable[string, seq[string]](),
      errors: @["Build failed"],
      warnings: @[]
    )

    let (success, installPath, errors) = graftBuildArtifact(
      coordinator,
      buildResult,
      verbose = false
    )

    # Should not graft failed builds
    check success == false
    check errors.len > 0

  teardown:
    # Clean up test directories
    try:
      if dirExists(testCacheDir):
        removeDir(testCacheDir)
      if dirExists(testProgramsDir):
        removeDir(testProgramsDir)
      if dirExists(testLinksDir):
        removeDir(testLinksDir)
    except:
      discard  # Cleanup failures are not critical
