## test_variant_fingerprint.nim
## Tests for variant fingerprint calculation
## Ensures determinism, uniqueness, and correctness

import std/[unittest, tables, strutils]
import ../src/nimpak/variant_fingerprint
import ../src/nimpak/variant_types
import ../src/nimpak/config

suite "Variant Fingerprint Tests":

  # Test data
  let testToolchain = newToolchainInfo("gcc", "13.2.0")
  let testTarget = newTargetInfo("x86_64", "linux")
  let testCompilerFlags = CompilerFlags(
    cflags: "-O2 -pipe",
    cxxflags: "-O2 -pipe",
    ldflags: "-Wl,-O1",
    makeflags: "-j4"
  )

  test "Same inputs produce identical fingerprints":
    # Create two identical configurations
    var domains1 = initTable[string, seq[string]]()
    domains1["init"] = @["dinit"]
    domains1["security"] = @["pie", "relro"]
    domains1["optimization"] = @["lto"]

    var domains2 = initTable[string, seq[string]]()
    domains2["init"] = @["dinit"]
    domains2["security"] = @["pie", "relro"]
    domains2["optimization"] = @["lto"]

    let fp1 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains1, testCompilerFlags, testToolchain, testTarget
    )

    let fp2 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains2, testCompilerFlags, testToolchain, testTarget
    )

    check fp1 == fp2
    check fp1.len == 20  # "blake2b-" + 12 chars
    check fp1.startsWith("blake2b-")

  test "Different package names produce different fingerprints":
    var domains = initTable[string, seq[string]]()
    domains["init"] = @["dinit"]

    let fp1 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget
    )

    let fp2 = calculateVariantFingerprint(
      "apache", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget
    )

    check fp1 != fp2


  test "Different versions produce different fingerprints":
    var domains = initTable[string, seq[string]]()
    domains["init"] = @["dinit"]

    let fp1 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget
    )

    let fp2 = calculateVariantFingerprint(
      "nginx", "1.29.0", domains, testCompilerFlags, testToolchain, testTarget
    )

    check fp1 != fp2

  test "Different domain flags produce different fingerprints":
    var domains1 = initTable[string, seq[string]]()
    domains1["init"] = @["dinit"]
    domains1["security"] = @["pie", "relro"]

    var domains2 = initTable[string, seq[string]]()
    domains2["init"] = @["systemd"]
    domains2["security"] = @["pie", "relro"]

    let fp1 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains1, testCompilerFlags, testToolchain, testTarget
    )

    let fp2 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains2, testCompilerFlags, testToolchain, testTarget
    )

    check fp1 != fp2

  test "Flag order independence - same flags different order produce same fingerprint":
    # Test that domain flags are sorted before hashing
    var domains1 = initTable[string, seq[string]]()
    domains1["security"] = @["pie", "relro", "hardened"]
    domains1["optimization"] = @["lto", "pgo"]
    domains1["init"] = @["dinit"]

    var domains2 = initTable[string, seq[string]]()
    domains2["init"] = @["dinit"]
    domains2["optimization"] = @["lto", "pgo"]
    domains2["security"] = @["pie", "relro", "hardened"]

    let fp1 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains1, testCompilerFlags, testToolchain, testTarget
    )

    let fp2 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains2, testCompilerFlags, testToolchain, testTarget
    )

    check fp1 == fp2

  test "Value order within domain affects fingerprint":
    # Different order of values within a domain should produce same result
    # because values are sorted
    var domains1 = initTable[string, seq[string]]()
    domains1["security"] = @["relro", "pie", "hardened"]

    var domains2 = initTable[string, seq[string]]()
    domains2["security"] = @["pie", "relro", "hardened"]

    let fp1 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains1, testCompilerFlags, testToolchain, testTarget
    )

    let fp2 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains2, testCompilerFlags, testToolchain, testTarget
    )

    check fp1 == fp2

  test "Different compiler flags produce different fingerprints":
    var domains = initTable[string, seq[string]]()
    domains["init"] = @["dinit"]

    let flags1 = CompilerFlags(
      cflags: "-O2 -pipe",
      cxxflags: "-O2 -pipe",
      ldflags: "-Wl,-O1",
      makeflags: "-j4"
    )

    let flags2 = CompilerFlags(
      cflags: "-O3 -march=native",
      cxxflags: "-O3 -march=native",
      ldflags: "-Wl,-O1",
      makeflags: "-j4"
    )

    let fp1 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, flags1, testToolchain, testTarget
    )

    let fp2 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, flags2, testToolchain, testTarget
    )

    check fp1 != fp2

  test "Different toolchains produce different fingerprints":
    var domains = initTable[string, seq[string]]()
    domains["init"] = @["dinit"]

    let toolchain1 = newToolchainInfo("gcc", "13.2.0")
    let toolchain2 = newToolchainInfo("clang", "17.0.0")

    let fp1 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, testCompilerFlags, toolchain1, testTarget
    )

    let fp2 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, testCompilerFlags, toolchain2, testTarget
    )

    check fp1 != fp2

  test "Different targets produce different fingerprints":
    var domains = initTable[string, seq[string]]()
    domains["init"] = @["dinit"]

    let target1 = newTargetInfo("x86_64", "linux")
    let target2 = newTargetInfo("aarch64", "linux")

    let fp1 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, target1
    )

    let fp2 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, target2
    )

    check fp1 != fp2

  test "buildVariantFingerprint creates complete object":
    var domains = initTable[string, seq[string]]()
    domains["init"] = @["dinit"]
    domains["security"] = @["pie", "relro"]

    let fp = buildVariantFingerprint(
      "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget
    )

    check fp.packageName == "nginx"
    check fp.version == "1.28.0"
    check fp.domainFlags == domains
    check fp.compilerFlags.cflags == testCompilerFlags.cflags
    check fp.toolchain == testToolchain
    check fp.target == testTarget
    check fp.hash.len == 20
    check fp.hash.startsWith("blake2b-")

  test "isValidFingerprint validates format correctly":
    check isValidFingerprint("blake2b-abc123def456") == true
    check isValidFingerprint("blake2b-ABC123DEF456") == true
    check isValidFingerprint("blake2b-0123456789ab") == true

    # Invalid cases
    check isValidFingerprint("blake2b-") == false
    check isValidFingerprint("blake2b-abc") == false  # Too short
    check isValidFingerprint("blake2b-abc123def456xyz") == false  # Too long
    check isValidFingerprint("blake3-abc123def456") == false  # Wrong prefix
    check isValidFingerprint("abc123def456") == false  # No prefix
    check isValidFingerprint("blake2b-xyz123def456") == false  # Invalid hex

  test "extractFingerprintPrefix extracts 12-char prefix":
    let fullHash = "blake2b-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    let prefix = extractFingerprintPrefix(fullHash)

    check prefix == "blake2b-0123456789ab"
    check prefix.len == 20

  test "getFingerprintInputString returns deterministic input":
    var domains = initTable[string, seq[string]]()
    domains["init"] = @["dinit"]
    domains["security"] = @["pie", "relro"]

    let input = getFingerprintInputString(
      "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget
    )

    check input.contains("nginx")
    check input.contains("1.28.0")
    check input.contains("init:dinit")
    check input.contains("security:")
    check input.contains("pie")
    check input.contains("relro")
    check input.contains("cflags:-O2 -pipe")
    check input.contains("toolchain:gcc-13.2.0")
    check input.contains("target:x86_64-linux")

  test "Empty domains produce valid fingerprint":
    var domains = initTable[string, seq[string]]()

    let fp = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget
    )

    check fp.len == 20
    check fp.startsWith("blake2b-")

  test "Multiple domains with multiple values":
    var domains = initTable[string, seq[string]]()
    domains["init"] = @["dinit"]
    domains["runtime"] = @["ssl", "http3", "zstd", "ipv6"]
    domains["security"] = @["pie", "relro", "hardened"]
    domains["optimization"] = @["lto", "march-native"]
    domains["network"] = @["ipv6", "wireguard"]

    let fp = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget
    )

    check fp.len == 20
    check fp.startsWith("blake2b-")

    # Verify it's deterministic
    let fp2 = calculateVariantFingerprint(
      "nginx", "1.28.0", domains, testCompilerFlags, testToolchain, testTarget
    )

    check fp == fp2

when isMainModule:
  echo "Running variant fingerprint tests..."
