## test_graft_coordinator.nim
## Unit tests for GraftCoordinator
## Tests package spec parsing, source detection, and adapter selection

import std/[unittest, os, json, tables, strutils]
import ../src/nimpak/graft_coordinator
import ../src/nimpak/install_manager
import ../src/nimpak/config

suite "GraftCoordinator - Package Spec Parsing":

  test "Parse package spec - simple name":
    let (source, name) = parsePackageSpec("firefox")

    check source == Auto
    check name == "firefox"

  test "Parse package spec - nix source":
    let (source, name) = parsePackageSpec("nix:firefox")

    check source == Nix
    check name == "firefox"

  test "Parse package spec - pkgsrc source":
    let (source, name) = parsePackageSpec("pkgsrc:vim")

    check source == PKGSRC
    check name == "vim"

  test "Parse package spec - pacman source":
    let (source, name) = parsePackageSpec("pacman:htop")

    check source == Pacman
    check name == "htop"

  test "Parse package spec - uppercase source":
    let (source, name) = parsePackageSpec("NIX:firefox")

    check source == Nix
    check name == "firefox"

  test "Parse package spec - mixed case source":
    let (source, name) = parsePackageSpec("PkgSrc:vim")

    check source == PKGSRC
    check name == "vim"

  test "Parse package spec - package name with hyphens":
    let (source, name) = parsePackageSpec("nix:my-package-name")

    check source == Nix
    check name == "my-package-name"

  test "Parse package spec - package name with underscores":
    let (source, name) = parsePackageSpec("pkgsrc:my_package_name")

    check source == PKGSRC
    check name == "my_package_name"

  test "Parse package spec - package name with numbers":
    let (source, name) = parsePackageSpec("nix:package123")

    check source == Nix
    check name == "package123"

  test "Parse package spec - invalid source defaults to auto":
    let (source, name) = parsePackageSpec("invalid:firefox")

    check source == Auto
    check name == "firefox"

  test "Parse package spec - multiple colons uses first":
    let (source, name) = parsePackageSpec("nix:package:with:colons")

    check source == Nix
    check name == "package:with:colons"

  test "Parse package spec - empty name":
    let (source, name) = parsePackageSpec("nix:")

    check source == Nix
    check name == ""

  test "Parse package spec - colon only":
    let (source, name) = parsePackageSpec(":")

    check source == Auto
    check name == ""

suite "GraftCoordinator - Source Detection":

  setup:
    let testCacheDir = getTempDir() / "nip-graft-coord-test"
    let testProgramsDir = getTempDir() / "Programs-coord-test"
    let testLinksDir = getTempDir() / "System-coord-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 "Detect source - returns valid source":
    # Test with aage that might exist
    let source = coordinator.detectSource("hello")

    # Should return one of the valid sources
    check source in [Nix, PKGSRC, Pacman]

  test "Detect source - consistency":
    # Same package should return same source
    let source1 = coordinator.detectSource("test-package")
    let source2 = coordinator.detectSource("test-package")

    check source1 == source2

  test "Detect source - different packages":
    # Different packages might have different sources
    let source1 = coordinator.detectSource("package-a")
    let source2 = coordinator.detectSource("package-b")

    # Both should be valid sources
    check source1 in [Nix, PKGSRC, Pacman]
    check source2 in [Nix, PKGSRC, Pacman]

  test "Detect source - priority order":
    # Detection should follow priority: Nix > PKGSRC > Pacman
    # If Nix is available, it should be preferred
    let source = coordinator.detectSource("common-package")

    # Should return a valid source
    check source in [Nix, PKGSRC, Pacman]

  test "Detect source - empty package name":
    # Should handle empty package name gracefully
    let source = coordinator.detectSource("")

    # Should still return a valid source (defaults to Nix)
    check source in [Nix, PKGSRC, Pacman]

  test "Detect source - package with special characters":
    let source = coordinator.detectSource("package-with-hyphens")

    check source in [Nix, PKGSRC, Pacman]

  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

suite "GraftCoordinator - Adapter Selection":

  setup:
    let testCacheDir = getTempDir() / "nip-graft-adapt-test"
    let testProgramsDir = getTempDir() / "Programs-adapt-test"
    let testLinksDir = getTempDir() / "System-adapt-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 "Coordinator has Nix adapter":
    check coordinator.nixAdapter != nil

  test "Coordinator has PKGSRC adapter":
    check coordinator.pkgsrcAdapter != nil

  test "Graft with Auto source - uses detection":
    # This will fail because we don't have actual packages
    # But it tests that the auto-detection path is taken
    let result = coordinator.graft("nonexistent-package", Auto)

    # Should attempt to graft (will fail, but that's expected)
    check result.packageName == "nonexistent-package"
    check result.source in ["nix", "pkgsrc", "pacman"]

  test "Graft with Nix source - uses Nix adapter":
    let result = coordinator.graft("nonexistent-package", Nix)

    check result.packageName == "nonexistent-package"
    check result.source == "nix"
    # Will fail because package doesn't exist
    check result.success == false

  test "Graft with PKGSRC source - uses PKGSRC adapter":
    let result = coordinator.graft("nonexistent-package", PKGSRC)

    check result.packageName == "nonexistent-package"
    check result.source == "pkgsrc"
    # Will fail because package doesn't exist
    check result.success == false

  test "Graft with Pacman source - not implemented":
    let result = coordinator.graft("nonexistent-package", Pacman)

    check result.packageName == "nonexistent-package"
    check result.source == "pacman"
    check result.success == false
    check result.errors.len > 0
    check "not yet implemented" in result.errors[0].toLower()

  test "Parse and graft - nix:package":
    let (source, name) = parsePackageSpec("nix:test-package")
    let result = coordinator.graft(name, source)

    check result.packageName == "test-package"
    check result.source == "nix"

  test "Parse and graft - pkgsrc:package":
    let (source, name) = parsePackageSpec("pkgsrc:test-package")
    let result = coordinator.graft(name, source)

    check result.packageName == "test-package"
    check result.source == "pkgsrc"

  test "Parse and graft - auto detection":
    let (source, name) = parsePackageSpec("test-package")
    let result = coordinator.graft(name, source)

    check result.packageName == "test-package"
    check result.source in ["nix", "pkgsrc", "pacman"]

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

    let verboseCoordinator = newGraftCoordinator(verboseConfig, true)

    check verboseCoordinator.verbose == true

  test "List installed packages - empty":
    let packages = coordinator.list()

    # Should return empty list for new coordinator
    check packages.len == 0

  test "List installed packages - filtered by source":
    let nixPackages = coordinator.list("nix")
    let pkgsrcPackages = coordinator.list("pkgsrc")

    # Should return empty lists for new coordinator
    check nixPackages.len == 0
    check pkgsrcPackages.len == 0

  test "Check if package is installed - not installed":
    let installed = coordinator.isInstalled("nonexistent-package")

    check installed == false

  test "Get status - returns JSON":
    let status = coordinator.status()

    check status != nil
    check status.kind == JObject

  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

suite "GraftCoordinator - Integration":

  setup:
    let testCacheDir = getTempDir() / "nip-graft-integ-test"
    let testProgramsDir = getTempDir() / "Programs-integ-test"
    let testLinksDir = getTempDir() / "System-integ-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 "Full workflow - parse, detect, graft":
    # Parse package spec
    let (source, name) = parsePackageSpec("nix:test-package")

    check source == Nix
    check name == "test-package"

    # Detect source (should use parsed source)
    let detectedSource = if source == Auto:
      coordinator.detectSource(name)
    else:
      source

    check detectedSource == Nix

    # Attempt to graft (will fail, but tests the flow)
    let result = coordinator.graft(name, detectedSource)

    check result.packageName == "test-package"
    check result.source == "nix"

  test "Full workflow - auto detection":
    let (source, name) = parsePackageSpec("test-package")

    check source == Auto

    # Auto-detect source
    let detectedSource = coordinator.detectSource(name)

    check detectedSource in [Nix, PKGSRC, Pacman]

    # Graft with detected source
    let result = coordinator.graft(name, detectedSource)

    check result.packageName == "test-package"

  test "Error handling - invalid package":
    let result = coordinator.graft("", Nix)

    # Should handle empty package name
    check result.success == false

  test "Error handling - nonexistent package":
    let result = coordinator.graft("definitely-does-not-exist-12345", Nix)

    # Should fail gracefully
    check result.success == false
    check result.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
