## Property-Based Tests for Build Synthesis
##
## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
## **Validates: Requirements 8.1, 8.2, 8.3**
##
## Property: For any build configuration, calculating the build hash multiple times
## produces the same result. This ensures reproducibility - same inputs always
## produce same outputs.

import std/[unittest, random, strutils, sequtils, times]
import ../src/nip/resolver/build_synthesis
import ../src/nip/resolver/variant_types

# Generator for random variant profiles
proc generateRandomVariantProfile(): VariantProfile =
  var profile = newVariantProfile()
  
  # Add random domains
  let domains = @["optimization", "security", "features", "network"]
  let flags = @["lto", "hardened", "wayland", "vulkan", "ipv6", "ssl", "zstd"]
  
  for domain in domains:
    if rand(0..1) == 0:  # 50% chance to include domain
      let numFlags = rand(1..3)
      for _ in 0..<numFlags:
        let flag = flags[rand(0..flags.len-1)]
        profile.addFlag(domain, flag)
  
  profile.calculateHash()
  return profile

# Generator for random build configs
proc generateRandomBuildConfig(): BuildConfig =
  let packageNames = @["nginx", "postgresql", "redis", "openssl", "zlib"]
  let versions = @["1.0.0", "2.1.0", "3.0.0", "1.24.0", "15.3"]
  let compilers = @["gcc-13.2.0", "clang-17.0.0", "gcc-12.0.0"]
  let archs = @["x86_64", "aarch64", "riscv64"]
  let libcs = @["musl", "glibc"]
  let allocators = @["jemalloc", "tcmalloc", "default"]
  
  let profile = generateRandomVariantProfile()
  
  let config = newBuildConfig(
    packageName = packageNames[rand(0..packageNames.len-1)],
    packageVersion = versions[rand(0..versions.len-1)],
    variantProfile = profile,
    sourceHash = "blake3-" & toHex(rand(0..0xFFFFFF)),
    compilerVersion = compilers[rand(0..compilers.len-1)],
    compilerFlags = @["-O2", "-march=native"],
    configureFlags = @["--with-ssl"],
    targetArchitecture = archs[rand(0..archs.len-1)],
    libc = libcs[rand(0..libcs.len-1)],
    allocator = allocators[rand(0..allocators.len-1)]
  )
  
  return config

suite "Build Synthesis Property Tests":
  
  test "Property 7: Build Determinism - Hash calculation is deterministic":
    ## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
    ## **Validates: Requirements 8.1, 8.2, 8.3**
    ##
    ## For any build configuration, calculating the build hash multiple times
    ## produces the same result. This ensures reproducibility.
    ##
    ## Property: ∀ config ∈ BuildConfig: calculateBuildHash(config) = calculateBuildHash(config)
    
    # Run property test with 100 iterations
    for iteration in 0..<100:
      # Generate random build config
      let config = generateRandomBuildConfig()
      
      # Calculate hash multiple times
      let hash1 = calculateBuildHash(config)
      let hash2 = calculateBuildHash(config)
      let hash3 = calculateBuildHash(config)
      
      # All hashes should be identical
      check hash1 == hash2
      check hash2 == hash3
      check hash1 == hash3
      
      # Hash should have correct format
      check hash1.startsWith("xxh3-")

  test "Property 7: Build Determinism - Synthesis produces deterministic hashes":
    ## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
    ## **Validates: Requirements 8.1, 8.2, 8.3**
    ##
    ## For any variant profile, synthesizing a build multiple times produces
    ## the same build hash. This ensures reproducibility across builds.
    ##
    ## Property: ∀ profile ∈ VariantProfile: synthesizeBuild(..., profile).buildHash = synthesizeBuild(..., profile).buildHash
    
    # Run property test with 100 iterations
    for iteration in 0..<100:
      # Generate random variant profile
      let profile = generateRandomVariantProfile()
      
      # Synthesize builds multiple times
      let result1 = synthesizeBuild(
        packageName = "test-package",
        packageVersion = "1.0.0",
        variantProfile = profile,
        sourceHash = "blake3-abc123"
      )
      
      let result2 = synthesizeBuild(
        packageName = "test-package",
        packageVersion = "1.0.0",
        variantProfile = profile,
        sourceHash = "blake3-abc123"
      )
      
      let result3 = synthesizeBuild(
        packageName = "test-package",
        packageVersion = "1.0.0",
        variantProfile = profile,
        sourceHash = "blake3-abc123"
      )
      
      # All build hashes should be identical
      check result1.buildHash == result2.buildHash
      check result2.buildHash == result3.buildHash
      check result1.buildHash == result3.buildHash

  test "Property 7: Build Determinism - Canonical representation is deterministic":
    ## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
    ## **Validates: Requirements 8.1, 8.2, 8.3**
    ##
    ## For any build configuration, the canonical representation is deterministic.
    ## This ensures that the same configuration always produces the same hash.
    ##
    ## Property: ∀ config ∈ BuildConfig: toCanonical(config) = toCanonical(config)
    
    # Run property test with 100 iterations
    for iteration in 0..<100:
      # Generate random build config
      let config = generateRandomBuildConfig()
      
      # Get canonical representation multiple times
      let canonical1 = config.toCanonical()
      let canonical2 = config.toCanonical()
      let canonical3 = config.toCanonical()
      
      # All canonical representations should be identical
      check canonical1 == canonical2
      check canonical2 == canonical3
      check canonical1 == canonical3

  test "Property 7: Build Determinism - Hash verification is consistent":
    ## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
    ## **Validates: Requirements 8.1, 8.2, 8.3**
    ##
    ## For any build configuration and its hash, verification always produces
    ## the same result. This ensures consistent verification.
    ##
    ## Property: ∀ config ∈ BuildConfig, hash = calculateBuildHash(config):
    ##           verifyBuildHash(hash, config) = verifyBuildHash(hash, config)
    
    # Run property test with 100 iterations
    for iteration in 0..<100:
      # Generate random build config
      let config = generateRandomBuildConfig()
      
      # Calculate hash
      let hash = calculateBuildHash(config)
      
      # Verify hash multiple times
      let verify1 = verifyBuildHash(hash, config)
      let verify2 = verifyBuildHash(hash, config)
      let verify3 = verifyBuildHash(hash, config)
      
      # All verifications should be identical and true
      check verify1 == verify2
      check verify2 == verify3
      check verify1 == true

  test "Property 7: Build Determinism - Different configs produce different hashes":
    ## **Feature: nip-dependency-resolution, Property 7: Build Determinism**
    ## **Validates: Requirements 8.1, 8.2, 8.3**
    ##
    ## For any two different build configurations, they should produce different
    ## hashes (with very high probability). This ensures uniqueness.
    ##
    ## Property: ∀ config1, config2 ∈ BuildConfig where config1 ≠ config2:
    ##           calculateBuildHash(config1) ≠ calculateBuildHash(config2)
    
    # Run property test with 50 iterations (fewer because we need pairs)
    for iteration in 0..<50:
      # Generate two random build configs
      let config1 = generateRandomBuildConfig()
      let config2 = generateRandomBuildConfig()
      
      # Calculate hashes
      let hash1 = calculateBuildHash(config1)
      let hash2 = calculateBuildHash(config2)
      
      # If configs are different, hashes should be different
      # (with very high probability - hash collisions are extremely rare)
      if config1.toCanonical() != config2.toCanonical():
        check hash1 != hash2
