"""
Benchmark: Redline cost model accuracy.

Creates test functions with KNOWN allocation/syscall/latency costs.
Compiles each with Redline constraints that should pass or fail.
Reports true positives, false positives, false negatives.
"""

import subprocess
import tempfile
import os
import json
import shutil

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

# Each test case: (name, source, should_compile)
# "should_compile" = True means Redline should accept it (constraints satisfied)
# "should_compile" = False means Redline should reject it (constraint violated)
TEST_CASES = [
    # --- True negatives: code violates constraints, Redline should reject ---
    (
        "1_alloc_with_limit_0",
        """
use redline::redline;
#[redline(allocs = 0)]
fn one_alloc() -> String {
    String::from("hello")
}
fn main() {}
""",
        False,
        {"expected_allocs": 1, "constraint_allocs": 0},
    ),
    (
        "3_allocs_with_limit_2",
        """
use redline::redline;
#[redline(allocs = 2)]
fn three_allocs() -> (String, String, String) {
    let a = String::from("a");
    let b = String::from("b");
    let c = String::from("c");
    (a, b, c)
}
fn main() {}
""",
        False,
        {"expected_allocs": 3, "constraint_allocs": 2},
    ),
    (
        "1_syscall_with_limit_0",
        """
use redline::redline;
#[redline(syscalls = 0)]
fn does_io() {
    println!("hello");
}
fn main() {}
""",
        False,
        {"expected_syscalls": 1, "constraint_syscalls": 0},
    ),
    (
        "vec_macro_alloc",
        """
use redline::redline;
#[redline(allocs = 0)]
fn uses_vec() -> Vec<u8> {
    vec![1, 2, 3]
}
fn main() {}
""",
        False,
        {"expected_allocs": 1, "constraint_allocs": 0},
    ),
    (
        "loop_latency_exceeded",
        """
use redline::redline;
#[redline(latency = "< 1us")]
fn big_loop() -> u64 {
    let mut s: u64 = 0;
    for i in 0..10000 {
        s += i;
    }
    s
}
fn main() {}
""",
        False,
        {"note": "10000 iterations * ALU cost exceeds 1us"},
    ),
    (
        "format_macro_alloc",
        """
use redline::redline;
#[redline(allocs = 0)]
fn uses_format() -> String {
    format!("hello {}", 42)
}
fn main() {}
""",
        False,
        {"expected_allocs": 1, "constraint_allocs": 0},
    ),
    (
        "network_syscall",
        """
use redline::redline;
use std::net::TcpStream;
#[redline(syscalls = 0)]
fn connects() {
    TcpStream::connect("127.0.0.1:80");
}
fn main() {}
""",
        False,
        {"expected_syscalls": 1, "constraint_syscalls": 0},
    ),
    # --- True positives: code satisfies constraints, Redline should accept ---
    (
        "pure_arithmetic_passes",
        """
use redline::redline;
#[redline(allocs = 0, syscalls = 0, latency = "< 1ms")]
fn pure_math(a: u64, b: u64) -> u64 {
    let c = a + b;
    let d = c * 2;
    d / 3
}
fn main() {}
""",
        True,
        {"expected_allocs": 0, "expected_syscalls": 0},
    ),
    (
        "bounded_loop_within_latency",
        """
use redline::redline;
#[redline(latency = "< 1ms")]
fn small_loop() -> u64 {
    let mut s: u64 = 0;
    for i in 0..100 {
        s += i;
    }
    s
}
fn main() {}
""",
        True,
        {"note": "100 iterations * 1ns ALU = 100ns, well under 1ms"},
    ),
    (
        "branching_no_allocs",
        """
use redline::redline;
#[redline(allocs = 0)]
fn branch(x: i32) -> i32 {
    if x > 0 {
        x * 2
    } else {
        x + 1
    }
}
fn main() {}
""",
        True,
        {"expected_allocs": 0},
    ),
    (
        "match_no_syscalls",
        """
use redline::redline;
#[redline(syscalls = 0)]
fn matcher(x: u8) -> u8 {
    match x {
        0 => 10,
        1 => 20,
        2 => 30,
        _ => 0,
    }
}
fn main() {}
""",
        True,
        {"expected_syscalls": 0},
    ),
    (
        "multiple_constraints_pass",
        """
use redline::redline;
#[redline(latency = "< 100us", allocs = 0, syscalls = 0)]
fn tight(data: &[u8]) -> u64 {
    let mut sum: u64 = 0;
    for i in 0..50 {
        sum += data.get(i).copied().unwrap_or(0) as u64;
    }
    sum
}
fn main() {}
""",
        True,
        {"expected_allocs": 0, "expected_syscalls": 0},
    ),
    # --- Edge cases ---
    (
        "allocs_at_exact_limit",
        """
use redline::redline;
#[redline(allocs = "< 4")]
fn exactly_three() -> (String, String, String) {
    let a = String::from("a");
    let b = String::from("b");
    let c = String::from("c");
    (a, b, c)
}
fn main() {}
""",
        True,
        {"expected_allocs": 3, "constraint_allocs": "< 4"},
    ),
    (
        "nested_loop_latency",
        """
use redline::redline;
#[redline(latency = "< 100us")]
fn nested() -> u64 {
    let mut s: u64 = 0;
    for _i in 0..10 {
        for _j in 0..10 {
            s += 1;
        }
    }
    s
}
fn main() {}
""",
        True,
        {"note": "10*10*1ns = 100ns, well under 100us"},
    ),
]


def compile_test(source: str, label: str) -> bool:
    """Returns True if the code compiles successfully."""
    tmpdir = tempfile.mkdtemp(prefix="redline_acc_")
    try:
        src_dir = os.path.join(tmpdir, "src")
        os.makedirs(src_dir)

        cargo_toml = f"""[package]
name = "redline-acc-{label}"
version = "0.0.1"
edition = "2021"

[dependencies]
redline = {{ package = "alia-redline", path = "{REDLINE_ROOT.replace(os.sep, '/')}" }}
"""
        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(source)

        env = os.environ.copy()
        env["PATH"] = os.path.expanduser("~/.cargo/bin") + os.pathsep + env.get("PATH", "")
        result = subprocess.run(
            ["cargo", "check"],
            cwd=tmpdir,
            capture_output=True,
            env=env,
            timeout=60,
        )
        return result.returncode == 0
    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)


def run_accuracy():
    print("=== Redline Accuracy Benchmark ===\n")

    tp = 0  # true positive: should compile, did compile
    tn = 0  # true negative: should fail, did fail
    fp = 0  # false positive: should fail, but compiled
    fn_ = 0  # false negative: should compile, but failed

    details = []

    for name, source, should_compile, meta in TEST_CASES:
        compiled = compile_test(source, name.replace(" ", "_"))
        correct = compiled == should_compile

        if should_compile and compiled:
            tp += 1
            verdict = "TP"
        elif not should_compile and not compiled:
            tn += 1
            verdict = "TN"
        elif not should_compile and compiled:
            fp += 1
            verdict = "FP"
        else:
            fn_ += 1
            verdict = "FN"

        status = "PASS" if correct else "FAIL"
        print(f"  [{status}] {name}: expected={'compile' if should_compile else 'reject'}, got={'compile' if compiled else 'reject'} ({verdict})")

        details.append({
            "name": name,
            "should_compile": should_compile,
            "did_compile": compiled,
            "correct": correct,
            "verdict": verdict,
            "meta": meta,
        })

    total = tp + tn + fp + fn_
    accuracy = (tp + tn) / total * 100 if total > 0 else 0
    precision = tp / (tp + fp) * 100 if (tp + fp) > 0 else 0
    recall = tp / (tp + fn_) * 100 if (tp + fn_) > 0 else 0

    print(f"\n--- Summary ---")
    print(f"Total: {total}  TP: {tp}  TN: {tn}  FP: {fp}  FN: {fn_}")
    print(f"Accuracy: {accuracy:.1f}%  Precision: {precision:.1f}%  Recall: {recall:.1f}%")

    results = {
        "total": total,
        "true_positives": tp,
        "true_negatives": tn,
        "false_positives": fp,
        "false_negatives": fn_,
        "accuracy_pct": round(accuracy, 1),
        "precision_pct": round(precision, 1),
        "recall_pct": round(recall, 1),
        "details": details,
    }
    return results


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