## Property-Based Tests for Variant Hash Calculation
##
## **Feature: 02-nip-dependency-resolution, Property 1: Hash Determinism**
##
## These tests verify that variant hash calculation is deterministic:
## - Same profile always produces same hash
## - Different profiles produce different hashes (with high probability)
## - Hash is independent of insertion order
## - Hash is platform-independent

import std/[unittest, random, tables, sets]
import ../src/nip/resolver/variant_types
import ../src/nip/resolver/variant_hash

suite "Property 1: Hash Determinism":
  
  test "Property 1.1: Same profile produces same hash (100 iterations)":
    ## **Validates: Requirements 1.5**
    ##
    ## For any variant profile, calculating the hash multiple times
    ## should always produce the same result
    
    var rng = initRand(42)  # Deterministic seed for reproducibility
    var passCount = 0
    
    for i in 0..<100:
      # Create random profile
      var profile = newVariantProfile()
      
      # Add random domains and flags
      let numDomains = rng.rand(1..5)
      for d in 0..<numDomains:
        let domainName = "domain" & $d
        let exclusivity = if rng.rand(1.0) < 0.3: Exclusive else: NonExclusive
        var domain = newVariantDomain(domainName, exclusivity)
        
        let numFlags = if exclusivity == Exclusive: 1 else: rng.rand(1..5)
        for f in 0..<numFlags:
          domain.flags.incl("flag" & $f)
        
        profile.addDomain(domain)
      
      # Calculate hash multiple times
      var profile1 = profile
      var profile2 = profile
      var profile3 = profile
      let hash1 = calculateVariantHash(profile1)
      let hash2 = calculateVariantHash(profile2)
      let hash3 = calculateVariantHash(profile3)
      
      # All hashes must be identical
      if hash1 == hash2 and hash2 == hash3:
        passCount += 1
    
    check passCount == 100
    echo "  ✅ Property 1.1: ", passCount, "/100 profiles produced deterministic hashes"

suite "Variant Unification Tests":
  
  test "Unify: Empty demands returns empty profile":
    ## Test that unifying zero demands produces an empty profile
    
    let result = unify(@[])
    
    check result.kind == Unified
    check result.profile.domains.len == 0
  
  test "Unify: Single demand returns that profile":
    ## Test that unifying one demand returns the same profile
    
    var profile = newVariantProfile()
    profile.addFlag("graphics", "wayland")
    profile.calculateHash()
    
    let demand = VariantDemand(
      packageName: "test",
      variantProfile: profile,
      optional: false
    )
    
    let result = unify(@[demand])
    
    check result.kind == Unified
    check result.profile == profile
  
  test "Unify: Compatible non-exclusive domains merge successfully":
    ## Test that non-exclusive domains accumulate flags
    
    var profile1 = newVariantProfile()
    profile1.addFlag("graphics", "wayland")
    profile1.calculateHash()
    
    var profile2 = newVariantProfile()
    profile2.addFlag("graphics", "vulkan")
    profile2.calculateHash()
    
    let demand1 = VariantDemand(
      packageName: "pkg1",
      variantProfile: profile1,
      optional: false
    )
    
    let demand2 = VariantDemand(
      packageName: "pkg2",
      variantProfile: profile2,
      optional: false
    )
    
    let result = unify(@[demand1, demand2])
    
    check result.kind == Unified
    check result.profile.hasDomain("graphics")
    
    let graphicsDomain = result.profile.getDomain("graphics")
    check "wayland" in graphicsDomain.flags
    check "vulkan" in graphicsDomain.flags
  
  test "Unify: Incompatible exclusive domains produce conflict":
    ## Test that conflicting exclusive domains are detected
    
    var profile1 = newVariantProfile()
    var initDomain1 = newVariantDomain("init", Exclusive)
    initDomain1.flags.incl("systemd")
    profile1.addDomain(initDomain1)
    profile1.calculateHash()
    
    var profile2 = newVariantProfile()
    var initDomain2 = newVariantDomain("init", Exclusive)
    initDomain2.flags.incl("dinit")
    profile2.addDomain(initDomain2)
    profile2.calculateHash()
    
    let demand1 = VariantDemand(
      packageName: "pkg1",
      variantProfile: profile1,
      optional: false
    )
    
    let demand2 = VariantDemand(
      packageName: "pkg2",
      variantProfile: profile2,
      optional: false
    )
    
    let result = unify(@[demand1, demand2])
    
    check result.kind == Conflict
    check result.conflictingDomain == "init"
  
  test "Unify: Compatible exclusive domains merge successfully":
    ## Test that identical exclusive domains can merge
    
    var profile1 = newVariantProfile()
    var initDomain1 = newVariantDomain("init", Exclusive)
    initDomain1.flags.incl("systemd")
    profile1.addDomain(initDomain1)
    profile1.calculateHash()
    
    var profile2 = newVariantProfile()
    var initDomain2 = newVariantDomain("init", Exclusive)
    initDomain2.flags.incl("systemd")
    profile2.addDomain(initDomain2)
    profile2.calculateHash()
    
    let demand1 = VariantDemand(
      packageName: "pkg1",
      variantProfile: profile1,
      optional: false
    )
    
    let demand2 = VariantDemand(
      packageName: "pkg2",
      variantProfile: profile2,
      optional: false
    )
    
    let result = unify(@[demand1, demand2])
    
    check result.kind == Unified
    check result.profile.hasDomain("init")
    
    let initDomain = result.profile.getDomain("init")
    check "systemd" in initDomain.flags
  
  test "Unify: Multiple demands with mixed domains":
    ## Test complex unification with multiple domains
    
    var profile1 = newVariantProfile()
    profile1.addFlag("graphics", "wayland")
    var initDomain1 = newVariantDomain("init", Exclusive)
    initDomain1.flags.incl("systemd")
    profile1.addDomain(initDomain1)
    profile1.calculateHash()
    
    var profile2 = newVariantProfile()
    profile2.addFlag("graphics", "vulkan")
    profile2.addFlag("optimization", "lto")
    profile2.calculateHash()
    
    var profile3 = newVariantProfile()
    var initDomain3 = newVariantDomain("init", Exclusive)
    initDomain3.flags.incl("systemd")
    profile3.addDomain(initDomain3)
    profile3.addFlag("optimization", "pgo")
    profile3.calculateHash()
    
    let demands = @[
      VariantDemand(packageName: "pkg1", variantProfile: profile1, optional: false),
      VariantDemand(packageName: "pkg2", variantProfile: profile2, optional: false),
      VariantDemand(packageName: "pkg3", variantProfile: profile3, optional: false)
    ]
    
    let result = unify(demands)
    
    check result.kind == Unified
    
    # Check graphics domain (non-exclusive)
    check result.profile.hasDomain("graphics")
    let graphicsDomain = result.profile.getDomain("graphics")
    check "wayland" in graphicsDomain.flags
    check "vulkan" in graphicsDomain.flags
    
    # Check init domain (exclusive)
    check result.profile.hasDomain("init")
    let initDomain = result.profile.getDomain("init")
    check "systemd" in initDomain.flags
    
    # Check optimization domain (non-exclusive)
    check result.profile.hasDomain("optimization")
    let optDomain = result.profile.getDomain("optimization")
    check "lto" in optDomain.flags
    check "pgo" in optDomain.flags
    
    echo "  ✅ Complex unification: ", $result.profile
