use crate::config::AppConfig;
use anyhow::Result;
use dictx_core::{DictEntry, DictSource, SearchRequest};
use dictx_index::read_metadata;
use dictx_search::SearchResult;
use owo_colors::OwoColorize;
use serde_json::json;
use std::env;
use terminal_size::{terminal_size, Width};
use unicode_width::UnicodeWidthStr;

#[derive(Debug, Clone, Default)]
pub struct RenderedEntries {
    pub shown: usize,
    pub available: usize,
    pub visible_words: Vec<String>,
}

impl RenderedEntries {
    pub fn can_load_more(&self) -> bool {
        self.shown < self.available
    }
}

pub fn print_json_result(request: &SearchRequest, result: &SearchResult, raw: bool) -> Result<()> {
    let entries: Vec<_> = result
        .entries
        .iter()
        .map(|item| {
            if raw {
                json!({
                    "score": item.score,
                    "entry": item.entry,
                })
            } else {
                json!({
                    "score": item.score,
                    "source": item.entry.source.display_name(),
                    "word": item.entry.word,
                    "phonetic": item.entry.phonetic(),
                    "definitions": item.entry.definitions,
                    "tags": item.entry.tags,
                    "freq_bnc": item.entry.freq_bnc,
                    "collins_star": item.entry.collins_star,
                })
            }
        })
        .collect();
    println!(
        "{}",
        serde_json::to_string_pretty(&json!({
            "query": request.query.user_text(),
            "total": result.total,
            "elapsed_ms": result.elapsed_ms,
            "results": entries,
        }))?
    );
    Ok(())
}

pub fn print_entries(
    request: &SearchRequest,
    result: &SearchResult,
    color: bool,
    raw: bool,
    display_limit: usize,
    interactive: bool,
) -> RenderedEntries {
    if result.entries.is_empty() {
        println!("未找到结果: {}", request.query.user_text());
        return RenderedEntries::default();
    }

    print_header(request, result, color);
    let rendered = print_entries_page(result, 0, display_limit, color, raw, interactive);
    if rendered.can_load_more() && !raw {
        print_load_more_hint(rendered.shown, rendered.available, color, interactive);
    }
    rendered
}

pub fn print_entries_page(
    result: &SearchResult,
    start: usize,
    count: usize,
    color: bool,
    raw: bool,
    interactive: bool,
) -> RenderedEntries {
    let width = card_width();
    let mut visible_words = Vec::new();
    let end = start.saturating_add(count).min(result.entries.len());
    for (idx, item) in result.entries.iter().enumerate().skip(start).take(count) {
        if raw {
            println!(
                "{}",
                serde_json::to_string_pretty(&item.entry).unwrap_or_default()
            );
            continue;
        }
        print_entry(
            idx + 1,
            idx.saturating_sub(start) + 1,
            &item.entry,
            item.score,
            width,
            color,
            interactive,
        );
        if idx < end {
            visible_words.push(item.entry.word.clone());
        }
    }
    RenderedEntries {
        shown: end,
        available: result.entries.len(),
        visible_words,
    }
}

pub fn print_load_more_hint(shown: usize, available: usize, color: bool, interactive: bool) {
    let remaining = available.saturating_sub(shown);
    if remaining == 0 {
        return;
    }
    if interactive && color {
        println!(
            "{} {} {}",
            "还有".dimmed(),
            remaining.to_string().bright_white().bold(),
            "条结果。按 n 加载更多，按 1/2/3 查询当前卡片 AI，按 q 退出。".dimmed()
        );
    } else if interactive {
        println!("还有 {remaining} 条结果。按 n 加载更多，按 1/2/3 查询当前卡片 AI，按 q 退出。");
    } else if color {
        println!(
            "{} {} {}",
            "还有".dimmed(),
            remaining.to_string().bright_white().bold(),
            "条结果。使用 --limit <数量> 显示更多。".dimmed()
        );
    } else {
        println!("还有 {remaining} 条结果。使用 --limit <数量> 显示更多。");
    }
}

pub fn print_ai_answer(term: &str, content: &str, color: bool) {
    let width = card_width();
    let title = format!("AI 详解 {}", term);

    println!("{}", top_line(&title, width, color));
    print_wrapped_meta(width, "在线 AI API  ·  结果供学习参考", color);

    let mut printed = false;
    for raw_line in content.lines() {
        let line = normalize_ai_line(raw_line);
        if line.is_empty() {
            if printed {
                print_spacer(width);
            }
            continue;
        }

        if let Some(section) = ai_section_title(&line) {
            if printed {
                print_spacer(width);
            }
            print_section(width, &section, color);
            printed = true;
            continue;
        }

        let line = line
            .strip_prefix("- ")
            .or_else(|| line.strip_prefix("* "))
            .unwrap_or(&line);
        let style = if has_cjk(line) {
            FieldStyle::Chinese
        } else {
            FieldStyle::English
        };
        print_wrapped_field(width, "AI", line, color, style);
        printed = true;
    }

    if !printed {
        print_wrapped_field(
            width,
            "AI",
            "AI API 没有返回可显示内容。",
            color,
            FieldStyle::Chinese,
        );
    }

    println!("╰{}╯", "─".repeat(width - 2));
}

fn print_header(request: &SearchRequest, result: &SearchResult, color: bool) {
    if color {
        println!(
            "{} {}  {} {}  {} {} ms",
            "DictX".bright_cyan().bold(),
            request.query.user_text().bright_white().bold(),
            "结果".dimmed(),
            result.total.to_string().bright_white(),
            "耗时".dimmed(),
            result.elapsed_ms.to_string().bright_white()
        );
    } else {
        println!(
            "DictX {}  结果 {}  耗时 {} ms",
            request.query.user_text(),
            result.total,
            result.elapsed_ms
        );
    }
}

fn print_entry(
    index: usize,
    action_slot: usize,
    entry: &DictEntry,
    score: f32,
    width: usize,
    color: bool,
    interactive: bool,
) {
    let title = format!(
        "{}. {} {}",
        index,
        entry.word,
        entry.phonetic().unwrap_or_default()
    );

    println!("{}", top_line(&title, width, color));
    print_wrapped_meta(width, &meta_text(entry, score), color);

    if !entry.tags.is_empty() {
        print_badges(width, "标签", &entry.tags, color);
    }

    if !entry.definitions.is_empty() {
        print_section(width, "释义", color);
        for (idx, definition) in entry.definitions.iter().take(5).enumerate() {
            let pos = definition
                .pos
                .as_deref()
                .map(friendly_pos)
                .filter(|value| !value.is_empty())
                .map(|value| format!("{value}  "))
                .unwrap_or_default();
            if !definition.zh.is_empty() {
                print_wrapped_field(
                    width,
                    if idx == 0 { "中" } else { "中" },
                    &format!("{pos}{}", definition.zh),
                    color,
                    FieldStyle::Chinese,
                );
            }
            if !definition.en.is_empty() {
                print_wrapped_field(width, "EN", &definition.en, color, FieldStyle::English);
            }
        }
    }

    if !entry.phrases.is_empty() {
        print_spacer(width);
        print_section(width, "短语", color);
        for phrase in entry.phrases.iter().take(4) {
            let text = if phrase.zh.is_empty() {
                phrase.en.clone()
            } else {
                format!("{}  {}", phrase.en, phrase.zh)
            };
            print_wrapped_field(width, "搭", &text, color, FieldStyle::Phrase);
        }
    }

    if !entry.examples.is_empty() {
        print_spacer(width);
        print_section(width, "例句", color);
        for example in entry.examples.iter().take(2) {
            if !example.en.is_empty() {
                print_wrapped_field(width, "EN", &example.en, color, FieldStyle::ExampleEn);
            }
            if !example.zh.is_empty() {
                print_wrapped_field(width, "中", &example.zh, color, FieldStyle::ExampleZh);
            }
        }
    }

    print_spacer(width);
    print_ai_hint(width, entry, action_slot, color, interactive);
    println!("╰{}╯", "─".repeat(width - 2));
}

fn meta_text(entry: &DictEntry, score: f32) -> String {
    let mut parts = vec![
        format!("来源 {}", friendly_source(&entry.source)),
        format!("相关度 {:.1}", score),
    ];
    if entry.collins_star > 0 {
        parts.push(format!(
            "Collins {}",
            "★".repeat(entry.collins_star as usize)
        ));
    }
    if let Some(freq) = entry.freq_bnc {
        parts.push(format!("BNC #{freq}"));
    }
    parts.join("  ·  ")
}

fn print_wrapped_meta(width: usize, text: &str, color: bool) {
    let text = if color {
        text.dimmed().to_string()
    } else {
        text.to_string()
    };
    for line in wrap_ansi_text(&text, width - 8) {
        print_box_line(width, &format!("  {line}"));
    }
}

fn print_badges(width: usize, title: &str, tags: &[String], color: bool) {
    let labels = tags.iter().fold(Vec::<String>::new(), |mut labels, tag| {
        let label = friendly_tag(tag);
        if !labels.contains(&label) {
            labels.push(label);
        }
        labels
    });
    let badges = labels
        .iter()
        .map(|label| {
            if color {
                format!("{}", format!("[{label}]").bright_magenta())
            } else {
                format!("[{label}]")
            }
        })
        .collect::<Vec<_>>()
        .join(" ");
    let title = if color {
        title.bright_black().to_string()
    } else {
        title.to_string()
    };
    for line in wrap_ansi_text(&badges, width - 12) {
        print_box_line(width, &format!("  {title} {line}"));
    }
}

fn print_section(width: usize, title: &str, color: bool) {
    let title = if color {
        format!("{}", title.bright_yellow().bold())
    } else {
        title.to_string()
    };
    print_box_line(width, &format!("  {title}"));
}

fn print_wrapped_field(width: usize, label: &str, text: &str, color: bool, style: FieldStyle) {
    let label_plain = format!("[{label}]");
    let label = paint_label(&label_plain, color, style);
    let prefix_width = UnicodeWidthStr::width(label_plain.as_str()) + 1;
    let continuation = " ".repeat(prefix_width);
    let content_width = width.saturating_sub(4 + 2 + prefix_width).max(16);
    let mut lines = wrap_plain_text(text, content_width);
    if lines.is_empty() {
        lines.push(String::new());
    }

    for (idx, line) in lines.into_iter().enumerate() {
        let text = paint_text(&line, color, style);
        if idx == 0 {
            print_box_line(width, &format!("  {label} {text}"));
        } else {
            print_box_line(width, &format!("  {continuation}{text}"));
        }
    }
}

fn print_ai_hint(
    width: usize,
    entry: &DictEntry,
    action_slot: usize,
    color: bool,
    interactive: bool,
) {
    let command = ai_command(&entry.word);
    let label = if color {
        "AI 详解".bright_blue().bold().to_string()
    } else {
        "AI 详解".to_string()
    };
    let prefix = if color {
        "  ↳".bright_black().to_string()
    } else {
        "  ->".to_string()
    };
    let action = if interactive && color {
        format!(
            "{} {} {}",
            "按".dimmed(),
            action_slot.to_string().bright_white().bold(),
            "或点击底部按钮".dimmed()
        )
    } else if interactive {
        format!("按 {action_slot} 或点击底部按钮")
    } else if color {
        "运行".dimmed().to_string()
    } else {
        "运行".to_string()
    };
    let text = format!("{prefix} {label}  {action}  {command}");
    for line in wrap_ansi_text(&text, width - 6) {
        print_box_line(width, &line);
    }
}

pub fn ai_command(word: &str) -> String {
    format!("{} ai {}", executable_command(), shell_quote(word))
}

pub fn print_source_list(config: &AppConfig) -> Result<()> {
    println!("词库目录: {}", config.dict_dir.display());
    println!("索引目录: {}", config.index_dir.display());
    for source in &config.sources {
        let status = if source.enabled { "启用" } else { "停用" };
        let meta = read_metadata(&config.index_dir_for(&source.name))?;
        let index = meta
            .map(|meta| {
                format!(
                    "索引 {} 条 / {}",
                    meta.entries,
                    human_bytes(meta.index_bytes)
                )
            })
            .unwrap_or_else(|| "索引未构建".to_string());
        println!(
            "- {} [{}] {} | {} | {}",
            source.name,
            status,
            source.format,
            source.path.display(),
            index
        );
    }
    Ok(())
}

#[derive(Debug, Clone, Copy)]
enum FieldStyle {
    Chinese,
    English,
    ExampleEn,
    ExampleZh,
    Phrase,
}

fn paint_label(label: &str, color: bool, style: FieldStyle) -> String {
    if !color {
        return label.to_string();
    }
    match style {
        FieldStyle::Chinese => label.green().bold().to_string(),
        FieldStyle::English => label.blue().bold().to_string(),
        FieldStyle::ExampleEn => label.cyan().bold().to_string(),
        FieldStyle::ExampleZh => label.green().bold().to_string(),
        FieldStyle::Phrase => label.yellow().bold().to_string(),
    }
}

fn paint_text(text: &str, color: bool, style: FieldStyle) -> String {
    if !color {
        return text.to_string();
    }
    match style {
        FieldStyle::Chinese => text.green().bold().to_string(),
        FieldStyle::English => text.blue().to_string(),
        FieldStyle::ExampleEn => text.cyan().to_string(),
        FieldStyle::ExampleZh => text.green().to_string(),
        FieldStyle::Phrase => text.yellow().to_string(),
    }
}

fn friendly_source(source: &DictSource) -> String {
    match source {
        DictSource::Ecdict => "ECDICT".to_string(),
        DictSource::Anki { deck_name } if deck_name.eq_ignore_ascii_case("KaoYan_3") => {
            "考研词库".to_string()
        }
        DictSource::Anki { deck_name } => deck_name.clone(),
        DictSource::Sqlite { name, table } if name == "kd_data" && table == "en" => {
            "金山词霸 英汉".to_string()
        }
        DictSource::Sqlite { name, table } if name == "kd_data" && table == "ch" => {
            "金山词霸 汉英".to_string()
        }
        DictSource::Sqlite { name, table } => format!("{name}:{table}"),
        DictSource::Mdx { filename } => filename.clone(),
        DictSource::Custom { name } => name.clone(),
    }
}

fn friendly_tag(tag: &str) -> String {
    match tag.trim().to_ascii_lowercase().as_str() {
        "zk" => "中考".to_string(),
        "gk" => "高考".to_string(),
        "cet4" => "CET-4".to_string(),
        "cet6" => "CET-6".to_string(),
        "tem4" => "TEM-4".to_string(),
        "tem8" => "TEM-8".to_string(),
        "kao_yan" | "kaoyan" | "kaoyan_3" => "考研".to_string(),
        "toefl" => "TOEFL".to_string(),
        "ielts" => "IELTS".to_string(),
        "gre" => "GRE".to_string(),
        "gmat" => "GMAT".to_string(),
        "sat" => "SAT".to_string(),
        other => other.to_ascii_uppercase().replace('_', "-"),
    }
}

fn friendly_pos(pos: &str) -> String {
    let pos = pos.trim().trim_end_matches('.');
    match pos.to_ascii_lowercase().as_str() {
        "n" => "名词 n.".to_string(),
        "n-var" => "名词 n-var.".to_string(),
        "n-count" => "可数名词 n-count.".to_string(),
        "n-uncount" => "不可数名词 n-uncount.".to_string(),
        "v" => "动词 v.".to_string(),
        "vt" | "v-t" => "及物动词 vt.".to_string(),
        "vi" | "v-i" => "不及物动词 vi.".to_string(),
        "adj" => "形容词 adj.".to_string(),
        "adv" => "副词 adv.".to_string(),
        "prep" => "介词 prep.".to_string(),
        "conj" => "连词 conj.".to_string(),
        "pron" => "代词 pron.".to_string(),
        "num" => "数词 num.".to_string(),
        other if other.is_empty() => String::new(),
        other => format!("{other}."),
    }
}

fn top_line(title: &str, width: usize, color: bool) -> String {
    let title = if color {
        title.bright_cyan().bold().to_string()
    } else {
        title.to_string()
    };
    let plain_width = display_width(&title);
    let fill = width.saturating_sub(plain_width + 5);
    format!("╭─ {} {}╮", title, "─".repeat(fill))
}

fn print_box_line(width: usize, text: &str) {
    println!("│ {} │", pad(text, width - 4));
}

fn print_spacer(width: usize) {
    print_box_line(width, "");
}

fn pad(text: &str, width: usize) -> String {
    let used = display_width(text);
    if used >= width {
        truncate_width(text, width)
    } else {
        format!("{text}{}", " ".repeat(width - used))
    }
}

fn wrap_plain_text(text: &str, max_width: usize) -> Vec<String> {
    let text = normalize_spaces(text);
    if text.is_empty() {
        return Vec::new();
    }

    let mut lines = Vec::new();
    let mut current = String::new();

    for word in text.split(' ') {
        if word.is_empty() {
            continue;
        }
        let word_width = UnicodeWidthStr::width(word);
        if word_width > max_width {
            if !current.is_empty() {
                lines.push(current);
                current = String::new();
            }
            lines.extend(split_long_word(word, max_width));
            continue;
        }

        let next_width = if current.is_empty() {
            word_width
        } else {
            UnicodeWidthStr::width(current.as_str()) + 1 + word_width
        };
        if next_width > max_width && !current.is_empty() {
            lines.push(current);
            current = word.to_string();
        } else {
            if !current.is_empty() {
                current.push(' ');
            }
            current.push_str(word);
        }
    }

    if !current.is_empty() {
        lines.push(current);
    }
    lines
}

fn wrap_ansi_text(text: &str, max_width: usize) -> Vec<String> {
    if display_width(text) <= max_width {
        return vec![text.to_string()];
    }
    wrap_plain_text(&strip_ansi(text), max_width)
}

fn split_long_word(word: &str, max_width: usize) -> Vec<String> {
    let mut lines = Vec::new();
    let mut current = String::new();
    let mut width = 0usize;

    for ch in word.chars() {
        let ch_width = UnicodeWidthStr::width(ch.to_string().as_str());
        if width + ch_width > max_width && !current.is_empty() {
            lines.push(current);
            current = String::new();
            width = 0;
        }
        current.push(ch);
        width += ch_width;
    }

    if !current.is_empty() {
        lines.push(current);
    }
    lines
}

fn normalize_spaces(text: &str) -> String {
    text.split_whitespace().collect::<Vec<_>>().join(" ")
}

fn truncate_width(text: &str, width: usize) -> String {
    let mut out = String::new();
    let mut used = 0usize;
    let plain = strip_ansi(text);
    for ch in plain.chars() {
        let ch_width = UnicodeWidthStr::width(ch.to_string().as_str());
        if used + ch_width + 1 > width {
            break;
        }
        out.push(ch);
        used += ch_width;
    }
    out.push('…');
    let used = UnicodeWidthStr::width(out.as_str());
    if used < width {
        out.push_str(&" ".repeat(width - used));
    }
    out
}

fn display_width(text: &str) -> usize {
    UnicodeWidthStr::width(strip_ansi(text).as_str())
}

fn strip_ansi(text: &str) -> String {
    let mut out = String::with_capacity(text.len());
    let mut chars = text.chars().peekable();
    while let Some(ch) = chars.next() {
        if ch == '\u{1b}' {
            match chars.next() {
                Some('[') => {
                    for next in chars.by_ref() {
                        if next.is_ascii_alphabetic() {
                            break;
                        }
                    }
                }
                Some(']') => {
                    let mut previous_was_esc = false;
                    for next in chars.by_ref() {
                        if next == '\u{7}' {
                            break;
                        }
                        if previous_was_esc && next == '\\' {
                            break;
                        }
                        previous_was_esc = next == '\u{1b}';
                    }
                }
                _ => {}
            }
        } else {
            out.push(ch);
        }
    }
    out
}

fn card_width() -> usize {
    env::var("COLUMNS")
        .ok()
        .and_then(|value| value.parse::<usize>().ok())
        .or_else(|| terminal_size().map(|(Width(width), _)| width as usize))
        .unwrap_or(96)
        .clamp(76, 124)
}

fn executable_command() -> String {
    let exe = env::current_exe().ok();
    let cwd = env::current_dir().ok();
    match (exe, cwd) {
        (Some(exe), Some(cwd)) if exe.parent() == Some(cwd.as_path()) => exe
            .file_name()
            .and_then(|name| name.to_str())
            .map(|name| format!("./{name}"))
            .unwrap_or_else(|| "dictx".to_string()),
        (Some(exe), _) => exe.display().to_string(),
        _ => "dictx".to_string(),
    }
}

fn normalize_ai_line(line: &str) -> String {
    let line = line
        .trim()
        .replace("**", "")
        .replace("__", "")
        .replace('`', "")
        .replace('*', "");
    let line = line.trim().trim_matches('*').trim();
    if line.chars().all(|ch| matches!(ch, '-' | '_' | '*')) {
        return String::new();
    }
    line.trim_start_matches('#').trim().to_string()
}

fn ai_section_title(line: &str) -> Option<String> {
    let trimmed = line.trim();
    let lower = trimmed.to_ascii_lowercase();
    let title = if lower.ends_with(':') || trimmed.ends_with('：') {
        trimmed.trim_end_matches(':').trim_end_matches('：')
    } else {
        trimmed
    };
    match title {
        "中文释义" | "英文释义" | "常用搭配" | "例句" | "词性" | "用法" | "辨析" => {
            Some(title.to_string())
        }
        value
            if value.chars().count() <= 24
                && !value.contains(':')
                && !value.contains('：')
                && (value.contains("名词")
                    || value.contains("动词")
                    || value.contains("形容词")
                    || value.contains("副词")
                    || lower.contains("noun")
                    || lower.contains("verb")
                    || lower.contains("adjective")
                    || lower.contains("adverb")) =>
        {
            Some(value.to_string())
        }
        _ => None,
    }
}

fn has_cjk(text: &str) -> bool {
    text.chars().any(|ch| {
        ('\u{4e00}'..='\u{9fff}').contains(&ch)
            || ('\u{3400}'..='\u{4dbf}').contains(&ch)
            || ('\u{f900}'..='\u{faff}').contains(&ch)
    })
}

fn shell_quote(value: &str) -> String {
    if value
        .chars()
        .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-' | '.'))
    {
        return value.to_string();
    }
    format!("\"{}\"", value.replace('\\', "\\\\").replace('"', "\\\""))
}

fn human_bytes(bytes: u64) -> String {
    const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"];
    let mut value = bytes as f64;
    let mut unit = 0usize;
    while value >= 1024.0 && unit < UNITS.len() - 1 {
        value /= 1024.0;
        unit += 1;
    }
    if unit == 0 {
        format!("{} {}", bytes, UNITS[unit])
    } else {
        format!("{value:.1} {}", UNITS[unit])
    }
}

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

    #[test]
    fn wraps_long_sentence_without_truncating() {
        let text = "The Russian foreign minister yesterday cancelled his visit to Washington.";
        let lines = wrap_plain_text(text, 24);

        assert!(lines.len() > 1);
        assert_eq!(lines.join(" "), text);
        assert!(lines
            .iter()
            .all(|line| UnicodeWidthStr::width(line.as_str()) <= 24));
    }

    #[test]
    fn formats_friendly_tags() {
        assert_eq!(friendly_tag("cet4"), "CET-4");
        assert_eq!(friendly_tag("kao_yan"), "考研");
    }

    #[test]
    fn normalizes_ai_markdown_lines() {
        assert_eq!(normalize_ai_line("## 名词 (noun)"), "名词 (noun)");
        assert_eq!(
            normalize_ai_line("`take a test` 参加考试"),
            "take a test 参加考试"
        );
        assert_eq!(normalize_ai_line("*She has a test.*"), "She has a test.");
        assert_eq!(normalize_ai_line("---"), "");
        assert_eq!(
            ai_section_title("名词 (noun)").as_deref(),
            Some("名词 (noun)")
        );
        assert_eq!(ai_section_title("词性：名词 / 动词"), None);
    }
}
