# tests/test_dependency.nim
# Unit tests for dependency resolution system

import unittest, tables, sets
import ../src/nimpak/[dependency, types, errors]

suite "Dependency Resolution Tests":

  setup:
    # Create test fragments
    let pkgA = PackageId(name: "pkgA", version: "1.0", stream: Stable)
    let pkgB = PackageId(name: "pkgB", version: "1.0", stream: Stable)
    let pkgC = PackageId(name: "pkgC", version: "1.0", stream: Stable)
    let pkgD = PackageId(name: "pkgD", version: "1.0", stream: Stable)

    let fragmentA = Fragment(
      id: pkgA,
      dependencies: @[pkgB, pkgC],
      source: Source(url: "test", hash: "test", hashAlgorithm: "blake2b", method: Http),
      buildSystem: CMake,
      metadata: PackageMetadata(description: "Test package A"),
      acul: AculCompliance(required: false)
    )

    let fragmentB = Fragment(
      id: pkgB,
      dependencies: @[pkgD],
      source: Source(url: "test", hash: "test", hashAlgorithm: "blake2b", method: Http),
      buildSystem: CMake,
      metadata: PackageMetadata(description: "Test package B"),
      acul: AculCompliance(required: false)
    )

    let fragmentC = Fragment(
      id: pkgC,
      dependencies: @[],
      source: Source(url: "test", hash: "test", hashAlgorithm: "blake2b", method: Http),
      buildSystem: CMake,
      metadata: PackageMetadata(description: "Test package C"),
      acul: AculCompliance(required: false)
    )

    let fragmentD = Fragment(
      id: pkgD,
      dependencies: @[],
      source: Source(url: "test", hash: "test", hashAlgorithm: "blake2b", method: Http),
      buildSystem: CMake,
      metadata: PackageMetadata(description: "Test package D"),
      acul: AculCompliance(required: false)
    )

    var fragments = initTable[PackageId, Fragment]()
    fragments[pkgA] = fragmentA
    fragments[pkgB] = fragmentB
    fragments[pkgC] = fragmentC
    fragments[pkgD] = fragmentD

  test "Simple dependency resolution":
    let result = resolveDependencies(pkgA, fragments)
    check result.isOk

    let installOrder = result.get()
    check installOrder.packages.len == 4
    check installOrder.totalSteps == 4

    # Dependencies should be installed before dependents
    let pkgIndices = installOrder.packages.mapIt((it.name, installOrder.packages.find(it)))
    check pkgIndices.filterIt(it[0] == "pkgD")[0][1] < pkgIndices.filterIt(it[0] == "pkgB")[0][1]
    check pkgIndices.filterIt(it[0] == "pkgB")[0][1] < pkgIndices.filterIt(it[0] == "pkgA")[0][1]
    check pkgIndices.filterIt(it[0] == "pkgC")[0][1] < pkgIndices.filterIt(it[0] == "pkgA")[0][1]

  test "Missing dependency detection":
    let pkgMissing = PackageId(name: "missing", version: "1.0", stream: Stable)
    let fragmentMissing = Fragment(
      id: pkgMissing,
      dependencies: @[PackageId(name: "nonexistent", version: "1.0", stream: Stable)],
      source: Source(url: "test", hash: "test", hashAlgorithm: "blake2b", method: Http),
      buildSystem: CMake,
      metadata: PackageMetadata(description: "Test package with missing dep"),
      acul: AculCompliance(required: false)
    )

    var testFragments = fragments
    testFragments[pkgMissing] = fragmentMissing

    let result = resolveDependencies(pkgMissing, testFragments)
    check result.isErr
    check result.error.code == PackageNotFound
    check result.error.missingDependencies.len > 0

  test "Circular dependency detection":
    # Create circular dependency: E -> F -> E
    let pkgE = PackageId(name: "pkgE", version: "1.0", stream: Stable)
    let pkgF = PackageId(name: "pkgF", version: "1.0", stream: Stable)

    let fragmentE = Fragment(
      id: pkgE,
      dependencies: @[pkgF],
      source: Source(url: "test", hash: "test", hashAlgorithm: "blake2b", method: Http),
      buildSystem: CMake,
      metadata: PackageMetadata(description: "Test package E"),
      acul: AculCompliance(required: false)
    )

    let fragmentF = Fragment(
      id: pkgF,
      dependencies: @[pkgE],
      source: Source(url: "test", hash: "test", hashAlgorithm: "blake2b", method: Http),
      buildSystem: CMake,
      metadata: PackageMetadata(description: "Test package F"),
      acul: AculCompliance(required: false)
    )

    var testFragments = initTable[PackageId, Fragment]()
    testFragments[pkgE] = fragmentE
    testFragments[pkgF] = fragmentF

    let result = resolveDependencies(pkgE, testFragments)
    check result.isErr
    check result.error.code == DependencyConflict
    check result.error.cyclicDependencies.len > 0

  test "Dependency tree generation":
    let treeResult = getDependencyTree(pkgA, fragments)
    check treeResult.isOk

    let tree = treeResult.get()
    check "pkgA" in tree
    check "pkgB" in tree
    check "pkgC" in tree
    check "pkgD" in tree

  test "Version constraint resolution stub":
    let result = resolveVersionConstraint("testpkg", "~> 1.0")
    check result.isOk
    check result.get().name == "testpkg"

  test "Dependency validation":
    let result = validateDependencies(fragments)
    check result.isOk

    # Test with missing dependency
    var invalidFragments = fragments
    let invalidPkg = PackageId(name: "invalid", version: "1.0", stream: Stable)
    invalidFragments[invalidPkg] = Fragment(
      id: invalidPkg,
      dependencies: @[PackageId(name: "missing", version: "1.0", stream: Stable)],
      source: Source(url: "test", hash: "test", hashAlgorithm: "blake2b", method: Http),
      buildSystem: CMake,
      metadata: PackageMetadata(description: "Invalid package"),
      acul: AculCompliance(required: false)
    )

    let invalidResult = validateDependencies(invalidFragments)
    check invalidResult.isErr
    check invalidResult.error.missingDependencies.len > 0

  test "Error formatting":
    let error = DependencyError(
      code: DependencyConflict,
      msg: "Test error",
      missingDependencies: @[pkgA],
      cyclicDependencies: @[pkgB],
      conflictingPackages: @[pkgC]
    )

    let formatted = formatDependencyError(error)
    check "Test error" in formatted
    check "Missing Dependencies" in formatted
    check "Circular Dependencies" in formatted
    check "Conflicting Packages" in formatted