import Database from "better-sqlite3";
import { rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { PolyglotExecutor } from "../src/executor.js";
import {
  detectRuntimes,
  getRuntimeSummary,
  hasBunRuntime,
  type Language,
} from "../src/runtime.js";
import { ContentStore } from "../src/store.js";

const runtimes = detectRuntimes();
const executor = new PolyglotExecutor({ runtimes });

interface BenchResult {
  name: string;
  language: string;
  iterations: number;
  avgMs: number;
  minMs: number;
  maxMs: number;
  p50Ms: number;
  p95Ms: number;
}

async function bench(
  name: string,
  language: Language,
  code: string,
  iterations: number = 10,
): Promise<BenchResult | null> {
  // Check if runtime is available
  const runtimeMap: Record<string, string | null> = runtimes;
  if (
    language !== "javascript" &&
    language !== "shell" &&
    !runtimeMap[language]
  ) {
    console.log(`  - ${name} [${language}] SKIP (runtime not available)`);
    return null;
  }

  const times: number[] = [];

  // Warmup (2 rounds)
  for (let i = 0; i < 2; i++) {
    await executor.execute({ language, code, timeout: 15000 });
  }

  // Measure
  for (let i = 0; i < iterations; i++) {
    const start = performance.now();
    await executor.execute({ language, code, timeout: 15000 });
    times.push(performance.now() - start);
  }

  times.sort((a, b) => a - b);
  const result = {
    name,
    language,
    iterations,
    avgMs: +(times.reduce((s, t) => s + t, 0) / times.length).toFixed(1),
    minMs: +times[0].toFixed(1),
    maxMs: +times[times.length - 1].toFixed(1),
    p50Ms: +times[Math.floor(times.length * 0.5)].toFixed(1),
    p95Ms: +times[Math.floor(times.length * 0.95)].toFixed(1),
  };

  console.log(
    `  ${name} [${language}]: avg=${result.avgMs}ms min=${result.minMs}ms p95=${result.p95Ms}ms`,
  );
  return result;
}

// ═══ Search-path micro-benchmarks ═══════════════════════════════════════════
// Measures wall-clock cost on the FTS5 search hot path:
//   - fuzzy-correct LRU cache: repeat-typo lookup, cold vs warm
//   - token dedup: FTS5 MATCH cost with/without duplicated query tokens
// Uses the real ContentStore for the cache path and raw FTS5 for the dedup
// path (raw FTS5 isolates the engine-side cost without hitting ContentStore's
// pre-deduped sanitize).

const SEARCH_N_DOCS = 5000;
const SEARCH_N_ITERS = 2000;
const SEARCH_TOPICS = [
  "error", "database", "connection", "timeout", "server", "authentication",
  "middleware", "handler", "controller", "endpoint", "request", "response",
  "session", "cookie", "token", "signature", "encryption", "compression",
  "throttle", "retry", "backoff", "deadline", "cancelled", "succeeded",
  "failed", "warning", "notice", "debug", "trace", "panic", "fatal",
];

function usPerCall(fn: () => void, iters: number): number {
  const t0 = process.hrtime.bigint();
  for (let i = 0; i < iters; i++) fn();
  return Number(process.hrtime.bigint() - t0) / 1e3 / iters;
}

function cleanupSearchDB(path: string): void {
  for (const p of [path, `${path}-wal`, `${path}-shm`]) {
    try { rmSync(p); } catch { /* ignore */ }
  }
}

function benchFuzzyCache(): { cold: number; warm: number } {
  const dbPath = join(tmpdir(), `bench-fuzzy-${Date.now()}.db`);
  const store = new ContentStore(dbPath);
  try {
    for (let i = 0; i < SEARCH_N_DOCS; i++) {
      const body = SEARCH_TOPICS.map((w) => `${w}${i % 13}`).join(" ") + ` doc_${i}`;
      store.indexPlainText(body, `src_${i}`);
    }
    const typo = "erorr"; // edit distance 2 from "error"
    const t0 = process.hrtime.bigint();
    store.fuzzyCorrect(typo);
    const cold = Number(process.hrtime.bigint() - t0) / 1e3;
    const warm = usPerCall(() => { store.fuzzyCorrect(typo); }, SEARCH_N_ITERS);
    return { cold, warm };
  } finally {
    (store as unknown as { close?: () => void }).close?.();
    cleanupSearchDB(dbPath);
  }
}

function benchTokenDedup(): { dup: number; deduped: number } {
  const dbPath = join(tmpdir(), `bench-dedup-${Date.now()}.db`);
  const db = new Database(dbPath);
  try {
    db.pragma("journal_mode = WAL");
    db.pragma("synchronous = NORMAL");
    db.exec(`CREATE VIRTUAL TABLE fts USING fts5(content, source);`);
    const insert = db.prepare("INSERT INTO fts (content, source) VALUES (?, ?)");
    const tx = db.transaction((n: number) => {
      for (let i = 0; i < n; i++) {
        const body = SEARCH_TOPICS.map((w) => `${w}${i % 13}`).join(" ") + ` doc_${i}`;
        insert.run(body, `src_${i}`);
      }
    });
    tx(SEARCH_N_DOCS);
    const stmt = db.prepare(
      `SELECT source FROM fts WHERE fts MATCH ? ORDER BY bm25(fts) LIMIT 10`,
    );
    const dupQuery = `"error" AND "error" AND "error" AND "error" AND "error"`;
    const oneQuery = `"error"`;
    for (let i = 0; i < 100; i++) { stmt.all(dupQuery); stmt.all(oneQuery); }
    const dup = usPerCall(() => { stmt.all(dupQuery); }, SEARCH_N_ITERS);
    const deduped = usPerCall(() => { stmt.all(oneQuery); }, SEARCH_N_ITERS);
    return { dup, deduped };
  } finally {
    db.close();
    cleanupSearchDB(dbPath);
  }
}

function printTable(results: BenchResult[]) {
  console.log(
    "\n| Benchmark                     | Lang       | Avg (ms) | Min (ms) | P50 (ms) | P95 (ms) | Max (ms) |",
  );
  console.log(
    "|-------------------------------|------------|----------|----------|----------|----------|----------|",
  );
  for (const r of results) {
    console.log(
      `| ${r.name.padEnd(29)} | ${r.language.padEnd(10)} | ${String(r.avgMs).padStart(8)} | ${String(r.minMs).padStart(8)} | ${String(r.p50Ms).padStart(8)} | ${String(r.p95Ms).padStart(8)} | ${String(r.maxMs).padStart(8)} |`,
    );
  }
}

async function main() {
  console.log("Context Mode — Performance Benchmarks");
  console.log("======================================\n");
  console.log("System:");
  console.log(getRuntimeSummary(runtimes));
  console.log(
    `\nBun detected: ${hasBunRuntime() ? "YES (fast path)" : "NO (using Node.js)"}`,
  );
  console.log();

  const results: BenchResult[] = [];

  // === 1. Hello World (Cold Start Overhead) ===
  console.log("1. Hello World (measures cold start overhead):");
  const r1 = await bench(
    "hello-world",
    "javascript",
    'console.log("hello");',
  );
  if (r1) results.push(r1);

  const r2 = await bench(
    "hello-world",
    "typescript",
    'const m: string = "hello"; console.log(m);',
  );
  if (r2) results.push(r2);

  const r3 = await bench("hello-world", "python", 'print("hello")');
  if (r3) results.push(r3);

  const r4 = await bench("hello-world", "shell", 'echo "hello"');
  if (r4) results.push(r4);

  const r5 = await bench("hello-world", "ruby", 'puts "hello"');
  if (r5) results.push(r5);

  const r6 = await bench("hello-world", "perl", 'print "hello\\n";');
  if (r6) results.push(r6);

  const r7 = await bench("hello-world", "php", 'echo "hello\\n";');
  if (r7) results.push(r7);

  // === 2. JSON Processing ===
  console.log("\n2. JSON Processing (1000 items → summary):");
  const r8 = await bench(
    "json-process",
    "javascript",
    `
    const data = Array.from({length: 1000}, (_, i) => ({ id: i, v: Math.random() }));
    const sum = data.reduce((s, d) => s + d.v, 0);
    console.log(JSON.stringify({ count: data.length, sum: sum.toFixed(2) }));
  `,
  );
  if (r8) results.push(r8);

  const r9 = await bench(
    "json-process",
    "python",
    `
import json, random
data = [{"id": i, "v": random.random()} for i in range(1000)]
total = sum(d["v"] for d in data)
print(json.dumps({"count": len(data), "sum": round(total, 2)}))
  `,
  );
  if (r9) results.push(r9);

  const r10 = await bench(
    "json-process",
    "ruby",
    `
require 'json'
data = (0...1000).map { |i| { id: i, v: rand } }
total = data.sum { |d| d[:v] }
puts JSON.generate({ count: data.length, sum: total.round(2) })
  `,
  );
  if (r10) results.push(r10);

  // === 3. String Processing (10K lines) ===
  console.log("\n3. String Processing (10K lines → filter):");
  const r11 = await bench(
    "string-10k-filter",
    "javascript",
    `
    const lines = Array.from({length: 10000}, (_, i) => "line " + i + ": " + "x".repeat(80));
    const filtered = lines.filter(l => l.includes("999"));
    console.log("filtered:", filtered.length);
  `,
  );
  if (r11) results.push(r11);

  const r12 = await bench(
    "string-10k-filter",
    "python",
    `
lines = [f"line {i}: {'x' * 80}" for i in range(10000)]
filtered = [l for l in lines if "999" in l]
print(f"filtered: {len(filtered)}")
  `,
  );
  if (r12) results.push(r12);

  const r13 = await bench(
    "string-10k-filter",
    "shell",
    `seq 1 10000 | while read i; do echo "line $i"; done | grep "999" | wc -l | tr -d ' '`,
  );
  if (r13) results.push(r13);

  // === 4. Output Size ===
  console.log("\n4. Output Size (measures stream processing):");
  const r14 = await bench(
    "output-1kb",
    "javascript",
    'console.log("x".repeat(1024));',
  );
  if (r14) results.push(r14);

  const r15 = await bench(
    "output-10kb",
    "javascript",
    'console.log("x".repeat(10240));',
  );
  if (r15) results.push(r15);

  const r16 = await bench(
    "output-50kb",
    "javascript",
    'console.log("x".repeat(51200));',
  );
  if (r16) results.push(r16);

  const r17 = await bench(
    "output-100kb",
    "javascript",
    'console.log("x".repeat(102400));',
  );
  if (r17) results.push(r17);

  // === 5. Concurrent Execution ===
  console.log("\n5. Concurrent Execution:");
  for (const concurrency of [1, 5, 10, 20]) {
    const start = performance.now();
    const promises = Array.from({ length: concurrency }, (_, i) =>
      executor.execute({
        language: "javascript",
        code: `console.log("c${i}");`,
      }),
    );
    await Promise.all(promises);
    const total = performance.now() - start;
    const perTask = total / concurrency;
    console.log(
      `  ${concurrency} concurrent: ${total.toFixed(0)}ms total, ${perTask.toFixed(1)}ms/task`,
    );
  }

  // === 6. Context Savings Simulation ===
  console.log("\n6. Context Savings (simulated real workloads):");

  const scenarios = [
    {
      name: "API Response (200 users)",
      rawSize: 50_000,
      code: `
        const data = Array.from({length: 200}, (_, i) => ({
          id: i, name: "User " + i, email: "u" + i + "@example.com",
          role: i % 5 === 0 ? "admin" : "user",
          meta: { logins: Math.floor(Math.random() * 100) }
        }));
        const admins = data.filter(u => u.role === "admin");
        console.log("Total:", data.length, "Admins:", admins.length);
      `,
    },
    {
      name: "Build Output (500 lines)",
      rawSize: 25_000,
      code: `
        const lines = Array.from({length: 500}, (_, i) => {
          const type = ["OK", "WARN", "ERROR"][Math.floor(Math.random() * 3)];
          return type + " module" + i;
        });
        const errors = lines.filter(l => l.startsWith("ERROR")).length;
        const warns = lines.filter(l => l.startsWith("WARN")).length;
        console.log("Total:", lines.length, "Errors:", errors, "Warnings:", warns);
      `,
    },
    {
      name: "Log File (1000 entries)",
      rawSize: 80_000,
      code: `
        const entries = Array.from({length: 1000}, (_, i) => ({
          ts: new Date(Date.now() - i * 60000).toISOString(),
          level: ["INFO","WARN","ERROR"][Math.floor(Math.random() * 3)],
          msg: "Event " + i
        }));
        const errors = entries.filter(e => e.level === "ERROR");
        console.log("Entries:", entries.length, "Errors:", errors.length);
        console.log("Recent errors:", errors.slice(0, 3).map(e => e.msg).join(", "));
      `,
    },
    {
      name: "npm ls output",
      rawSize: 40_000,
      code: `
        const deps = Array.from({length: 150}, (_, i) => ({
          name: "pkg-" + i,
          version: Math.floor(Math.random()*10) + "." + Math.floor(Math.random()*20) + ".0",
          depth: Math.floor(Math.random() * 4)
        }));
        const top = deps.filter(d => d.depth === 0);
        console.log("Total:", deps.length, "Top-level:", top.length);
      `,
    },
  ];

  for (const s of scenarios) {
    const r = await executor.execute({
      language: "javascript",
      code: s.code,
    });
    const savings = ((1 - r.stdout.length / s.rawSize) * 100).toFixed(0);
    console.log(
      `  ${s.name}: ${r.stdout.length} bytes output (was ~${(s.rawSize / 1024).toFixed(0)}KB) → ${savings}% context saved`,
    );
  }

  // === Search Path Performance (FTS5 hot path) ===
  console.log("\n=== Search Path Performance ===");
  console.log(
    `Setup: ${SEARCH_N_DOCS} seeded documents, ${SEARCH_N_ITERS} iterations per measurement`,
  );

  const fuzzy = benchFuzzyCache();
  console.log("\nfuzzy-correct LRU cache (ContentStore)");
  console.log(`  cold (1st call, levenshtein over vocab) : ${fuzzy.cold.toFixed(1)} µs`);
  console.log(`  warm (cache hit, avg of ${SEARCH_N_ITERS})      : ${fuzzy.warm.toFixed(2)} µs`);
  console.log(`  speedup                                  : ${(fuzzy.cold / fuzzy.warm).toFixed(0)}×`);

  const dedup = benchTokenDedup();
  console.log(`\ntoken dedup (raw FTS5, ${SEARCH_N_DOCS} docs)`);
  console.log(`  5× duplicate tokens (pre-dedup) : ${dedup.dup.toFixed(1)} µs/query`);
  console.log(`  1 token (post-dedup)            : ${dedup.deduped.toFixed(1)} µs/query`);
  console.log(`  speedup from dedup              : ${(dedup.dup / dedup.deduped).toFixed(2)}×`);

  // === Print Summary Table ===
  console.log("\n=== Full Results Table ===");
  printTable(results);

  // === Comparison Note ===
  console.log("\n=== Comparison: context-mode vs raw cat/bash ===");
  console.log(
    "When Claude Code uses cat/head/Read to view a 50KB file, ALL 50KB enters context.",
  );
  console.log(
    "With context-mode execute_file, only the summary (typically 100-500 bytes) enters context.",
  );
  console.log("This means 95-99% context savings on large files.\n");
}

main().catch((err) => {
  console.error("Benchmark error:", err);
  process.exit(1);
});
