## Unit Tests for Build Synthesis
##
## Tests for the build synthesis module which creates deterministic builds
## from unified variant profiles and stores them in the CAS.

import std/[unittest, options, tables, os, tempfiles, strutils, times]
import ../src/nip/resolver/build_synthesis
import ../src/nip/resolver/variant_types

suite "Build Synthesis Tests":
  
  test "Build hash calculation is deterministic":
    ## Test that the same configuration always produces the same hash
    
    # Create variant profile
    var profile = newVariantProfile()
    profile.addFlag("optimization", "lto")
    profile.addFlag("security", "hardened")
    profile.calculateHash()
    
    # Create build config
    let config = newBuildConfig(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123",
      compilerVersion = "gcc-13.2.0",
      compilerFlags = @["-O2", "-march=native"],
      configureFlags = @["--with-http_ssl_module"],
      targetArchitecture = "x86_64",
      libc = "musl",
      allocator = "jemalloc"
    )
    
    # Calculate hash twice
    let hash1 = calculateBuildHash(config)
    let hash2 = calculateBuildHash(config)
    
    # Hashes should be identical
    check hash1 == hash2
    check hash1.startsWith("xxh3-")

  test "Different configurations produce different hashes":
    ## Test that different configurations produce different hashes
    
    # Create first variant profile
    var profile1 = newVariantProfile()
    profile1.addFlag("optimization", "lto")
    profile1.calculateHash()
    
    # Create second variant profile (different)
    var profile2 = newVariantProfile()
    profile2.addFlag("optimization", "o3")
    profile2.calculateHash()
    
    # Create configs with different profiles
    let config1 = newBuildConfig(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile1,
      sourceHash = "blake3-abc123"
    )
    
    let config2 = newBuildConfig(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile2,
      sourceHash = "blake3-abc123"
    )
    
    # Hashes should be different
    let hash1 = calculateBuildHash(config1)
    let hash2 = calculateBuildHash(config2)
    
    check hash1 != hash2

  test "Build synthesis creates valid result":
    ## Test that build synthesis creates a valid BuildSynthesisResult
    
    # Create variant profile
    var profile = newVariantProfile()
    profile.addFlag("optimization", "lto")
    profile.calculateHash()
    
    # Synthesize build
    let result = synthesizeBuild(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123"
    )
    
    # Verify result
    check result.buildHash.startsWith("xxh3-")
    check result.casID == result.buildHash
    check result.buildConfig.packageName == "nginx"
    check result.buildConfig.packageVersion == "1.24.0"

  test "CAS storage and retrieval":
    ## Test storing and retrieving builds from CAS
    
    # Create temporary CAS directory
    let casRoot = getTempDir() / "test_cas_" & $getTime().toUnix()
    createDir(casRoot)
    
    try:
      # Create variant profile
      var profile = newVariantProfile()
      profile.addFlag("optimization", "lto")
      profile.calculateHash()
      
      # Synthesize build
      let result = synthesizeBuild(
        packageName = "nginx",
        packageVersion = "1.24.0",
        variantProfile = profile,
        sourceHash = "blake3-abc123"
      )
      
      # Store in CAS
      let casID = storeBuildInCAS(result, casRoot)
      
      # Verify CAS ID
      check casID == result.casID
      check casID.startsWith("xxh3-")
      
      # Retrieve from CAS
      let retrieved = retrieveBuildFromCAS(casID, casRoot)
      
      # Verify retrieved build
      check retrieved.buildHash == result.buildHash
      check retrieved.buildConfig.packageName == "nginx"
      check retrieved.buildConfig.packageVersion == "1.24.0"
    
    finally:
      # Clean up
      removeDir(casRoot)

  test "Build hash verification":
    ## Test verifying build hashes
    
    # Create variant profile
    var profile = newVariantProfile()
    profile.addFlag("optimization", "lto")
    profile.calculateHash()
    
    # Create config
    let config = newBuildConfig(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123"
    )
    
    # Calculate hash
    let hash = calculateBuildHash(config)
    
    # Verify hash
    check verifyBuildHash(hash, config) == true
    
    # Verify with wrong hash fails
    check verifyBuildHash("xxh3-wronghash", config) == false

  test "Build identity comparison":
    ## Test comparing builds for identity
    
    # Create variant profile
    var profile = newVariantProfile()
    profile.addFlag("optimization", "lto")
    profile.calculateHash()
    
    # Synthesize two builds with same config
    let result1 = synthesizeBuild(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123"
    )
    
    let result2 = synthesizeBuild(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123"
    )
    
    # Builds should be identical
    check isBuildIdentical(result1, result2) == true

  test "Different source hashes produce different builds":
    ## Test that different source hashes produce different build hashes
    
    # Create variant profile
    var profile = newVariantProfile()
    profile.addFlag("optimization", "lto")
    profile.calculateHash()
    
    # Synthesize builds with different source hashes
    let result1 = synthesizeBuild(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123"
    )
    
    let result2 = synthesizeBuild(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-def456"
    )
    
    # Build hashes should be different
    check result1.buildHash != result2.buildHash

  test "Canonical representation is deterministic":
    ## Test that canonical representation is deterministic
    
    # Create variant profile
    var profile = newVariantProfile()
    profile.addFlag("optimization", "lto")
    profile.addFlag("security", "hardened")
    profile.calculateHash()
    
    # Create config
    let config = newBuildConfig(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123",
      compilerFlags = @["-O2", "-march=native", "-fPIC"],
      configureFlags = @["--with-ssl", "--with-http2"]
    )
    
    # Get canonical representation twice
    let canonical1 = config.toCanonical()
    let canonical2 = config.toCanonical()
    
    # Should be identical
    check canonical1 == canonical2
    
    # Should contain all components
    check canonical1.contains("nginx")
    check canonical1.contains("1.24.0")
    check canonical1.contains("blake3-abc123")
    check canonical1.contains("gcc-13.2.0")

  test "Build synthesis with custom compiler flags":
    ## Test build synthesis with custom compiler flags
    
    # Create variant profile
    var profile = newVariantProfile()
    profile.addFlag("optimization", "o3")
    profile.calculateHash()
    
    # Synthesize with custom flags
    let result = synthesizeBuild(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123",
      compilerVersion = "clang-17.0.0",
      compilerFlags = @["-O3", "-march=native", "-flto"],
      configureFlags = @["--with-http_ssl_module", "--with-http_v2_module"]
    )
    
    # Verify custom flags are in config
    check result.buildConfig.compilerVersion == "clang-17.0.0"
    check result.buildConfig.compilerFlags.contains("-O3")
    check result.buildConfig.compilerFlags.contains("-flto")
    check result.buildConfig.configureFlags.contains("--with-http_ssl_module")

  test "Build synthesis with different architectures":
    ## Test build synthesis with different target architectures
    
    # Create variant profile
    var profile = newVariantProfile()
    profile.addFlag("optimization", "lto")
    profile.calculateHash()
    
    # Synthesize for x86_64
    let resultX86 = synthesizeBuild(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123",
      targetArchitecture = "x86_64"
    )
    
    # Synthesize for aarch64
    let resultARM = synthesizeBuild(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123",
      targetArchitecture = "aarch64"
    )
    
    # Build hashes should be different
    check resultX86.buildHash != resultARM.buildHash
    check resultX86.buildConfig.targetArchitecture == "x86_64"
    check resultARM.buildConfig.targetArchitecture == "aarch64"

  test "Build synthesis with different libc":
    ## Test build synthesis with different libc types
    
    # Create variant profile
    var profile = newVariantProfile()
    profile.addFlag("optimization", "lto")
    profile.calculateHash()
    
    # Synthesize with musl
    let resultMusl = synthesizeBuild(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123",
      libc = "musl"
    )
    
    # Synthesize with glibc
    let resultGlibc = synthesizeBuild(
      packageName = "nginx",
      packageVersion = "1.24.0",
      variantProfile = profile,
      sourceHash = "blake3-abc123",
      libc = "glibc"
    )
    
    # Build hashes should be different
    check resultMusl.buildHash != resultGlibc.buildHash
    check resultMusl.buildConfig.libc == "musl"
    check resultGlibc.buildConfig.libc == "glibc"
