use anyhow::Result;
use forge::tracker::store::ExecutionStore;

pub fn handle() -> Result<()> {
    let home = super::home_dir();
    let db_path = std::path::PathBuf::from(&home)
        .join(".bctx")
        .join("executions.db");

    if !db_path.exists() {
        println!();
        println!("  No executions recorded yet.");
        println!("  Run  bctx git log  or  bctx cargo build  to start tracking.");
        println!();
        return Ok(());
    }

    let store = ExecutionStore::open(&db_path)?;
    let summary = store.summary()?;

    // All headline figures come from one SQL snapshot so they're mutually consistent.
    let (total_before, total_sent, cmd_count) = store.totals()?;
    let total_saved = total_before.saturating_sub(total_sent);
    let compression_pct = total_saved
        .checked_mul(100)
        .and_then(|v| v.checked_div(total_before))
        .unwrap_or(0);
    let cost_avoided = total_saved as f64 * 0.000_015;

    let sep = "  ─────────────────────────────────────────────────";
    println!();
    println!("  TOKEN SAVINGS  (local · all time)");
    println!("{sep}");
    println!(
        "  Tokens saved  {:>10}    Compression   {:>3}%",
        fmt_tokens(total_saved),
        compression_pct
    );
    println!(
        "  Tokens sent   {:>10}    Commands run  {:>6}",
        fmt_tokens(total_sent),
        cmd_count
    );
    println!("  Cost avoided  {:>10}", format!("${cost_avoided:.2}"));
    println!();

    if !summary.is_empty() {
        println!("  TOP COMMANDS");
        let max_saved = summary
            .iter()
            .map(|(_, tb, ta, _)| tb.saturating_sub(*ta))
            .max()
            .unwrap_or(1);
        for (prog, tb, ta, pct) in summary.iter().take(8) {
            let saved = tb.saturating_sub(*ta);
            let bar = bar(saved, max_saved, 16);
            let name: String = prog
                .split_whitespace()
                .next()
                .unwrap_or(prog)
                .chars()
                .take(12)
                .collect();
            println!(
                "  {:<12}  {}  {:>8} saved  {:>3.0}%",
                name,
                bar,
                fmt_tokens(saved),
                pct
            );
        }
        println!();
    }

    println!("{sep}");

    // Cloud quota — append if logged in
    if let Some(token) = cloud::client::auth::load_token() {
        if let Ok(gauge) = fetch_gauge(&token.endpoint, &token.access_token) {
            let used = gauge["used_tokens"].as_u64().unwrap_or(0);
            let quota = gauge["quota_tokens"].as_u64().unwrap_or(0);
            let reset = gauge["reset_at"].as_str().unwrap_or("—");
            if quota > 0 {
                let pct = used
                    .checked_mul(100)
                    .and_then(|v| v.checked_div(quota))
                    .unwrap_or(0)
                    .min(100);
                if used > quota {
                    println!(
                        "  Cloud quota   {} / {}  ⚠ over limit  ·  resets {}",
                        fmt_tokens(used as usize),
                        fmt_tokens(quota as usize),
                        reset
                    );
                } else {
                    println!(
                        "  Cloud quota   {} / {}  ({}% used)  ·  resets {}",
                        fmt_tokens(used as usize),
                        fmt_tokens(quota as usize),
                        pct,
                        reset
                    );
                }
            } else {
                println!(
                    "  Cloud quota   {} used  (unlimited)  resets {}",
                    fmt_tokens(used as usize),
                    reset
                );
            }
            println!("{sep}");
        }
    }

    println!("  Run `bctx dashboard` to open the full dashboard");
    println!();
    Ok(())
}

fn fmt_tokens(n: usize) -> String {
    if n >= 1_000_000 {
        format!("{:.1}M", n as f64 / 1_000_000.0)
    } else if n >= 1_000 {
        format!("{:.1}K", n as f64 / 1_000.0)
    } else {
        n.to_string()
    }
}

fn bar(value: usize, max: usize, width: usize) -> String {
    use std::io::IsTerminal;
    let filled = value
        .checked_mul(width)
        .and_then(|v| v.checked_div(max))
        .unwrap_or(0)
        .min(width);
    let empty = width - filled;
    let blocks = "█".repeat(filled);
    let shade = "░".repeat(empty);
    if std::io::stdout().is_terminal() {
        format!("\x1b[32m{blocks}\x1b[2m{shade}\x1b[0m")
    } else {
        format!("{blocks}{shade}")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fmt_tokens_formats_millions() {
        assert_eq!(fmt_tokens(1_000_000), "1.0M");
        assert_eq!(fmt_tokens(2_400_000), "2.4M");
    }

    #[test]
    fn fmt_tokens_formats_thousands() {
        assert_eq!(fmt_tokens(1_000), "1.0K");
        assert_eq!(fmt_tokens(890_000), "890.0K");
    }

    #[test]
    fn fmt_tokens_formats_small() {
        assert_eq!(fmt_tokens(0), "0");
        assert_eq!(fmt_tokens(42), "42");
        assert_eq!(fmt_tokens(999), "999");
    }

    #[test]
    fn bar_full_when_value_equals_max() {
        let b = bar(16, 16, 16);
        assert!(b.contains("████████████████"));
        assert!(!b.contains("░"));
    }

    #[test]
    fn bar_empty_when_value_is_zero() {
        let b = bar(0, 16, 16);
        assert!(b.contains("░░░░░░░░░░░░░░░░"));
    }

    #[test]
    fn bar_half_fill() {
        let b = bar(8, 16, 16);
        // ANSI codes sit between filled and empty segments
        assert!(b.contains("████████"));
        assert!(b.contains("░░░░░░░░"));
    }

    #[test]
    fn bar_zero_max_does_not_panic() {
        let b = bar(5, 0, 16);
        assert!(b.contains("░"));
    }
}

fn fetch_gauge(endpoint: &str, access_token: &str) -> Result<serde_json::Value> {
    let output = std::process::Command::new("curl")
        .args([
            "-sf",
            "--max-time",
            "8",
            "-H",
            &format!("Authorization: Bearer {access_token}"),
            &format!("{endpoint}/dashboard/gauge"),
        ])
        .output()?;
    if !output.status.success() {
        anyhow::bail!("curl failed");
    }
    let val: serde_json::Value = serde_json::from_slice(&output.stdout)?;
    Ok(val)
}
