"""
Benchmark: Redline cost model estimates vs actual runtime.

For each operation in Redline's cost model, measures actual wall-clock time
on this machine and compares against the static estimate.

Operations tested:
  - ALU (arithmetic)
  - Function call overhead
  - Vec allocation
  - String allocation
  - HashMap lookup
  - HashMap insert
  - Mutex lock/unlock
  - format!() string formatting
  - println!() / write to stdout
  - Syscall (file open+close)
  - Index into array
"""

import subprocess
import tempfile
import os
import json
import shutil

REDLINE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Redline's static cost model (from src/cost.rs)
REDLINE_COSTS = {
    "alu": 1,
    "call": 5,
    "index": 1,
    "hashmap_lookup": 25,
    "hashmap_insert": 50,
    "alloc_vec": 50,
    "alloc_string": 50,
    "mutex_lock": 100,
    "format": 200,
    "syscall_file": 1000,
    "println": 5000,
}

# Rust program that measures each operation.
# Uses std::hint::black_box (stable since 1.66) to prevent optimization.
MEASURE_SOURCE = r"""
use std::collections::HashMap;
use std::sync::Mutex;
use std::time::Instant;
use std::hint::black_box;
use std::io::Write;

const ITERS: u64 = 1_000_000;
const ITERS_SLOW: u64 = 10_000;

fn emit(name: &str, iterations: u64, elapsed_ns: u128) {
    let per_op = elapsed_ns as f64 / iterations as f64;
    println!("RESULT:{}:{:.2}", name, per_op);
}

#[inline(never)]
fn noop_fn(x: u64) -> u64 { x + 1 }

fn main() {
    // ALU: a + b
    {
        for _ in 0..1000 { black_box(black_box(42u64) + black_box(13u64)); }
        let t0 = Instant::now();
        for _ in 0..ITERS { black_box(black_box(42u64) + black_box(13u64)); }
        emit("alu", ITERS, t0.elapsed().as_nanos());
    }

    // Function call
    {
        for _ in 0..1000 { black_box(noop_fn(black_box(42))); }
        let t0 = Instant::now();
        for _ in 0..ITERS { black_box(noop_fn(black_box(42))); }
        emit("call", ITERS, t0.elapsed().as_nanos());
    }

    // Index
    {
        let arr = [0u8; 256];
        for _ in 0..1000 { black_box(arr[black_box(42usize)]); }
        let t0 = Instant::now();
        for _ in 0..ITERS { black_box(arr[black_box(42usize)]); }
        emit("index", ITERS, t0.elapsed().as_nanos());
    }

    // Vec allocation (alloc + drop)
    {
        for _ in 0..1000 {
            let v: Vec<u8> = Vec::with_capacity(64);
            black_box(&v);
        }
        let t0 = Instant::now();
        for _ in 0..ITERS {
            let v: Vec<u8> = Vec::with_capacity(64);
            black_box(&v);
        }
        emit("alloc_vec", ITERS, t0.elapsed().as_nanos());
    }

    // String allocation (alloc + copy + drop)
    {
        for _ in 0..1000 {
            let s = String::from("hello world");
            black_box(&s);
        }
        let t0 = Instant::now();
        for _ in 0..ITERS {
            let s = String::from("hello world");
            black_box(&s);
        }
        emit("alloc_string", ITERS, t0.elapsed().as_nanos());
    }

    // HashMap lookup
    {
        let mut map = HashMap::new();
        for i in 0i64..100 { map.insert(i, i * 2); }
        for _ in 0..1000 { black_box(map.get(&42i64)); }
        let t0 = Instant::now();
        for _ in 0..ITERS { black_box(map.get(black_box(&42i64))); }
        emit("hashmap_lookup", ITERS, t0.elapsed().as_nanos());
    }

    // HashMap insert (new map each time = alloc + insert + drop)
    {
        for _ in 0..1000 {
            let mut m: HashMap<i64, i64> = HashMap::new();
            m.insert(1, 2);
            black_box(&m);
        }
        let t0 = Instant::now();
        for _ in 0..ITERS_SLOW {
            let mut m: HashMap<i64, i64> = HashMap::new();
            m.insert(black_box(1), 2);
            black_box(&m);
        }
        emit("hashmap_insert", ITERS_SLOW, t0.elapsed().as_nanos());
    }

    // Mutex lock (uncontended)
    {
        let m = Mutex::new(0u64);
        for _ in 0..1000 { *m.lock().unwrap() += 1; }
        let t0 = Instant::now();
        for _ in 0..ITERS { *m.lock().unwrap() += 1; }
        emit("mutex_lock", ITERS, t0.elapsed().as_nanos());
    }

    // format!
    {
        for _ in 0..1000 {
            let s = format!("hello {} world {}", 42, 99);
            black_box(&s);
        }
        let t0 = Instant::now();
        for _ in 0..ITERS_SLOW {
            let s = format!("hello {} world {}", black_box(42), black_box(99));
            black_box(&s);
        }
        emit("format", ITERS_SLOW, t0.elapsed().as_nanos());
    }

    // Syscall: file open + close
    {
        let path = std::env::temp_dir().join("redline_bench_syscall.tmp");
        std::fs::write(&path, b"test").unwrap();
        for _ in 0..100 {
            let f = std::fs::File::open(&path).unwrap();
            black_box(&f);
        }
        let t0 = Instant::now();
        for _ in 0..ITERS_SLOW {
            let f = std::fs::File::open(&path).unwrap();
            black_box(&f);
        }
        emit("syscall_file", ITERS_SLOW, t0.elapsed().as_nanos());
        let _ = std::fs::remove_file(&path);
    }

    // println (write to io::sink, measures format + write overhead without actual I/O)
    {
        for _ in 0..100 {
            let mut s = std::io::sink();
            writeln!(s, "hello {}", 42).unwrap();
        }
        let t0 = Instant::now();
        for _ in 0..ITERS_SLOW {
            let mut s = std::io::sink();
            writeln!(s, "hello {}", black_box(42)).unwrap();
        }
        emit("println", ITERS_SLOW, t0.elapsed().as_nanos());
    }
}
"""


def run_cost_model_bench():
    print("=== Redline Cost Model Benchmark ===\n")

    tmpdir = tempfile.mkdtemp(prefix="redline_cost_")
    try:
        src_dir = os.path.join(tmpdir, "src")
        os.makedirs(src_dir)

        cargo_toml = """[package]
name = "redline-cost-bench"
version = "0.0.1"
edition = "2021"

[profile.release]
opt-level = 2
"""
        with open(os.path.join(tmpdir, "Cargo.toml"), "w") as f:
            f.write(cargo_toml)
        with open(os.path.join(src_dir, "main.rs"), "w") as f:
            f.write(MEASURE_SOURCE)

        env = os.environ.copy()
        env["PATH"] = os.path.expanduser("~/.cargo/bin") + os.pathsep + env.get("PATH", "")

        # Build release
        print("Building measurement binary (release)...")
        result = subprocess.run(
            ["cargo", "build", "--release"],
            cwd=tmpdir,
            capture_output=True,
            env=env,
            timeout=120,
        )
        if result.returncode != 0:
            print(f"Build failed:\n{result.stderr.decode()}")
            return None

        # Run
        print("Running measurements...\n")
        if os.name == "nt":
            binary = os.path.join(tmpdir, "target", "release", "redline-cost-bench.exe")
        else:
            binary = os.path.join(tmpdir, "target", "release", "redline-cost-bench")

        result = subprocess.run(
            [binary],
            capture_output=True,
            timeout=120,
        )
        output = result.stdout.decode("utf-8", errors="replace")
        stderr_out = result.stderr.decode("utf-8", errors="replace")

        if result.returncode != 0:
            print(f"Run failed (exit {result.returncode}):\n{stderr_out}\n{output}")
            return None

        # Parse results
        actual = {}
        for line in output.splitlines():
            line = line.strip()
            if line.startswith("RESULT:"):
                parts = line.split(":")
                if len(parts) >= 3:
                    name = parts[1]
                    ns = float(parts[2])
                    actual[name] = ns

        # Compare
        comparisons = []
        print(f"{'Operation':<20} {'Redline (ns)':<14} {'Actual (ns)':<14} {'Ratio':<10} {'Verdict'}")
        print("-" * 72)

        for op, estimated_ns in REDLINE_COSTS.items():
            if op in actual:
                actual_ns = actual[op]
                ratio = estimated_ns / actual_ns if actual_ns > 0 else float("inf")
                # Conservative means estimate >= actual (ratio >= 1.0)
                if ratio >= 1.0:
                    verdict = "OK (conservative)"
                elif ratio >= 0.5:
                    verdict = "WARN (under-estimate)"
                else:
                    verdict = "SEVERE UNDER-ESTIMATE"

                print(f"{op:<20} {estimated_ns:<14} {actual_ns:<14.2f} {ratio:<10.2f} {verdict}")

                comparisons.append({
                    "operation": op,
                    "estimated_ns": estimated_ns,
                    "actual_ns": round(actual_ns, 2),
                    "ratio": round(ratio, 3),
                    "verdict": verdict,
                })

        # Summary stats
        ratios = [c["ratio"] for c in comparisons]
        avg_ratio = sum(ratios) / len(ratios) if ratios else 0
        conservative_count = sum(1 for c in comparisons if c["ratio"] >= 1.0)

        print(f"\n--- Summary ---")
        print(f"Operations tested: {len(comparisons)}")
        print(f"Conservative (estimate >= actual): {conservative_count}/{len(comparisons)}")
        print(f"Average estimate/actual ratio: {avg_ratio:.2f}")

        results = {
            "comparisons": comparisons,
            "summary": {
                "total_ops": len(comparisons),
                "conservative_count": conservative_count,
                "avg_ratio": round(avg_ratio, 3),
            },
        }
        return results
    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)


if __name__ == "__main__":
    results = run_cost_model_bench()
    if results:
        out_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cost_model_results.json")
        with open(out_path, "w") as f:
            json.dump(results, f, indent=2)
        print(f"\nResults saved to {out_path}")
