use std::collections::HashMap;

use crate::core::cache::SessionCache;
use crate::tools::{CrpMode, ToolCallRecord};

use crate::core::stats::{DEFAULT_INPUT_PRICE_PER_M, DEFAULT_OUTPUT_PRICE_PER_M};

const COST_PER_1M_INPUT: f64 = DEFAULT_INPUT_PRICE_PER_M;
const COST_PER_1M_OUTPUT: f64 = DEFAULT_OUTPUT_PRICE_PER_M;

pub fn handle(cache: &SessionCache, tool_calls: &[ToolCallRecord], crp_mode: CrpMode) -> String {
    let cache_stats = cache.get_stats();
    let refs = cache.file_ref_map();

    let total_original: u64 = tool_calls.iter().map(|c| c.original_tokens as u64).sum();
    let total_saved: u64 = tool_calls.iter().map(|c| c.saved_tokens as u64).sum();
    let total_sent = total_original.saturating_sub(total_saved);
    let pct = if total_original > 0 {
        total_saved as f64 / total_original as f64 * 100.0
    } else {
        0.0
    };

    let mut out = Vec::new();

    if crp_mode.is_tdd() {
        out.push("§metrics".to_string());
        out.push("═".repeat(40));

        out.push(format!(
            "files:{} reads:{} hits:{} ({:.0}%)",
            cache_stats.files_tracked,
            cache_stats.total_reads,
            cache_stats.cache_hits,
            cache_stats.hit_rate()
        ));

        out.push(format!(
            "tok: {}→{} | saved:{} ({:.1}%)",
            format_tokens(total_original),
            format_tokens(total_sent),
            format_tokens(total_saved),
            pct
        ));

        let cost_saved = total_saved as f64 / 1_000_000.0 * COST_PER_1M_INPUT;
        let cost_without = total_original as f64 / 1_000_000.0 * COST_PER_1M_INPUT;
        out.push(format!(
            "cost: ${:.4}→${:.4} | -${:.4}",
            cost_without,
            cost_without - cost_saved,
            cost_saved
        ));
    } else {
        out.push("lean-ctx session metrics".to_string());
        out.push("═".repeat(50));

        out.push(format!(
            "Files tracked: {} | Reads: {} | Cache hits: {} ({:.0}%)",
            cache_stats.files_tracked,
            cache_stats.total_reads,
            cache_stats.cache_hits,
            cache_stats.hit_rate()
        ));

        out.push(format!(
            "Input tokens:  {} original → {} sent | {} saved ({:.1}%)",
            format_tokens(total_original),
            format_tokens(total_sent),
            format_tokens(total_saved),
            pct
        ));

        let cost_saved = total_saved as f64 / 1_000_000.0 * COST_PER_1M_INPUT;
        let cost_without = total_original as f64 / 1_000_000.0 * COST_PER_1M_INPUT;
        let cost_with = total_sent as f64 / 1_000_000.0 * COST_PER_1M_INPUT;
        out.push(format!(
            "Cost estimate: ${:.4} without → ${:.4} with lean-ctx | ${:.4} saved",
            cost_without, cost_with, cost_saved
        ));
    }

    if !tool_calls.is_empty() {
        out.push(String::new());

        let sep_w = if crp_mode.is_tdd() { 40 } else { 50 };
        if crp_mode.is_tdd() {
            out.push(format!(
                "{:<12} {:>4} {:>7} {:>7} {:>4}",
                "tool", "n", "orig", "saved", "%"
            ));
        } else {
            out.push("By Tool:".to_string());
            out.push(format!(
                "{:<14} {:>5}  {:>8}  {:>8}  {:>5}",
                "Tool", "Calls", "Original", "Saved", "Avg%"
            ));
        }
        out.push("─".repeat(sep_w));

        let mut by_tool: HashMap<&str, ToolStats> = HashMap::new();
        for call in tool_calls {
            let entry = by_tool.entry(&call.tool).or_default();
            entry.calls += 1;
            entry.original += call.original_tokens;
            entry.saved += call.saved_tokens;
        }

        let mut sorted: Vec<_> = by_tool.iter().collect();
        sorted.sort_by(|a, b| b.1.saved.cmp(&a.1.saved));

        for (tool, ts) in &sorted {
            let avg = if ts.original > 0 {
                ts.saved as f64 / ts.original as f64 * 100.0
            } else {
                0.0
            };
            if crp_mode.is_tdd() {
                out.push(format!(
                    "{:<12} {:>4} {:>7} {:>7} {:>3.0}%",
                    tool,
                    ts.calls,
                    format_tokens(ts.original as u64),
                    format_tokens(ts.saved as u64),
                    avg
                ));
            } else {
                out.push(format!(
                    "{:<14} {:>5}  {:>8}  {:>8}  {:>4.0}%",
                    tool,
                    ts.calls,
                    format_tokens(ts.original as u64),
                    format_tokens(ts.saved as u64),
                    avg
                ));
            }
        }

        let mut by_mode: HashMap<&str, ModeStats> = HashMap::new();
        for call in tool_calls {
            if let Some(ref mode) = call.mode {
                let entry = by_mode.entry(mode).or_default();
                entry.calls += 1;
                entry.saved += call.saved_tokens;
            }
        }

        if !by_mode.is_empty() {
            out.push(String::new());
            if crp_mode.is_tdd() {
                out.push(format!("{:<12} {:>4} {:>7}", "mode", "n", "saved"));
            } else {
                out.push("By Mode:".to_string());
                out.push(format!("{:<14} {:>5}  {:>8}", "Mode", "Calls", "Saved"));
            }
            out.push("─".repeat(if crp_mode.is_tdd() { 28 } else { 30 }));

            let mut sorted_modes: Vec<_> = by_mode.iter().collect();
            sorted_modes.sort_by(|a, b| b.1.saved.cmp(&a.1.saved));

            for (mode, ms) in &sorted_modes {
                if crp_mode.is_tdd() {
                    out.push(format!(
                        "{:<12} {:>4} {:>7}",
                        mode,
                        ms.calls,
                        format_tokens(ms.saved as u64)
                    ));
                } else {
                    out.push(format!(
                        "{:<14} {:>5}  {:>8}",
                        mode,
                        ms.calls,
                        format_tokens(ms.saved as u64)
                    ));
                }
            }
        }
    }

    if !refs.is_empty() {
        out.push(String::new());
        if crp_mode.is_tdd() {
            out.push("§refs:".to_string());
        } else {
            out.push("File Refs:".to_string());
        }
        let mut ref_list: Vec<_> = refs.iter().collect();
        ref_list.sort_by_key(|(_, r)| (*r).clone());
        for (path, r) in &ref_list {
            let short = crate::core::protocol::shorten_path(path);
            if let Some(entry) = cache.get(path) {
                out.push(format!(
                    "  {r}={short} [{}L {}t r:{}]",
                    entry.line_count, entry.original_tokens, entry.read_count
                ));
            } else {
                out.push(format!("  {r}={short}"));
            }
        }
    }

    let projected_session =
        total_saved as f64 / 1_000_000.0 * (COST_PER_1M_INPUT + COST_PER_1M_OUTPUT * 0.3);
    if projected_session > 0.001 {
        out.push(String::new());
        if crp_mode.is_tdd() {
            out.push(format!(
                "∴ session savings (incl. thinking): ${:.3}",
                projected_session
            ));
        } else {
            out.push(format!(
                "Projected session savings (incl. thinking): ${:.3}",
                projected_session
            ));
        }
    }

    let cep = compute_cep_compliance(cache, tool_calls);
    out.push(String::new());
    if crp_mode.is_tdd() {
        out.push("§CEP compliance".to_string());
    } else {
        out.push("CEP Compliance:".to_string());
    }
    out.push(format!(
        "  Cache utilization: {:.0}%  (hit rate for repeated files)",
        cep.cache_utilization * 100.0
    ));
    out.push(format!(
        "  Mode diversity:    {:.0}%  (using optimal modes per file)",
        cep.mode_diversity * 100.0
    ));
    out.push(format!(
        "  Compression rate:  {:.0}%  (overall token reduction)",
        cep.compression_rate * 100.0
    ));
    out.push(format!(
        "  CEP Score:         {:.0}/100",
        cep.overall_score * 100.0
    ));

    let complexity = crate::core::adaptive::classify_from_context(cache);
    out.push(format!("  Task complexity:   {:?}", complexity));

    out.join("\n")
}

struct CepCompliance {
    cache_utilization: f64,
    mode_diversity: f64,
    compression_rate: f64,
    overall_score: f64,
}

fn compute_cep_compliance(cache: &SessionCache, tool_calls: &[ToolCallRecord]) -> CepCompliance {
    let stats = cache.get_stats();

    let cache_utilization = stats.hit_rate() / 100.0;

    let modes_used: std::collections::HashSet<&str> = tool_calls
        .iter()
        .filter_map(|c| c.mode.as_deref())
        .collect();
    let possible_modes = 6.0; // full, map, signatures, aggressive, entropy, cache_hit
    let mode_diversity = (modes_used.len() as f64 / possible_modes).min(1.0);

    let total_original: u64 = tool_calls.iter().map(|c| c.original_tokens as u64).sum();
    let total_saved: u64 = tool_calls.iter().map(|c| c.saved_tokens as u64).sum();
    let compression_rate = if total_original > 0 {
        total_saved as f64 / total_original as f64
    } else {
        0.0
    };

    let overall_score = cache_utilization * 0.3 + mode_diversity * 0.2 + compression_rate * 0.5;

    CepCompliance {
        cache_utilization,
        mode_diversity,
        compression_rate,
        overall_score,
    }
}

fn format_tokens(n: u64) -> 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 {
        format!("{n}")
    }
}

#[derive(Default)]
struct ToolStats {
    calls: u32,
    original: usize,
    saved: usize,
}

#[derive(Default)]
struct ModeStats {
    calls: u32,
    saved: usize,
}

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

    #[test]
    fn test_cep_compliance_section_present_tdd() {
        let cache = SessionCache::new();
        let calls = vec![ToolCallRecord {
            tool: "ctx_read".to_string(),
            original_tokens: 1000,
            saved_tokens: 300,
            mode: Some("full".to_string()),
        }];
        let output = handle(&cache, &calls, CrpMode::Tdd);
        assert!(
            output.contains("§CEP compliance"),
            "TDD output must contain CEP compliance section"
        );
        assert!(output.contains("Cache utilization:"));
        assert!(output.contains("Mode diversity:"));
        assert!(output.contains("Compression rate:"));
        assert!(output.contains("CEP Score:"));
        assert!(output.contains("Task complexity:"));
    }

    #[test]
    fn test_cep_compliance_section_present_normal() {
        let cache = SessionCache::new();
        let calls = vec![];
        let output = handle(&cache, &calls, CrpMode::Off);
        assert!(
            output.contains("CEP Compliance:"),
            "Normal output must contain CEP Compliance section"
        );
        assert!(output.contains("Task complexity:"));
    }

    #[test]
    fn test_cep_scores_zero_with_no_calls() {
        let cache = SessionCache::new();
        let calls = vec![];
        let output = handle(&cache, &calls, CrpMode::Tdd);
        assert!(output.contains("CEP Score:         0/100"));
        assert!(output.contains("Cache utilization: 0%"));
    }

    #[test]
    fn test_format_tokens_units() {
        assert_eq!(format_tokens(500), "500");
        assert_eq!(format_tokens(1500), "1.5K");
        assert_eq!(format_tokens(1_500_000), "1.5M");
    }
}
