## Unit Tests for Solver Data Structures
##
## Tests for the PubGrub solver type system including Terms,
## Incompatibilities, Constraints, and Assignments.

import std/[unittest, options, strutils]
import ../src/nip/resolver/solver_types
import ../src/nip/resolver/variant_types
import ../src/nip/manifest_parser

suite "Solver Types Tests":
  
  test "Create basic constraint":
    ## Test creating a simple version constraint
    
    let constraint = Constraint(
      versionRange: VersionConstraint(
        operator: OpGreaterEq,
        version: SemanticVersion(major: 1, minor: 2, patch: 0)
      ),
      variantReq: newVariantProfile(),
      isNegative: false
    )
    
    check not constraint.isNegative
    check not constraint.isEmpty
    check not constraint.isAny
  
  test "Create 'any' constraint":
    ## Test creating a constraint that accepts any version
    
    let constraint = Constraint(
      versionRange: VersionConstraint(operator: OpAny),
      variantReq: newVariantProfile(),
      isNegative: false
    )
    
    check constraint.isAny
    check not constraint.isEmpty
  
  test "Create empty constraint":
    ## Test creating an empty constraint (negated 'any')
    
    let constraint = Constraint(
      versionRange: VersionConstraint(operator: OpAny),
      variantReq: newVariantProfile(),
      isNegative: true
    )
    
    check constraint.isEmpty
    check not constraint.isAny
  
  test "Create positive term":
    ## Test creating a positive term
    
    let term = Term(
      package: "nginx",
      constraint: Constraint(
        versionRange: VersionConstraint(
          operator: OpGreaterEq,
          version: SemanticVersion(major: 1, minor: 20, patch: 0)
        ),
        variantReq: newVariantProfile(),
        isNegative: false
      )
    )
    
    check term.isPositive
    check not term.isNegative
    check term.package == "nginx"
  
  test "Create negative term":
    ## Test creating a negative term
    
    let term = Term(
      package: "nginx",
      constraint: Constraint(
        versionRange: VersionConstraint(
          operator: OpGreaterEq,
          version: SemanticVersion(major: 1, minor: 20, patch: 0)
        ),
        variantReq: newVariantProfile(),
        isNegative: true
      )
    )
    
    check term.isNegative
    check not term.isPositive
  
  test "Negate term":
    ## Test negating a term
    
    let positiveTerm = Term(
      package: "nginx",
      constraint: Constraint(
        versionRange: VersionConstraint(
          operator: OpGreaterEq,
          version: SemanticVersion(major: 1, minor: 20, patch: 0)
        ),
        variantReq: newVariantProfile(),
        isNegative: false
      )
    )
    
    let negativeTerm = negate(positiveTerm)
    
    check positiveTerm.isPositive
    check negativeTerm.isNegative
    check negativeTerm.package == positiveTerm.package
    
    # Double negation should give back positive
    let doubleNegated = negate(negativeTerm)
    check doubleNegated.isPositive
  
  test "Create dependency incompatibility":
    ## Test creating an incompatibility from a dependency
    
    let incomp = createDependencyIncompatibility(
      dependent = "nginx",
      dependentVersion = SemanticVersion(major: 1, minor: 24, patch: 0),
      dependency = "zlib",
      dependencyConstraint = Constraint(
        versionRange: VersionConstraint(
          operator: OpGreaterEq,
          version: SemanticVersion(major: 1, minor: 2, patch: 0)
        ),
        variantReq: newVariantProfile(),
        isNegative: false
      )
    )
    
    check incomp.terms.len == 2
    check incomp.cause == Dependency
    check incomp.fromPackage.isSome
    check incomp.fromPackage.get() == "nginx"
    
    # First term should be positive (nginx = 1.24.0)
    check incomp.terms[0].isPositive
    check incomp.terms[0].package == "nginx"
    
    # Second term should be negative (NOT zlib >= 1.2.0)
    check incomp.terms[1].isNegative
    check incomp.terms[1].package == "zlib"
  
  test "Create root incompatibility":
    ## Test creating an incompatibility from a root requirement
    
    let incomp = createRootIncompatibility(
      package = "nginx",
      constraint = Constraint(
        versionRange: VersionConstraint(
          operator: OpGreaterEq,
          version: SemanticVersion(major: 1, minor: 20, patch: 0)
        ),
        variantReq: newVariantProfile(),
        isNegative: false
      )
    )
    
    check incomp.terms.len == 1
    check incomp.cause == Root
    check incomp.fromPackage.isSome
    check incomp.fromPackage.get() == "nginx"
    
    # Term should be negative (NOT nginx >= 1.20.0)
    # This means "it's incompatible to NOT have nginx >= 1.20.0"
    check incomp.terms[0].isNegative
    check incomp.terms[0].package == "nginx"
  
  test "Constraint satisfies version":
    ## Test checking if a version satisfies a constraint
    
    let constraint = Constraint(
      versionRange: VersionConstraint(
        operator: OpGreaterEq,
        version: SemanticVersion(major: 1, minor: 20, patch: 0)
      ),
      variantReq: newVariantProfile(),
      isNegative: false
    )
    
    let version1 = SemanticVersion(major: 1, minor: 24, patch: 0)
    let version2 = SemanticVersion(major: 1, minor: 19, patch: 0)
    let variant = newVariantProfile()
    
    check satisfies(version1, variant, constraint)
    check not satisfies(version2, variant, constraint)
  
  test "Constraint satisfies variant":
    ## Test checking if a variant satisfies a constraint
    
    var variantReq = newVariantProfile()
    variantReq.addFlag("optimization", "lto")
    variantReq.calculateHash()
    
    let constraint = Constraint(
      versionRange: VersionConstraint(operator: OpAny),
      variantReq: variantReq,
      isNegative: false
    )
    
    var matchingVariant = newVariantProfile()
    matchingVariant.addFlag("optimization", "lto")
    matchingVariant.calculateHash()
    
    var nonMatchingVariant = newVariantProfile()
    nonMatchingVariant.addFlag("optimization", "none")
    nonMatchingVariant.calculateHash()
    
    let version = SemanticVersion(major: 1, minor: 0, patch: 0)
    
    check satisfies(version, matchingVariant, constraint)
    check not satisfies(version, nonMatchingVariant, constraint)
  
  test "Solver state operations":
    ## Test solver state management
    
    var state = newSolverState()
    
    check state.decisionLevel == 0
    check not state.hasAssignment("nginx")
    
    # Add an assignment
    let assignment = Assignment(
      package: "nginx",
      version: SemanticVersion(major: 1, minor: 24, patch: 0),
      variant: newVariantProfile(),
      decisionLevel: 1,
      cause: none(Incompatibility)
    )
    
    state.addAssignment(assignment)
    
    check state.hasAssignment("nginx")
    check state.getAssignment("nginx").isSome
    check state.getAssignment("nginx").get().version.major == 1
    
    # Increment decision level
    state.incrementDecisionLevel()
    check state.decisionLevel == 1
    
    # Mark as processed
    state.markProcessed("nginx")
    check state.isProcessed("nginx")
    check not state.isProcessed("zlib")
  
  test "Constraint equality":
    ## Test constraint equality
    
    let constraint1 = Constraint(
      versionRange: VersionConstraint(
        operator: OpGreaterEq,
        version: SemanticVersion(major: 1, minor: 20, patch: 0)
      ),
      variantReq: newVariantProfile(),
      isNegative: false
    )
    
    let constraint2 = Constraint(
      versionRange: VersionConstraint(
        operator: OpGreaterEq,
        version: SemanticVersion(major: 1, minor: 20, patch: 0)
      ),
      variantReq: newVariantProfile(),
      isNegative: false
    )
    
    let constraint3 = Constraint(
      versionRange: VersionConstraint(
        operator: OpGreaterEq,
        version: SemanticVersion(major: 1, minor: 21, patch: 0)
      ),
      variantReq: newVariantProfile(),
      isNegative: false
    )
    
    check constraint1 == constraint2
    check constraint1 != constraint3
  
  test "Term equality":
    ## Test term equality
    
    let term1 = Term(
      package: "nginx",
      constraint: Constraint(
        versionRange: VersionConstraint(
          operator: OpGreaterEq,
          version: SemanticVersion(major: 1, minor: 20, patch: 0)
        ),
        variantReq: newVariantProfile(),
        isNegative: false
      )
    )
    
    let term2 = Term(
      package: "nginx",
      constraint: Constraint(
        versionRange: VersionConstraint(
          operator: OpGreaterEq,
          version: SemanticVersion(major: 1, minor: 20, patch: 0)
        ),
        variantReq: newVariantProfile(),
        isNegative: false
      )
    )
    
    let term3 = Term(
      package: "zlib",
      constraint: Constraint(
        versionRange: VersionConstraint(
          operator: OpGreaterEq,
          version: SemanticVersion(major: 1, minor: 20, patch: 0)
        ),
        variantReq: newVariantProfile(),
        isNegative: false
      )
    )
    
    check term1 == term2
    check term1 != term3
  
  test "String representations":
    ## Test string conversion for debugging
    
    let constraint = Constraint(
      versionRange: VersionConstraint(
        operator: OpGreaterEq,
        version: SemanticVersion(major: 1, minor: 20, patch: 0)
      ),
      variantReq: newVariantProfile(),
      isNegative: false
    )
    
    let term = Term(package: "nginx", constraint: constraint)
    
    let constraintStr = $constraint
    let termStr = $term
    
    check constraintStr.len > 0
    check termStr.len > 0
    check termStr.contains("nginx")
