use crate::product::agent::config::Config;
use crate::product::agent::project_doc::discover_project_doc_paths;
use crate::product::agent::protocol::TokenUsage;
use crate::product::tui_app::exec_command::relativize_to_home;
use crate::product::tui_app::text_formatting;
use std::path::Path;
use unicode_width::UnicodeWidthStr;

fn normalize_agents_display_path(path: &Path) -> String {
    dunce::simplified(path).display().to_string()
}

pub(crate) fn compose_model_display(
    model_name: &str,
    entries: &[(&str, String)],
) -> (String, Vec<String>) {
    let mut details: Vec<String> = Vec::new();
    if let Some((_, effort)) = entries.iter().find(|(k, _)| *k == "reasoning effort") {
        details.push(format!("reasoning {}", effort.to_ascii_lowercase()));
    }
    if let Some((_, summary)) = entries.iter().find(|(k, _)| *k == "reasoning summaries") {
        let summary = summary.trim();
        if summary.eq_ignore_ascii_case("none") || summary.eq_ignore_ascii_case("off") {
            details.push("summaries off".to_string());
        } else if !summary.is_empty() {
            details.push(format!("summaries {}", summary.to_ascii_lowercase()));
        }
    }

    (model_name.to_string(), details)
}

pub(crate) fn compose_agents_summary(config: &Config) -> String {
    match discover_project_doc_paths(config) {
        Ok(paths) => {
            let mut rels: Vec<String> = Vec::new();
            for p in paths {
                let file_name = p
                    .file_name()
                    .map(|name| name.to_string_lossy().to_string())
                    .unwrap_or_else(|| "<unknown>".to_string());
                let display = if let Some(parent) = p.parent() {
                    if parent == config.cwd {
                        file_name.clone()
                    } else {
                        let mut cur = config.cwd.as_path();
                        let mut ups = 0usize;
                        let mut reached = false;
                        while let Some(c) = cur.parent() {
                            if cur == parent {
                                reached = true;
                                break;
                            }
                            cur = c;
                            ups += 1;
                        }
                        if reached {
                            let up = format!("..{}", std::path::MAIN_SEPARATOR);
                            format!("{}{}", up.repeat(ups), file_name)
                        } else if let Ok(stripped) = p.strip_prefix(&config.cwd) {
                            normalize_agents_display_path(stripped)
                        } else {
                            normalize_agents_display_path(&p)
                        }
                    }
                } else {
                    normalize_agents_display_path(&p)
                };
                rels.push(display);
            }
            if rels.is_empty() {
                "<none>".to_string()
            } else {
                rels.join(", ")
            }
        }
        Err(_) => "<none>".to_string(),
    }
}

pub(crate) fn format_tokens_compact(value: i64) -> String {
    let value = value.max(0);
    if value == 0 {
        return "0".to_string();
    }
    if value < 1_000 {
        return value.to_string();
    }

    let value_f64 = value as f64;
    let (scaled, suffix) = if value >= 1_000_000_000_000 {
        (value_f64 / 1_000_000_000_000.0, "T")
    } else if value >= 1_000_000_000 {
        (value_f64 / 1_000_000_000.0, "B")
    } else if value >= 1_000_000 {
        (value_f64 / 1_000_000.0, "M")
    } else {
        (value_f64 / 1_000.0, "K")
    };

    let decimals = if scaled < 10.0 {
        2
    } else if scaled < 100.0 {
        1
    } else {
        0
    };

    let mut formatted = format!("{scaled:.decimals$}");
    if formatted.contains('.') {
        while formatted.ends_with('0') {
            formatted.pop();
        }
        if formatted.ends_with('.') {
            formatted.pop();
        }
    }

    format!("{formatted}{suffix}")
}

pub(crate) fn cache_hit_percent(usage: &TokenUsage) -> Option<i64> {
    let input = usage.input_tokens.max(0);
    let cached = usage.cached_input().min(input);
    if input == 0 || cached == 0 {
        return None;
    }

    Some((cached.saturating_mul(100).saturating_add(input / 2)) / input)
}

pub(crate) fn format_directory_display(directory: &Path, max_width: Option<usize>) -> String {
    let formatted = if let Some(rel) = relativize_to_home(directory) {
        if rel.as_os_str().is_empty() {
            "~".to_string()
        } else {
            format!("~{}{}", std::path::MAIN_SEPARATOR, rel.display())
        }
    } else {
        directory.display().to_string()
    };

    if let Some(max_width) = max_width {
        if max_width == 0 {
            return String::new();
        }
        if UnicodeWidthStr::width(formatted.as_str()) > max_width {
            return text_formatting::center_truncate_path(&formatted, max_width);
        }
    }

    formatted
}

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

    #[test]
    fn cache_hit_percent_is_unknown_without_input_tokens() {
        let usage = TokenUsage {
            cached_input_tokens: 5_000,
            ..TokenUsage::default()
        };

        assert_eq!(cache_hit_percent(&usage), None);
    }

    #[test]
    fn cache_hit_percent_is_unknown_without_cached_input_tokens() {
        let usage = TokenUsage {
            input_tokens: 20_000,
            output_tokens: 2_000,
            total_tokens: 22_000,
            ..TokenUsage::default()
        };

        assert_eq!(cache_hit_percent(&usage), None);
    }

    #[test]
    fn cache_hit_percent_rounds_cached_input_share() {
        let usage = TokenUsage {
            input_tokens: 20_000,
            cached_input_tokens: 5_000,
            output_tokens: 2_000,
            total_tokens: 22_000,
            ..TokenUsage::default()
        };

        assert_eq!(cache_hit_percent(&usage), Some(25));
    }

    #[test]
    fn cache_hit_percent_clamps_cached_input_to_input_tokens() {
        let usage = TokenUsage {
            input_tokens: 100,
            cached_input_tokens: 200,
            total_tokens: 100,
            ..TokenUsage::default()
        };

        assert_eq!(cache_hit_percent(&usage), Some(100));
    }
}
