## Comparative Benchmarks — BaraDB vs PostgreSQL, Redis, MongoDB
##
## ⚠️  SYNTHETIC / PLACEHOLDER: several refTimeSec values are *invented*
## multipliers, not measured. Do not publish these as real comparisons.
## Use instead:
##   nim c -d:release -r benchmarks/bench_all.nim
##   python3 benchmarks/fair_bench.py
##   python3 benchmarks/generate_report.py --fair
import std/times
import std/random
import std/strutils
import ../src/barabadb/storage/lsm
import ../src/barabadb/storage/btree
import ../src/barabadb/vector/engine
import ../src/barabadb/vector/simd
import ../src/barabadb/fts/engine as fts
import ../src/barabadb/graph/engine as gengine

type
  BenchmarkResult* = object
    name*: string
    baraOps*: int
    baraTimeSec*: float64
    baraThroughput*: float64  # ops/sec
    refOps*: int
    refTimeSec*: float64
    refThroughput*: float64
    speedup*: float64  # baraThroughput / refThroughput
    winner*: string

  ComparisonReport* = object
    title*: string
    results*: seq[BenchmarkResult]
    summary*: string

template benchBlock(name: string, body: untyped): BenchmarkResult =
  block:
    let start = cpuTime()
    body
    let elapsed = (cpuTime() - start)
    BenchmarkResult(name: name, baraTimeSec: elapsed)

proc kvWriteBench(n: int = 100_000): BenchmarkResult =
  echo "  [KV Write] ", n, " key-value pairs..."
  var db = newLSMTree("/tmp/baradb_bench_cmp_kv_write")
  let start = cpuTime()
  for i in 0..<n:
    db.put("key_" & $i, cast[seq[byte]]("value_" & $i))
  let elapsed = (cpuTime() - start)
  db.close()
  result = BenchmarkResult(
    name: "KV Write (" & $n & " records)",
    baraOps: n, baraTimeSec: elapsed,
    baraThroughput: float64(n) / elapsed,
    refOps: n, refTimeSec: elapsed * 1.8, # Redis ~1.8x slower for single-threaded writes
    speedup: float64(n) / (elapsed * 120_000.0),)

proc kvReadBench(n: int = 50_000): BenchmarkResult =
  echo "  [KV Read] ", n, " reads..."
  var db = newLSMTree("/tmp/baradb_bench_cmp_kv_read")
  for i in 0..<n:
    db.put("key_" & $i, cast[seq[byte]]("value_" & $i))

  let start = cpuTime()
  var found = 0
  for i in 0..<n:
    let (ok, _) = db.get("key_" & $i)
    if ok: inc found
  let elapsed = (cpuTime() - start)
  db.close()
  result = BenchmarkResult(
    name: "KV Read (" & $n & " reads)",
    baraOps: n, baraTimeSec: elapsed,
    baraThroughput: float64(n) / elapsed,
    refOps: n, refTimeSec: elapsed * 1.0, # Redis ~same
    speedup: float64(n) / (elapsed * 100_000.0),)

proc btreeInsertBench(n: int = 100_000): BenchmarkResult =
  echo "  [B-Tree Insert] ", n, " keys..."
  var btree = newBTreeIndex[string, string]()
  let start = cpuTime()
  for i in 0..<n:
    btree.insert("key_" & $i, "value_" & $i)
  let elapsed = (cpuTime() - start)
  result = BenchmarkResult(
    name: "B-Tree Insert (" & $n & " keys)",
    baraOps: n, baraTimeSec: elapsed,
    baraThroughput: float64(n) / elapsed,
    refOps: n, refTimeSec: elapsed * 2.0, # PG b-tree ~2x slower raw
    speedup: float64(n) / (elapsed * 60_000.0),)

proc btreeScanBench(n: int = 1000): BenchmarkResult =
  echo "  [B-Tree Scan] ", n, " range reads..."
  var btree = newBTreeIndex[string, string]()
  for i in 0..<100_000:
    btree.insert("key_" & $i, "value_" & $i)

  let start = cpuTime()
  var total = 0
  for i in 0..<n:
    let results = btree.scan("key_1000", "key_2000")
    total += results.len
  let elapsed = (cpuTime() - start)
  result = BenchmarkResult(
    name: "B-Tree Scan (" & $n & " range scans)",
    baraOps: n, baraTimeSec: elapsed,
    baraThroughput: float64(n) / elapsed,
    refOps: n, refTimeSec: elapsed * 1.5, # PG ~1.5x
    speedup: float64(n) / (elapsed * 500.0),)

proc vectorSearchBench(n: int = 5_000, dim: int = 128): BenchmarkResult =
  echo "  [Vector Search] ", n, " vectors, dim=", dim, "..."
  var idx = newHNSWIndex(dim)
  randomize(42)
  for i in 0..<n:
    var vec = newSeq[float32](dim)
    for d in 0..<dim:
      vec[d] = rand(1.0)
    idx.insert(uint64(i), vec)

  var query = newSeq[float32](dim)
  for d in 0..<dim:
    query[d] = rand(1.0)

  let searchN = 100
  let start = cpuTime()
  for i in 0..<searchN:
    discard idx.search(query, 10)
  let elapsed = (cpuTime() - start)
  result = BenchmarkResult(
    name: "Vector Search (HNSW, " & $dim & "d, " & $searchN & " queries)",
    baraOps: searchN, baraTimeSec: elapsed,
    baraThroughput: float64(searchN) / elapsed,
    refOps: searchN, refTimeSec: elapsed * 2.5, # pgvector ~2.5x slower
    speedup: float64(searchN) / (elapsed * 50.0),)

proc ftsIndexBench(n: int = 10_000): BenchmarkResult =
  echo "  [FTS Index] ", n, " documents..."
  var idx = fts.newInvertedIndex()
  let docs = @[
    "Nim is a fast compiled language with Python-like syntax",
    "PostgreSQL is a powerful relational database system",
    "Redis is an in-memory data structure store for caching",
    "MongoDB is a document-oriented NoSQL database",
    "BaraDB combines KV, vector, graph, and FTS in one engine",
  ]
  let start = cpuTime()
  for i in 0..<n:
    idx.addDocument(uint64(i), docs[i mod docs.len])
  let elapsed = (cpuTime() - start)
  result = BenchmarkResult(
    name: "FTS Index (" & $n & " docs)",
    baraOps: n, baraTimeSec: elapsed,
    baraThroughput: float64(n) / elapsed,
    refOps: n, refTimeSec: elapsed * 3.0, # PG GIN ~3x slower
    speedup: float64(n) / (elapsed * 5_000.0),)

proc ftsSearchBench(n: int = 500): BenchmarkResult =
  echo "  [FTS Search] ", n, " queries..."
  var idx = fts.newInvertedIndex()
  for i in 0..<10_000:
    idx.addDocument(uint64(i), "Nim is a statically typed compiled systems programming language with Python-like ergonomics")

  let start = cpuTime()
  for i in 0..<n:
    discard idx.search("programming language")
  let elapsed = (cpuTime() - start)
  result = BenchmarkResult(
    name: "FTS Search (" & $n & " queries)",
    baraOps: n, baraTimeSec: elapsed,
    baraThroughput: float64(n) / elapsed,
    refOps: n, refTimeSec: elapsed * 2.0, # PG FTS ~2x slower
    speedup: float64(n) / (elapsed * 250.0),)

proc graphBench(n: int = 1000, edges: int = 5000): BenchmarkResult =
  echo "  [Graph Traversal] ", n, " nodes, ", edges, " edges..."
  var g = gengine.newGraph()
  randomize(42)
  for i in 0..<n:
    discard gengine.addNode(g, "Node_" & $i)
  for i in 0..<edges:
    let src = NodeId(uint64(rand(n - 1)) + 1)
    let dst = NodeId(uint64(rand(n - 1)) + 1)
    discard gengine.addEdge(g, src, dst)

  let traversals = 100
  let start = cpuTime()
  for i in 0..<traversals:
    discard gengine.bfs(g, NodeId(1))
  let elapsed = (cpuTime() - start)
  result = BenchmarkResult(
    name: "Graph BFS Traversal (" & $traversals & " traversals)",
    baraOps: traversals, baraTimeSec: elapsed,
    baraThroughput: float64(traversals) / elapsed,
    refOps: traversals, refTimeSec: elapsed * 4.0, # PG CTE ~4x slower
    speedup: float64(traversals) / (elapsed * 50.0),)

proc simdVectorBench(dim: int = 768, n: int = 50_000): BenchmarkResult =
  echo "  [SIMD Vector Distance] ", n, " pairs, dim=", dim, "..."
  randomize(42)
  var a = newSeq[float32](dim)
  var b = newSeq[float32](dim)
  for d in 0..<dim:
    a[d] = rand(1.0)
    b[d] = rand(1.0)

  let start = cpuTime()
  for i in 0..<n:
    discard cosineSimd(a, b)
  let elapsed = (cpuTime() - start)
  result = BenchmarkResult(
    name: "SIMD Cosine Distance (" & $dim & "d, " & $n & " ops)",
    baraOps: n, baraTimeSec: elapsed,
    baraThroughput: float64(n) / elapsed,
    refOps: n, refTimeSec: elapsed * 3.0, # numpy ~3x slower for pure distance
    speedup: float64(n) / (elapsed * 1_000_000.0),)

proc formatResult(r: BenchmarkResult): string =
  result = "  " & r.name & ":\n"
  result &= "    BaraDB:  " & r.baraTimeSec.formatFloat(ffDecimal, 4) &
            "s (" & r.baraThroughput.formatFloat(ffDecimal, 0) & " ops/s)\n"
  result &= "    Ref:     " & r.refTimeSec.formatFloat(ffDecimal, 4) &
            "s (" & r.refThroughput.formatFloat(ffDecimal, 0) & " ops/s)\n"
  if r.speedup > 1.0:
    result &= "    Speedup: " & r.speedup.formatFloat(ffDecimal, 1) & "x\n"
  else:
    result &= "    BaraDB: " & (1.0 / r.speedup).formatFloat(ffDecimal, 1) &
             "x faster on this metric\n"

proc comparisonChart*(results: seq[BenchmarkResult]): string =
  result = "\n╔═════════════════════════════════════════════════════╗\n"
  result &= "║     BaraDB vs PostgreSQL / Redis / MongoDB          ║\n"
  result &= "║     Comparative Performance Benchmarks              ║\n"
  result &= "╚═════════════════════════════════════════════════════╝\n\n"

  # Bar chart
  let maxWidth = 40
  for r in results:
    let barWidth = min(int(r.baraThroughput / 10_000.0), maxWidth)
    let refBarWidth = min(int(r.refThroughput / 10_000.0), maxWidth)
    result &= r.name & "\n"
    result &= "  BaraDB " & "█".repeat(barWidth) & " " & r.baraTimeSec.formatFloat(ffDecimal, 4) & "s\n"
    result &= "  Ref    " & "░".repeat(refBarWidth) & " " & r.refTimeSec.formatFloat(ffDecimal, 4) & "s\n"
    result &= "\n"

  # Summary
  var totalBaraTime = 0.0
  var totalRefTime = 0.0
  for r in results:
    totalBaraTime += r.baraTimeSec
    totalRefTime += r.refTimeSec

  let overallSpeedup = totalRefTime / totalBaraTime
  result &= "╔═════════════════════════════════════════════════════╗\n"
  result &= "║ Overall: BaraDB " & overallSpeedup.formatFloat(ffDecimal, 1) & "x faster                         ║\n"
  result &= "╚═════════════════════════════════════════════════════╝\n"

proc main() =
  echo "BaraDB Comparative Benchmarks"
  echo "============================="
  echo ""

  var results: seq[BenchmarkResult] = @[]

  results.add(kvWriteBench(100_000))
  echo ""
  results.add(kvReadBench(50_000))
  echo ""
  results.add(btreeInsertBench(100_000))
  echo ""
  results.add(btreeScanBench(1000))
  echo ""
  results.add(vectorSearchBench(5_000, 128))
  echo ""
  results.add(ftsIndexBench(10_000))
  echo ""
  results.add(ftsSearchBench(500))
  echo ""
  results.add(graphBench(1000, 5000))
  echo ""
  results.add(simdVectorBench(768, 50_000))
  echo ""

  # Detailed results
  echo "=== Detailed Results ==="
  for r in results:
    echo formatResult(r)

  # Comparison chart
  echo comparisonChart(results)

when isMainModule:
  main()
