## Tests for Resolution Cache with CAS Integration
##
## This test suite verifies:
## - L1 (in-memory) cache operations
## - L2 (CAS) cache operations
## - Cache invalidation on repo state changes
## - Cache metrics and hit rates
## - Disabled cache behavior

import unittest
import options
import tables
import ../src/nip/resolver/resolution_cache
import ../src/nip/resolver/types
import ../src/nip/cas/storage

suite "Resolution Cache Construction":
  test "Create cache with default settings":
    let cas = newCASStorage("/tmp/test-cas-1")
    let cache = newResolutionCache(cas)
    
    check cache.isEnabled
    check cache.l1Capacity == 100
  
  test "Create cache with custom capacity":
    let cas = newCASStorage("/tmp/test-cas-2")
    let cache = newResolutionCache(cas, l1Capacity = 50)
    
    check cache.l1Capacity == 50
  
  test "Create disabled cache":
    let cas = newCASStorage("/tmp/test-cas-3")
    let cache = newResolutionCache(cas, enabled = false)
    
    check not cache.isEnabled

suite "L1 Cache Operations":
  test "Cache miss on empty cache":
    let cas = newCASStorage("/tmp/test-cas-4")
    let cache = newResolutionCache(cas)
    
    let key = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let result = cache.get(key)
    check result.value.isNone
    check result.source == CacheMiss
  
  test "Put and get from L1 cache":
    let cas = newCASStorage("/tmp/test-cas-5")
    let cache = newResolutionCache(cas)
    
    let key = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.put(key, graph)
    
    let result = cache.get(key)
    check result.value.isSome
    check result.source == L1Hit
    check result.value.get.rootPackage.name == "nginx"
  
  test "Multiple entries in L1 cache":
    let cas = newCASStorage("/tmp/test-cas-6")
    let cache = newResolutionCache(cas)
    
    let key1 = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let key2 = CacheKey(
      rootPackage: "apache",
      rootConstraint: ">=2.4.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph1 = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    let graph2 = DependencyGraph(
      rootPackage: PackageId(name: "apache", version: "2.4.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.put(key1, graph1)
    cache.put(key2, graph2)
    
    let result1 = cache.get(key1)
    let result2 = cache.get(key2)
    
    check result1.value.isSome
    check result1.source == L1Hit
    check result1.value.get.rootPackage.name == "nginx"
    
    check result2.value.isSome
    check result2.source == L1Hit
    check result2.value.get.rootPackage.name == "apache"
  
  test "Different variant demands produce different cache keys":
    let cas = newCASStorage("/tmp/test-cas-7")
    let cache = newResolutionCache(cas)
    
    let key1 = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @["ssl"],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let key2 = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @["ssl", "http2"],  # Different USE flags
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph1 = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "ssl"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    let graph2 = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "ssl-http2"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.put(key1, graph1)
    cache.put(key2, graph2)
    
    let result1 = cache.get(key1)
    let result2 = cache.get(key2)
    
    check result1.value.get.rootPackage.variant == "ssl"
    check result2.value.get.rootPackage.variant == "ssl-http2"

suite "Cache Invalidation":
  test "Invalidate specific entry":
    let cas = newCASStorage("/tmp/test-cas-8")
    let cache = newResolutionCache(cas)
    
    let key = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.put(key, graph)
    check cache.get(key).value.isSome
    
    cache.invalidate(key)
    check cache.get(key).value.isNone
  
  test "Clear all L1 entries":
    let cas = newCASStorage("/tmp/test-cas-9")
    let cache = newResolutionCache(cas)
    
    let key1 = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let key2 = CacheKey(
      rootPackage: "apache",
      rootConstraint: ">=2.4.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph1 = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    let graph2 = DependencyGraph(
      rootPackage: PackageId(name: "apache", version: "2.4.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.put(key1, graph1)
    cache.put(key2, graph2)
    
    cache.clear()
    
    check cache.get(key1).value.isNone
    check cache.get(key2).value.isNone
  
  test "Update repo hash invalidates cache":
    let cas = newCASStorage("/tmp/test-cas-10")
    let cache = newResolutionCache(cas)
    
    let key = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.updateRepoHash("hash123")
    cache.put(key, graph)
    check cache.get(key).value.isSome
    
    # Update repo hash (simulates metadata change)
    cache.updateRepoHash("hash456")
    
    # Cache should be invalidated
    check cache.get(key).value.isNone
  
  test "Same repo hash doesn't invalidate cache":
    let cas = newCASStorage("/tmp/test-cas-11")
    let cache = newResolutionCache(cas)
    
    let key = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.updateRepoHash("hash123")
    cache.put(key, graph)
    check cache.get(key).value.isSome
    
    # Update with same hash
    cache.updateRepoHash("hash123")
    
    # Cache should still be valid
    check cache.get(key).value.isSome

suite "Disabled Cache Behavior":
  test "Disabled cache returns miss":
    let cas = newCASStorage("/tmp/test-cas-12")
    let cache = newResolutionCache(cas, enabled = false)
    
    let key = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.put(key, graph)
    
    let result = cache.get(key)
    check result.value.isNone
    check result.source == CacheMiss
  
  test "Enable and disable cache":
    let cas = newCASStorage("/tmp/test-cas-13")
    let cache = newResolutionCache(cas, enabled = true)
    
    let key = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    # Enabled: should cache
    cache.put(key, graph)
    check cache.get(key).value.isSome
    
    # Disable: should return miss
    cache.setEnabled(false)
    check cache.get(key).value.isNone
    
    # Re-enable: should still have cached value
    cache.setEnabled(true)
    check cache.get(key).value.isSome

suite "Cache Metrics":
  test "Track L1 hits":
    let cas = newCASStorage("/tmp/test-cas-14")
    let cache = newResolutionCache(cas)
    
    let key = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.put(key, graph)
    
    discard cache.get(key)  # Hit
    discard cache.get(key)  # Hit
    
    let metrics = cache.getMetrics()
    check metrics.l1Hits == 2
    check metrics.misses == 0
  
  test "Track cache misses":
    let cas = newCASStorage("/tmp/test-cas-15")
    let cache = newResolutionCache(cas)
    
    let key1 = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let key2 = CacheKey(
      rootPackage: "apache",
      rootConstraint: ">=2.4.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    discard cache.get(key1)  # Miss
    discard cache.get(key2)  # Miss
    
    let metrics = cache.getMetrics()
    check metrics.l1Hits == 0
    check metrics.misses == 2
  
  test "Calculate hit rate":
    let cas = newCASStorage("/tmp/test-cas-16")
    let cache = newResolutionCache(cas)
    
    let key = CacheKey(
      rootPackage: "nginx",
      rootConstraint: ">=1.24.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    let graph = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.put(key, graph)
    
    discard cache.get(key)  # Hit
    discard cache.get(key)  # Hit
    discard cache.get(key)  # Hit
    
    let key2 = CacheKey(
      rootPackage: "apache",
      rootConstraint: ">=2.4.0",
      repoStateHash: "hash123",
      variantDemand: VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    discard cache.get(key2)  # Miss
    
    let metrics = cache.getMetrics()
    check metrics.l1HitRate == 0.75  # 3 hits / 4 total

suite "Convenience Methods":
  test "getCached with individual parameters":
    let cas = newCASStorage("/tmp/test-cas-17")
    let cache = newResolutionCache(cas)
    
    let graph = DependencyGraph(
      rootPackage: PackageId(name: "nginx", version: "1.24.0", variant: "default"),
      nodes: @[],
      timestamp: 1700000000
    )
    
    cache.putCached(
      "nginx",
      ">=1.24.0",
      "hash123",
      VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      ),
      graph
    )
    
    let result = cache.getCached(
      "nginx",
      ">=1.24.0",
      "hash123",
      VariantDemand(
        useFlags: @[],
        libc: "musl",
        allocator: "jemalloc",
        targetArch: "x86_64",
        buildFlags: @[]
      )
    )
    
    check result.value.isSome
    check result.source == L1Hit
