use lean_ctx::core::patterns::git;
use lean_ctx::core::tokens::count_tokens;

fn measure_compression(command: &str, output: &str) -> (usize, usize, f64) {
    let original = count_tokens(output);
    let compressed = git::compress(command, output).unwrap_or_else(|| output.to_string());
    let compressed_tokens = count_tokens(&compressed);
    let savings_pct = if original > 0 {
        (original - compressed_tokens) as f64 / original as f64 * 100.0
    } else {
        0.0
    };
    (original, compressed_tokens, savings_pct)
}

#[test]
fn verify_git_log_patch_savings() {
    let output = generate_git_log_patch(10);
    let (orig, comp, pct) = measure_compression("git log -p", &output);
    eprintln!("[git log -p, 10 commits] {orig} → {comp} tokens ({pct:.1}% saved)");
    assert!(
        pct > 70.0,
        "git log -p should save >70% tokens, got {pct:.1}%"
    );
}

#[test]
fn verify_git_log_patch_large_savings() {
    let output = generate_git_log_patch(50);
    let (orig, comp, pct) = measure_compression("git log -p", &output);
    eprintln!("[git log -p, 50 commits] {orig} → {comp} tokens ({pct:.1}% saved)");
    assert!(
        pct > 80.0,
        "git log -p 50 commits should save >80% tokens, got {pct:.1}%"
    );
}

#[test]
fn verify_git_log_stat_savings() {
    let output = generate_git_log_stat(20);
    let (orig, comp, pct) = measure_compression("git log --stat", &output);
    eprintln!("[git log --stat, 20 commits] {orig} → {comp} tokens ({pct:.1}% saved)");
    assert!(
        pct > 50.0,
        "git log --stat should save >50% tokens, got {pct:.1}%"
    );
}

#[test]
fn verify_git_log_standard_savings() {
    let output = generate_git_log_standard(30);
    let (orig, comp, pct) = measure_compression("git log", &output);
    eprintln!("[git log standard, 30 commits] {orig} → {comp} tokens ({pct:.1}% saved)");
    assert!(
        pct > 60.0,
        "git log standard 30 commits should save >60%, got {pct:.1}%"
    );
}

#[test]
fn verify_git_log_oneline_short_no_regression() {
    let output = "abc1234 feat: one\ndef5678 fix: two\nghi9012 docs: three";
    let (orig, comp, _pct) = measure_compression("git log --oneline", output);
    eprintln!("[git log --oneline, 3 entries] {orig} → {comp} tokens");
    assert_eq!(orig, comp, "short oneline should pass through unchanged");
}

#[test]
fn verify_git_commit_feature_branch_savings() {
    let output = "[feature/my-cool-branch abc1234] feat: implement new feature\n \
                  3 files changed, 45 insertions(+), 12 deletions(-)\n \
                  create mode 100644 src/new_module.rs\n";
    let (orig, comp, pct) = measure_compression("git commit -m 'feat'", output);
    let compressed = git::compress("git commit -m 'feat'", output).unwrap();
    eprintln!(
        "[git commit feature/branch] {orig} → {comp} tokens ({pct:.1}% saved)\n  result: {compressed}"
    );
    assert!(compressed.contains("abc1234"), "must contain hash");
    assert!(
        compressed.contains("feature/my-cool-branch"),
        "must contain branch name"
    );
}

#[test]
fn verify_git_commit_many_hooks_savings() {
    let mut output = String::new();
    for i in 0..50 {
        output.push_str(&format!("check-{i:02}..................passed\n"));
    }
    output.push_str("[main abc1234] fix: resolve lint issues\n");
    output.push_str(" 5 files changed, 30 insertions(+), 10 deletions(-)\n");

    let (orig, comp, pct) = measure_compression("git commit -m 'fix'", &output);
    let compressed = git::compress("git commit -m 'fix'", &output).unwrap();
    eprintln!(
        "[git commit 50 hooks] {orig} → {comp} tokens ({pct:.1}% saved)\n  result: {compressed}"
    );
    assert!(
        pct > 60.0,
        "50 hook lines should compress >60%, got {pct:.1}%"
    );
    assert!(
        compressed.contains("hooks passed"),
        "should summarize hooks"
    );
    assert!(compressed.contains("abc1234"), "must contain hash");
}

#[test]
fn verify_git_commit_with_failures_preserves_errors() {
    let mut output = String::new();
    for i in 0..20 {
        output.push_str(&format!("check-{i:02}..................passed\n"));
    }
    output.push_str("ruff.......................failed\n");
    output.push_str("fixing src/app.py: E302 expected 2 blank lines\n");
    output.push_str("mypy.......................failed\n");
    output.push_str("[main abc1234] fix: lint\n");
    output.push_str(" 2 files changed, 5 insertions(+), 3 deletions(-)\n");

    let (orig, comp, pct) = measure_compression("git commit -m 'fix'", &output);
    let compressed = git::compress("git commit -m 'fix'", &output).unwrap();
    eprintln!(
        "[git commit with failures] {orig} → {comp} tokens ({pct:.1}% saved)\n  result: {compressed}"
    );
    assert!(compressed.contains("ruff"), "must preserve failed check");
    assert!(compressed.contains("mypy"), "must preserve failed check");
    assert!(compressed.contains("passed"), "should mention passed count");
}

#[test]
fn verify_overall_savings_estimation() {
    let scenarios: Vec<(&str, String, &str)> = vec![
        ("git log -p -5", generate_git_log_patch(5), "git log -p"),
        ("git log -p -20", generate_git_log_patch(20), "git log -p"),
        (
            "git log --stat -10",
            generate_git_log_stat(10),
            "git log --stat",
        ),
        ("git log -10", generate_git_log_standard(10), "git log"),
        (
            "git commit (50 hooks)",
            {
                let mut s = String::new();
                for i in 0..50 {
                    s.push_str(&format!("check-{i:02}..passed\n"));
                }
                s.push_str("[main abc1234] fix: stuff\n 1 file changed, 1 insertion(+)\n");
                s
            },
            "git commit -m 'fix'",
        ),
    ];

    eprintln!("\n{}", "=".repeat(60));
    eprintln!("  SAVINGS VERIFICATION REPORT");
    eprintln!("{}", "=".repeat(60));
    eprintln!(
        "  {:<25} {:>8} {:>8} {:>7}",
        "Scenario", "Original", "Compr.", "Saved%"
    );
    eprintln!("  {}", "-".repeat(50));

    let mut total_original = 0usize;
    let mut total_compressed = 0usize;

    for (label, output, command) in &scenarios {
        let (orig, comp, pct) = measure_compression(command, output);
        total_original += orig;
        total_compressed += comp;
        eprintln!("  {label:<25} {orig:>8} {comp:>8} {pct:>6.1}%");
    }

    let total_pct = if total_original > 0 {
        (total_original - total_compressed) as f64 / total_original as f64 * 100.0
    } else {
        0.0
    };
    eprintln!("  {}", "-".repeat(50));
    eprintln!(
        "  {:<25} {:>8} {:>8} {:>6.1}%",
        "TOTAL", total_original, total_compressed, total_pct
    );
    eprintln!("{}\n", "=".repeat(60));

    assert!(
        total_pct > 50.0,
        "overall savings should be >50%, got {total_pct:.1}%"
    );
}

#[test]
fn verify_cep_delta_tracking_prevents_overcounting() {
    use std::collections::HashMap;

    let test_dir = std::env::temp_dir().join(format!("lean-ctx-cep-test-{}", std::process::id()));
    let lean_ctx_dir = test_dir.join(".lean-ctx");
    let _ = std::fs::create_dir_all(&lean_ctx_dir);
    let stats_path = lean_ctx_dir.join("stats.json");
    let _ = std::fs::remove_file(&stats_path);

    // TODO: Audit that the environment access only happens in single-threaded code.
    unsafe {
        std::env::set_var(
            "LEAN_CTX_DATA_DIR",
            lean_ctx_dir.to_string_lossy().to_string(),
        );
    };

    let mut modes = HashMap::new();
    modes.insert("full".to_string(), 5u64);

    lean_ctx::core::stats::record_cep_session(70, 5, 10, 1000, 600, &modes, 10, "Standard");

    let store1 = lean_ctx::core::stats::load();
    let orig1 = store1.cep.total_tokens_original;
    let comp1 = store1.cep.total_tokens_compressed;
    let sessions1 = store1.cep.sessions;
    eprintln!("After call 1: orig={orig1}, comp={comp1}, sessions={sessions1}");

    lean_ctx::core::stats::record_cep_session(75, 8, 15, 2000, 1200, &modes, 20, "Standard");

    let store2 = lean_ctx::core::stats::load();
    let orig2 = store2.cep.total_tokens_original;
    let comp2 = store2.cep.total_tokens_compressed;
    let sessions2 = store2.cep.sessions;
    eprintln!("After call 2: orig={orig2}, comp={comp2}, sessions={sessions2}");

    let pid = std::process::id();
    assert_eq!(store2.cep.last_session_pid, Some(pid), "should track PID");

    assert_eq!(
        sessions2, 1,
        "should still be 1 session (same PID), got {sessions2}"
    );

    assert_eq!(
        orig2, 2000,
        "total_original should be 2000 (1000 + delta 1000), got {orig2}"
    );

    assert_eq!(
        comp2, 1200,
        "total_compressed should be 1200 (600 + delta 600), got {comp2}"
    );

    eprintln!("[CEP delta tracking] PASSED: no over-counting detected");
    eprintln!("  Call 1: 1000/600 → totals 1000/600");
    eprintln!("  Call 2: 2000/1200 → delta 1000/600 → totals 2000/1200");
    eprintln!("  Without fix: totals would be 3000/1800 (1000+2000 / 600+1200)");

    let _ = std::fs::remove_dir_all(&test_dir);
    // TODO: Audit that the environment access only happens in single-threaded code.
    unsafe { std::env::remove_var("LEAN_CTX_DATA_DIR") };
}

#[test]
#[ignore = "requires manual setup: git log -p -10 > /tmp/git_log_raw.txt"]
fn e2e_real_git_log_compression() {
    let raw_path = "/tmp/git_log_raw.txt";
    if !std::path::Path::new(raw_path).exists() {
        eprintln!(
            "[SKIP] /tmp/git_log_raw.txt not found — run: git log -p -10 > /tmp/git_log_raw.txt"
        );
        return;
    }
    let raw = std::fs::read_to_string(raw_path).unwrap();
    let original = count_tokens(&raw);
    let compressed = git::compress("git log -p -10", &raw).unwrap_or_else(|| raw.clone());
    let compressed_tokens = count_tokens(&compressed);
    let saved = original.saturating_sub(compressed_tokens);
    let pct = if original > 0 {
        saved as f64 / original as f64 * 100.0
    } else {
        0.0
    };

    eprintln!("\n=== E2E: Real git log -p -10 ===");
    eprintln!("Raw: {} bytes, {} lines", raw.len(), raw.lines().count());
    eprintln!("Original: {original} tokens");
    eprintln!("Compressed: {compressed_tokens} tokens");
    eprintln!("Saved: {saved} tokens ({pct:.1}%)");
    eprintln!("\n=== Compressed output ===");
    eprintln!("{}", compressed.chars().take(800).collect::<String>());

    assert!(
        pct > 80.0,
        "real git log -p should save >80%, got {pct:.1}%"
    );
}

#[test]
fn e2e_real_git_log_stat_compression() {
    let raw = std::process::Command::new("git")
        .args(["log", "--stat", "-20"])
        .current_dir(env!("CARGO_MANIFEST_DIR"))
        .output()
        .ok()
        .and_then(|o| String::from_utf8(o.stdout).ok())
        .unwrap_or_default();

    if raw.is_empty() {
        eprintln!("[SKIP] could not run git log --stat");
        return;
    }

    let original = count_tokens(&raw);
    let compressed = git::compress("git log --stat", &raw).unwrap_or_else(|| raw.clone());
    let compressed_tokens = count_tokens(&compressed);
    let saved = original.saturating_sub(compressed_tokens);
    let pct = if original > 0 {
        saved as f64 / original as f64 * 100.0
    } else {
        0.0
    };

    eprintln!("\n=== E2E: Real git log --stat -20 ===");
    eprintln!("Raw: {} bytes, {} lines", raw.len(), raw.lines().count());
    eprintln!("Original: {original} tokens → Compressed: {compressed_tokens} tokens");
    eprintln!("Saved: {saved} tokens ({pct:.1}%)");
    eprintln!("\n=== Compressed output (first 500 chars) ===");
    eprintln!("{}", compressed.chars().take(500).collect::<String>());

    assert!(
        pct > 60.0,
        "real git log --stat should save >60%, got {pct:.1}%"
    );
}

fn generate_git_log_patch(n: usize) -> String {
    let mut output = String::new();
    for i in 0..n {
        let msg = format!("implement feature number {i} with improvements");
        output.push_str(&format!(
            "commit {i:07}abc1234567890abcdef1234567890\n\
             Author: Developer <dev@example.com>\n\
             Date:   Mon Mar {d} 10:{h:02}:00 2026 +0100\n\
             \n\
             {ty}: {msg}\n\
             \n\
             diff --git a/src/module_{i}.rs b/src/module_{i}.rs\n\
             index abc1234..def5678 100644\n\
             --- a/src/module_{i}.rs\n\
             +++ b/src/module_{i}.rs\n\
             @@ -1,{ctx} +1,{ctx2} @@\n",
            d = 10 + (i % 20),
            h = i % 24,
            ty = ["feat", "fix", "refactor", "docs", "test"][i % 5],
            ctx = 10 + i,
            ctx2 = 12 + i,
        ));
        for j in 0..(5 + i % 10) {
            output.push_str(&format!(" fn existing_function_{j}() {{}}\n"));
        }
        for j in 0..(3 + i % 5) {
            output.push_str(&format!("+fn new_function_{i}_{j}() {{ todo!() }}\n"));
        }
        for j in 0..(2 + i % 3) {
            output.push_str(&format!(
                "-fn old_function_{i}_{j}() {{ unimplemented!() }}\n"
            ));
        }
        output.push('\n');
    }
    output
}

fn generate_git_log_stat(n: usize) -> String {
    let mut output = String::new();
    for i in 0..n {
        let msg = format!("update module {i}");
        output.push_str(&format!(
            "commit {i:07}abc1234567890abcdef1234567890\n\
             Author: Developer <dev@example.com>\n\
             Date:   Mon Mar {d} 10:{h:02}:00 2026 +0100\n\
             \n\
             {ty}: {msg}\n\
             \n",
            d = 10 + (i % 20),
            h = i % 24,
            ty = ["feat", "fix", "refactor", "docs", "test"][i % 5],
        ));
        for j in 0..(2 + i % 4) {
            let plus = 3 + j;
            let minus = 1 + j;
            let bar: String = "+".repeat(plus) + &"-".repeat(minus);
            output.push_str(&format!(
                " src/mod_{i}_{j}.rs | {total} {bar}\n",
                total = plus + minus,
            ));
        }
        let total_files = 2 + i % 4;
        output.push_str(&format!(
            " {total_files} files changed, {} insertions(+), {} deletions(-)\n\n",
            10 + i * 2,
            5 + i,
        ));
    }
    output
}

/// End-to-end audit: trace the full savings pipeline for each tool
/// and verify that "original" and "saved" are fair comparisons.
#[test]
fn audit_full_savings_pipeline() {
    use lean_ctx::core::cache::SessionCache;
    use lean_ctx::core::tokens::count_tokens;
    use lean_ctx::tools::CrpMode;

    eprintln!("\n{}", "=".repeat(70));
    eprintln!("  FULL SAVINGS PIPELINE AUDIT");
    eprintln!("{}", "=".repeat(70));

    // 1. ctx_read — first read in full mode: savings should be ~0
    {
        let mut cache = SessionCache::new();
        let content = "use std::io;\nfn main() {\n    println!(\"hello\");\n}\n";
        let tmp = std::env::temp_dir().join("audit_test_file.rs");
        std::fs::write(&tmp, content).unwrap();

        let output = lean_ctx::tools::ctx_read::handle(
            &mut cache,
            tmp.to_str().unwrap(),
            "full",
            CrpMode::Off,
        );
        let file_tokens = count_tokens(content);
        let output_tokens = count_tokens(&output);
        let saved = file_tokens.saturating_sub(output_tokens);

        eprintln!("\n  ctx_read (first read, full mode):");
        eprintln!("    file tokens:   {file_tokens}");
        eprintln!("    output tokens: {output_tokens}");
        eprintln!("    saved:         {saved}");
        eprintln!(
            "    contains savings line: {}",
            output.contains("tok saved")
        );

        assert!(
            output_tokens >= file_tokens,
            "first full read includes header, so output >= original: {output_tokens} vs {file_tokens}"
        );

        // 2. ctx_read — second read (cache hit): massive savings
        let output2 = lean_ctx::tools::ctx_read::handle(
            &mut cache,
            tmp.to_str().unwrap(),
            "full",
            CrpMode::Off,
        );
        let output2_tokens = count_tokens(&output2);
        let is_cache_hit =
            output2.is_empty() || output2.contains("unchanged") || output2.contains("cached");

        eprintln!("\n  ctx_read (cache re-read):");
        eprintln!("    file tokens:   {file_tokens}");
        eprintln!("    output tokens: {output2_tokens}");
        eprintln!("    is_cache_hit:  {is_cache_hit}");
        eprintln!("    output: {output2:?}");

        assert!(
            is_cache_hit,
            "second read should be a cache hit, got: {output2}"
        );
        assert!(
            output2_tokens < 40,
            "cache hit stub should be <40 tokens, got {output2_tokens}",
        );

        let _ = std::fs::remove_file(&tmp);
    }

    // 3. ctx_read — compressed mode (signatures): fair comparison
    {
        let mut cache = SessionCache::new();
        let mut code = Vec::new();
        code.push("use std::collections::HashMap;".to_string());
        for i in 0..50 {
            code.push(format!("pub fn handler_{i}(req: Request) -> Response {{"));
            code.push(format!(
                "    let data = db.query(\"SELECT * FROM table_{i}\");"
            ));
            code.push(
                "    let filtered = data.iter().filter(|r| r.active).collect::<Vec<_>>();"
                    .to_string(),
            );
            code.push("    let result = process_items(&filtered, req.params());".to_string());
            code.push("    if result.is_err() { return Response::error(500); }".to_string());
            code.push("    Response::json(result.unwrap())".to_string());
            code.push("}".to_string());
            code.push(String::new());
        }
        let content = code.join("\n");
        let tmp = std::env::temp_dir().join("audit_test_sigs.rs");
        std::fs::write(&tmp, &content).unwrap();

        let output = lean_ctx::tools::ctx_read::handle(
            &mut cache,
            tmp.to_str().unwrap(),
            "signatures",
            CrpMode::Off,
        );
        let file_tokens = count_tokens(&content);
        let output_tokens = count_tokens(&output);
        let saved = file_tokens.saturating_sub(output_tokens);
        let ratio = if file_tokens > 0 {
            saved as f64 / file_tokens as f64 * 100.0
        } else {
            0.0
        };

        eprintln!("\n  ctx_read (signatures mode, 50 multi-line fns):");
        eprintln!("    file tokens:   {file_tokens}");
        eprintln!("    output tokens: {output_tokens}");
        eprintln!("    savings:       {saved} ({ratio:.1}%)");

        assert!(
            output_tokens < file_tokens,
            "signatures should compress: {output_tokens} < {file_tokens}"
        );
        assert!(
            ratio > 20.0 && ratio < 98.0,
            "signatures savings should be 20-98%, got {ratio:.1}%"
        );

        let _ = std::fs::remove_file(&tmp);
    }

    // 4. ctx_tree — fair comparison (same depth)
    {
        let dir = env!("CARGO_MANIFEST_DIR");
        let (output, raw_tokens) = lean_ctx::tools::ctx_tree::handle(dir, 2, false, true);
        let compact_tokens = count_tokens(&output);
        let savings = raw_tokens.saturating_sub(compact_tokens);
        let ratio = if raw_tokens > 0 {
            savings as f64 / raw_tokens as f64 * 100.0
        } else {
            0.0
        };

        eprintln!("\n  ctx_tree (depth=2):");
        eprintln!("    raw tokens:     {raw_tokens}");
        eprintln!("    compact tokens: {compact_tokens}");
        eprintln!("    savings:        {savings} ({ratio:.1}%)");

        assert!(
            raw_tokens < 3000,
            "raw tree at depth 2 must be <3000 tokens, got {raw_tokens}"
        );
        assert!(
            ratio < 80.0,
            "tree savings ratio should be <80% for fair comparison, got {ratio:.1}%"
        );
    }

    // 5. ctx_shell — real compression
    {
        let raw_output = generate_git_log_patch(10);
        let original = count_tokens(&raw_output);
        let compressed = lean_ctx::core::patterns::git::compress("git log -p", &raw_output)
            .unwrap_or_else(|| raw_output.clone());
        let compressed_tokens = count_tokens(&compressed);
        let saved = original.saturating_sub(compressed_tokens);
        let ratio = if original > 0 {
            saved as f64 / original as f64 * 100.0
        } else {
            0.0
        };

        eprintln!("\n  ctx_shell (git log -p, 10 commits):");
        eprintln!("    raw tokens:       {original}");
        eprintln!("    compressed tokens: {compressed_tokens}");
        eprintln!("    savings:          {saved} ({ratio:.1}%)");

        assert!(ratio > 70.0, "git log -p should save >70%, got {ratio:.1}%");
    }

    // 6. Verify tokenizer accuracy by cross-checking with known strings
    {
        let test_cases = [("hello world", 2), ("fn main() {}", 5), ("", 0), ("a", 1)];
        eprintln!("\n  Tokenizer (o200k_base) spot checks:");
        for (text, expected_approx) in &test_cases {
            let actual = count_tokens(text);
            eprintln!(
                "    {text:?}{:width$} → {actual:>3} tokens (expected ~{expected_approx})",
                "",
                width = 20_usize.saturating_sub(format!("{text:?}").len())
            );
            let diff = (actual as i64 - i64::from(*expected_approx)).unsigned_abs();
            assert!(
                diff <= 2,
                "tokenizer off by >2 for {text:?}: got {actual}, expected ~{expected_approx}",
            );
        }
    }

    // 7. CostModel sanity check
    {
        let input_saved: u64 = 100_000;
        let commands: u64 = 500;
        let output_bonus = commands * 60;
        let total_saved = input_saved + output_bonus;
        let cost_per_m_input = 2.50_f64;
        let cost_per_m_output = 10.0_f64;
        let usd_input = input_saved as f64 / 1_000_000.0 * cost_per_m_input;
        let usd_output = output_bonus as f64 / 1_000_000.0 * cost_per_m_output;
        let usd_total = usd_input + usd_output;

        eprintln!("\n  CostModel sanity (100K input saved, 500 calls):");
        eprintln!("    input saved:  {input_saved} tok → ${usd_input:.4}");
        eprintln!("    output bonus: {output_bonus} tok → ${usd_output:.4}");
        eprintln!("    total:        {total_saved} tok → ${usd_total:.4}");

        assert!(
            usd_total < 1.0,
            "100K saved + 500 calls should be < $1.00, got ${usd_total:.4}"
        );
        assert!(usd_total > 0.0, "savings should be positive");
    }

    eprintln!("\n  AUDIT COMPLETE — all checks passed");
    eprintln!("{}\n", "=".repeat(70));
}

fn generate_git_log_standard(n: usize) -> String {
    let mut output = String::new();
    for i in 0..n {
        let msg = format!("update component {i} with better error handling");
        output.push_str(&format!(
            "commit {i:07}abc1234567890abcdef1234567890\n\
             Author: Developer <dev@example.com>\n\
             Date:   Mon Mar {d} 10:{h:02}:00 2026 +0100\n\
             \n\
             {ty}: {msg}\n\
             \n\
             This is a longer commit message that describes the changes made.\n\
             It spans multiple lines to be more realistic.\n\
             \n",
            d = 10 + (i % 20),
            h = i % 24,
            ty = ["feat", "fix", "refactor", "docs", "test"][i % 5],
        ));
    }
    output
}
