import unittest, os, strutils, tables, options, json
import ../src/nimpak/cas
import ../src/nip/types

suite "CAS Performance Optimizations Tests":
  
  var 
    cas: CasManager
    testRoot = getTempDir() / "nip_optim_test_" & $getCurrentProcessId()
    testFile = testRoot / "large_file.dat"
    
  setup:
    createDir(testRoot)
    cas = initCasManager(testRoot)
    
    # Create a dummy large file (larger than 1MB to trigger chunking)
    var f = open(testFile, fmWrite)
    # Write 1.5MB
    let chunk = newString(1024 * 64) # 64KB
    for i in 0..<24:
      f.write(chunk)
    f.close()

  teardown:
    removeDir(testRoot)

  test "Index Caching":
    # Initial state
    check cas.indexCache.isNone
    
    # Load index (should create empty one)
    discard cas.loadIndex()
    check cas.indexCache.isSome
    let idx = cas.indexCache.get()
    check idx.totalChunks == 0
    
    # Update index
    cas.updateIndex(100, 1)
    check cas.indexCache.get().totalChunks == 1
    check cas.indexCache.get().totalSize == 100
    
    # Save and reload
    discard cas.saveIndex()
    
    # Reset manager to clear cache
    var newCas = initCasManager(testRoot)
    discard newCas.loadIndex()
    check newCas.indexCache.get().totalChunks == 1
    check newCas.indexCache.get().totalSize == 100

  test "Existence Caching":
    let data = "test data"
    let res = cas.storeObject(data.toOpenArrayByte(0, data.len-1))
    let hash = res.get().hash
    
    # Cache should be empty initially (or populated by storeObject? storeObject doesn't update existenceCache currently)
    # Wait, storeObject calls objectExists.
    # But objectExists updates cache ONLY if found.
    # When we store a NEW object, objectExists returns false.
    # Then we write file.
    # So cache is NOT updated for the new object yet.
    
    check not cas.existenceCache.hasKey(hash)
    
    # First check - should hit disk and populate cache
    check cas.objectExists(hash)
    check cas.existenceCache.hasKey(hash)
    check cas.existenceCache[hash].endsWith(hash.split('-')[1])
    
    # Second check - should hit cache
    # Since our cache verifies file existence, deleting the file invalidates the cache.
    # So we just verify the cache is populated.
    check cas.existenceCache.hasKey(hash)
    check cas.existenceCache[hash].endsWith(hash.split('-')[1])
    
    # Invalidate cache manually
    cas.existenceCache.del(hash)
    check not cas.existenceCache.hasKey(hash)

  test "Parallel Store Correctness":
    # We test storeFileParallel for correctness
    let res = cas.storeFileParallel(testFile)
    if not res.isOk:
      echo "Store failed: ", res.getError().msg
    check res.isOk
    let obj = res.get()
    check obj.chunks.len > 0
    
    # Verify we can retrieve it
    let outFile = testRoot / "retrieved.dat"
    let retRes = cas.retrieveFile(obj.hash, outFile)
    if not retRes.isOk:
      echo "Retrieve failed: ", retRes.errValue.msg
    check retRes.isOk
    
    if retRes.isOk:
      check getFileInfo(outFile).size == getFileInfo(testFile).size
    else:
      # Debug: check if it wrote a manifest file
      if fileExists(outFile):
        echo "Output file size: ", getFileInfo(outFile).size
        echo "Output content preview: ", readFile(outFile)[0..100]

  test "Manifest Caching":
    # Store a file (creates manifest)
    let res = cas.storeFile(testFile)
    let hash = res.get().hash
    
    check not cas.manifestCache.hasKey(hash)
    
    # Retrieve file - should populate manifest cache
    let outFile = testRoot / "retrieved_manifest.dat"
    discard cas.retrieveFile(hash, outFile)
    
    check cas.manifestCache.hasKey(hash)
    check cas.manifestCache[hash].kind == JObject
    check cas.manifestCache[hash].hasKey("chunks")