use anyhow::Result;
use crossterm::style::{
    Attribute, Color as CrosstermColor, ResetColor, SetAttribute, SetForegroundColor,
};
use std::fmt::Write;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

use crate::{document::*, ColorDepth};

pub struct AnsiOptions {
    pub terminal_width: usize,
    pub color_depth: ColorDepth,
}

impl Default for AnsiOptions {
    fn default() -> Self {
        Self {
            terminal_width: std::env::var("COLUMNS")
                .ok()
                .and_then(|s| s.parse().ok())
                .unwrap_or(80),
            color_depth: ColorDepth::Auto,
        }
    }
}

pub fn export_to_ansi_with_options(document: &Document, options: &AnsiOptions) -> Result<String> {
    let mut output = String::new();

    // Add document title
    write_ansi_heading(&mut output, &document.title, 1, options)?;
    output.push('\n');

    // Add metadata
    writeln!(
        output,
        "{}Document Information{}",
        format_ansi_text("", true, false, false, false, None, options),
        format_ansi_reset()
    )?;
    let prefix = "- File: ";
    let available = options.terminal_width.saturating_sub(prefix.len());
    let path = &document.metadata.file_path;
    let file_str = if UnicodeWidthStr::width(path.as_str()) <= available {
        path.clone()
    } else {
        let truncated: String = path
            .graphemes(true)
            .rev()
            .scan(0usize, |w, g| {
                *w += UnicodeWidthStr::width(g);
                if *w < available {
                    Some(g)
                } else {
                    None
                }
            })
            .collect::<Vec<_>>()
            .into_iter()
            .rev()
            .collect();
        format!("…{truncated}")
    };
    writeln!(output, "{prefix}{file_str}")?;
    writeln!(output, "- Pages: {}", document.metadata.page_count)?;
    writeln!(output, "- Words: {}", document.metadata.word_count)?;
    if let Some(author) = &document.metadata.author {
        writeln!(output, "- Author: {author}")?;
    }
    output.push('\n');

    // Separator
    let separator = "=".repeat(std::cmp::min(50, options.terminal_width));
    writeln!(output, "{separator}")?;
    output.push('\n');

    // Convert document content
    for element in &document.elements {
        match element {
            DocumentElement::Heading {
                level,
                text,
                number,
            } => {
                let heading_text = if let Some(number) = number {
                    format!("{number} {text}")
                } else {
                    text.clone()
                };
                write_ansi_heading(&mut output, &heading_text, *level, options)?;
                output.push('\n');
            }
            DocumentElement::Paragraph { runs } => {
                if runs.is_empty() || runs.iter().all(|run| run.text.trim().is_empty()) {
                    continue;
                }
                write_ansi_paragraph(&mut output, runs, options)?;
                output.push('\n');
            }
            DocumentElement::List { items, ordered } => {
                write_ansi_list(&mut output, items, *ordered, options)?;
                output.push('\n');
            }
            DocumentElement::Table { table } => {
                write_ansi_table(&mut output, table, options)?;
                output.push('\n');
            }
            DocumentElement::Image { description, .. } => {
                writeln!(
                    output,
                    "{}🖼️  [Image: {}]{}",
                    format_ansi_color(Some("#FF00FF"), options), // Magenta
                    description,
                    format_ansi_reset()
                )?;
                output.push('\n');
            }
            DocumentElement::Equation { latex, .. } => {
                writeln!(
                    output,
                    "{}📐 {}{}",
                    format_ansi_color(Some("#00AAFF"), options), // Cyan
                    latex,
                    format_ansi_reset()
                )?;
                output.push('\n');
            }
            DocumentElement::CodeBlock { text } => {
                let code_color = format_ansi_color(Some("#AAFFAA"), options);
                let reset = format_ansi_reset();
                for line in text.lines() {
                    writeln!(output, "  {code_color}{line}{reset}")?;
                }
                output.push('\n');
            }
            DocumentElement::TextBox { lines } => {
                let border_color = format_ansi_color(Some("#00FFFF"), options);
                let reset = format_ansi_reset();
                let inner_width = options.terminal_width.saturating_sub(4);
                let bar = "─".repeat(options.terminal_width.saturating_sub(2));
                writeln!(output, "{border_color}┌{bar}┐{reset}")?;
                for line in lines {
                    let truncated: String = line.chars().take(inner_width).collect();
                    writeln!(
                        output,
                        "{border_color}│{reset} {truncated:<inner_width$} {border_color}│{reset}",
                        inner_width = inner_width
                    )?;
                }
                writeln!(output, "{border_color}└{bar}┘{reset}")?;
                output.push('\n');
            }
            DocumentElement::PageBreak => {
                let separator = "─".repeat(std::cmp::min(60, options.terminal_width));
                writeln!(
                    output,
                    "{}{}{}",
                    format_ansi_color(Some("#666666"), options), // Dark gray
                    separator,
                    format_ansi_reset()
                )?;
                output.push('\n');
            }
        }
    }

    Ok(output)
}

fn write_ansi_heading(
    output: &mut String,
    text: &str,
    level: u8,
    options: &AnsiOptions,
) -> Result<()> {
    let color = match level {
        1 => Some("#FFFF00"), // Yellow
        2 => Some("#00FF00"), // Green
        _ => Some("#00FFFF"), // Cyan
    };

    let prefix = match level {
        1 => "■ ",
        2 => "  ▶ ",
        3 => "    ◦ ",
        _ => "      • ",
    };

    let prefix_width = UnicodeWidthStr::width(prefix);
    let available_width = options.terminal_width.saturating_sub(prefix_width);
    let wrapped = wrap_plain_text(text, available_width);
    let indent = " ".repeat(prefix_width);

    for (i, line) in wrapped.iter().enumerate() {
        let display = if i == 0 {
            format!("{prefix}{line}")
        } else {
            format!("{indent}{line}")
        };
        writeln!(
            output,
            "{}",
            format_ansi_text(&display, true, false, false, false, color, options)
        )?;
    }

    Ok(())
}

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

    let mut lines = Vec::new();
    let mut current_line = String::new();
    let mut current_width = 0;

    for word in text.split_whitespace() {
        let word_width = UnicodeWidthStr::width(word);
        if current_width == 0 {
            current_line.push_str(word);
            current_width = word_width;
        } else if current_width + 1 + word_width > max_width {
            lines.push(current_line.clone());
            current_line = word.to_string();
            current_width = word_width;
        } else {
            current_line.push(' ');
            current_line.push_str(word);
            current_width += 1 + word_width;
        }
    }

    if !current_line.is_empty() {
        lines.push(current_line);
    }
    if lines.is_empty() {
        lines.push(String::new());
    }

    lines
}

fn write_ansi_paragraph(
    output: &mut String,
    runs: &[FormattedRun],
    options: &AnsiOptions,
) -> Result<()> {
    let wrapped_lines = wrap_formatted_runs(runs, options);
    for line in wrapped_lines {
        writeln!(output, "{}{}", line, format_ansi_reset())?;
    }
    Ok(())
}

/// Wrap formatted text runs to terminal width while preserving formatting
fn wrap_formatted_runs(runs: &[FormattedRun], options: &AnsiOptions) -> Vec<String> {
    if runs.is_empty() {
        return vec![];
    }

    let max_width = options.terminal_width;
    let mut lines = Vec::new();
    let mut current_line = String::new();
    let mut current_width = 0;
    let mut line_needs_formatting = false;

    for run in runs {
        let graphemes: Vec<&str> = run.text.graphemes(true).collect();
        let mut word = String::new();
        let mut word_width = 0;

        // Apply formatting at start of run
        let format_start = get_ansi_format_start(
            run.formatting.bold,
            run.formatting.italic,
            run.formatting.underline,
            run.formatting.strikethrough,
            run.formatting.color.as_deref(),
            options,
        );

        for grapheme in graphemes {
            let grapheme_width = UnicodeWidthStr::width(grapheme);

            if grapheme == " " || grapheme == "\n" {
                // End of word - try to add it to the current line
                if !word.is_empty() {
                    if current_width + word_width > max_width && current_width > 0 {
                        // Word doesn't fit on current line, start new line
                        if line_needs_formatting {
                            current_line.push_str(&format_ansi_reset());
                        }
                        lines.push(current_line.clone());
                        current_line.clear();
                        current_width = 0;
                        line_needs_formatting = false;
                    }

                    // Apply formatting if not already applied on this line
                    if !line_needs_formatting && !format_start.is_empty() {
                        current_line.push_str(&format_start);
                        line_needs_formatting = true;
                    }

                    current_line.push_str(&word);
                    current_width += word_width;

                    word.clear();
                    word_width = 0;
                }

                // Handle space or newline
                if grapheme == "\n" {
                    if line_needs_formatting {
                        current_line.push_str(&format_ansi_reset());
                    }
                    lines.push(current_line.clone());
                    current_line.clear();
                    current_width = 0;
                    line_needs_formatting = false;
                } else if current_width < max_width {
                    current_line.push(' ');
                    current_width += 1;
                }
            } else {
                // Building a word
                word.push_str(grapheme);
                word_width += grapheme_width;
            }
        }

        // Handle remaining word at end of run
        if !word.is_empty() {
            if current_width + word_width > max_width && current_width > 0 {
                if line_needs_formatting {
                    current_line.push_str(&format_ansi_reset());
                }
                lines.push(current_line.clone());
                current_line.clear();
                current_width = 0;
                line_needs_formatting = false;
            }

            // Apply formatting if not already applied on this line
            if !line_needs_formatting && !format_start.is_empty() {
                current_line.push_str(&format_start);
                line_needs_formatting = true;
            }

            current_line.push_str(&word);
            current_width += word_width;
        }

        // Reset formatting at end of run if it was applied
        if line_needs_formatting && !current_line.is_empty() {
            current_line.push_str(&format_ansi_reset());
            line_needs_formatting = false;
        }
    }

    // Add final line if not empty
    if !current_line.is_empty() {
        lines.push(current_line);
    }

    lines
}

/// Get ANSI formatting codes for start of formatted text
fn get_ansi_format_start(
    bold: bool,
    italic: bool,
    underline: bool,
    strikethrough: bool,
    color: Option<&str>,
    options: &AnsiOptions,
) -> String {
    let mut result = String::new();

    if bold {
        result.push_str(&format!("{}", SetAttribute(Attribute::Bold)));
    }
    if italic {
        result.push_str(&format!("{}", SetAttribute(Attribute::Italic)));
    }
    if underline {
        result.push_str(&format!("{}", SetAttribute(Attribute::Underlined)));
    }
    if strikethrough {
        result.push_str(&format!("{}", SetAttribute(Attribute::CrossedOut)));
    }
    if let Some(color_hex) = color {
        result.push_str(&format_ansi_color(Some(color_hex), options));
    }

    result
}

fn write_ansi_list(
    output: &mut String,
    items: &[ListItem],
    ordered: bool,
    options: &AnsiOptions,
) -> Result<()> {
    for (i, item) in items.iter().enumerate() {
        let bullet = if ordered {
            format!("{}. ", i + 1)
        } else {
            "• ".to_string()
        };

        let indent = "  ".repeat(item.level as usize);
        let bullet_color = format_ansi_color(Some("#0066FF"), options); // Blue
        let prefix = format!("{}{}{}", bullet_color, indent, bullet);
        let prefix_visual_width = indent.len() + bullet.len();

        // Wrap item text with proper indentation
        let available_width = options.terminal_width.saturating_sub(prefix_visual_width);
        let wrapped_lines = wrap_formatted_runs_with_width(&item.runs, available_width, options);

        for (line_idx, line) in wrapped_lines.iter().enumerate() {
            if line_idx == 0 {
                // First line: include bullet
                writeln!(output, "{}{}{}", prefix, format_ansi_reset(), line)?;
            } else {
                // Continuation lines: indent to align with first line
                writeln!(output, "{}{}", " ".repeat(prefix_visual_width), line)?;
            }
        }
    }
    Ok(())
}

/// Wrap formatted text runs to a specific width
fn wrap_formatted_runs_with_width(
    runs: &[FormattedRun],
    max_width: usize,
    options: &AnsiOptions,
) -> Vec<String> {
    if runs.is_empty() || max_width == 0 {
        return vec![String::new()];
    }

    let mut lines = Vec::new();
    let mut current_line = String::new();
    let mut current_width = 0;
    let mut line_needs_formatting = false;

    for run in runs {
        let graphemes: Vec<&str> = run.text.graphemes(true).collect();
        let mut word = String::new();
        let mut word_width = 0;

        // Get formatting codes for this run
        let format_start = get_ansi_format_start(
            run.formatting.bold,
            run.formatting.italic,
            run.formatting.underline,
            run.formatting.strikethrough,
            run.formatting.color.as_deref(),
            options,
        );

        for grapheme in graphemes {
            let grapheme_width = UnicodeWidthStr::width(grapheme);

            if grapheme == " " || grapheme == "\n" {
                // End of word - try to add it to the current line
                if !word.is_empty() {
                    if current_width + word_width > max_width && current_width > 0 {
                        // Word doesn't fit on current line, start new line
                        if line_needs_formatting {
                            current_line.push_str(&format_ansi_reset());
                        }
                        lines.push(current_line.clone());
                        current_line.clear();
                        current_width = 0;
                        line_needs_formatting = false;
                    }

                    // Apply formatting if not already applied on this line
                    if !line_needs_formatting && !format_start.is_empty() {
                        current_line.push_str(&format_start);
                        line_needs_formatting = true;
                    }

                    current_line.push_str(&word);
                    current_width += word_width;

                    word.clear();
                    word_width = 0;
                }

                // Handle space or newline
                if grapheme == "\n" {
                    if line_needs_formatting {
                        current_line.push_str(&format_ansi_reset());
                    }
                    lines.push(current_line.clone());
                    current_line.clear();
                    current_width = 0;
                    line_needs_formatting = false;
                } else if current_width < max_width {
                    current_line.push(' ');
                    current_width += 1;
                }
            } else {
                // Building a word
                word.push_str(grapheme);
                word_width += grapheme_width;
            }
        }

        // Handle remaining word at end of run
        if !word.is_empty() {
            if current_width + word_width > max_width && current_width > 0 {
                if line_needs_formatting {
                    current_line.push_str(&format_ansi_reset());
                }
                lines.push(current_line.clone());
                current_line.clear();
                current_width = 0;
                line_needs_formatting = false;
            }

            // Apply formatting if not already applied on this line
            if !line_needs_formatting && !format_start.is_empty() {
                current_line.push_str(&format_start);
                line_needs_formatting = true;
            }

            current_line.push_str(&word);
            current_width += word_width;
        }

        // Reset formatting at end of run if it was applied
        if line_needs_formatting && !current_line.is_empty() {
            current_line.push_str(&format_ansi_reset());
            line_needs_formatting = false;
        }
    }

    // Add final line if not empty
    if !current_line.is_empty() {
        lines.push(current_line);
    }

    // Return at least one line even if empty
    if lines.is_empty() {
        lines.push(String::new());
    }

    lines
}

fn write_ansi_table(output: &mut String, table: &TableData, options: &AnsiOptions) -> Result<()> {
    // Add table title if present
    if let Some(title) = &table.metadata.title {
        let formatted_title = format_ansi_text(
            &format!("📊 {title}"),
            true,
            false,
            false,
            false,
            Some("#0066FF"), // Blue
            options,
        );
        writeln!(output, "{}{}", formatted_title, format_ansi_reset())?;
        output.push('\n');
    }

    // Simple table rendering for ANSI
    if !table.headers.is_empty() {
        // Headers
        write!(output, "│")?;
        for header in &table.headers {
            write!(
                output,
                " {}{}{} │",
                format_ansi_text("", true, false, false, false, None, options),
                header.content,
                format_ansi_reset()
            )?;
        }
        writeln!(output)?;

        // Separator
        write!(output, "├")?;
        for _ in &table.headers {
            write!(output, "─────┼")?;
        }
        writeln!(output, "┤")?;

        // Rows
        for row in &table.rows {
            write!(output, "│")?;
            for cell in row {
                write!(output, " {} │", cell.content)?;
            }
            writeln!(output)?;
        }
    }

    Ok(())
}

fn format_ansi_text(
    text: &str,
    bold: bool,
    italic: bool,
    underline: bool,
    strikethrough: bool,
    color: Option<&str>,
    options: &AnsiOptions,
) -> String {
    let mut result = String::new();

    // Apply formatting attributes
    if bold {
        result.push_str(&format!("{}", SetAttribute(Attribute::Bold)));
    }
    if italic {
        result.push_str(&format!("{}", SetAttribute(Attribute::Italic)));
    }
    if underline {
        result.push_str(&format!("{}", SetAttribute(Attribute::Underlined)));
    }
    if strikethrough {
        result.push_str(&format!("{}", SetAttribute(Attribute::CrossedOut)));
    }

    // Apply color
    if let Some(color_hex) = color {
        result.push_str(&format_ansi_color(Some(color_hex), options));
    }

    result.push_str(text);

    // Reset formatting after this run to prevent bleeding into subsequent runs
    result.push_str(&format_ansi_reset());

    result
}

fn format_ansi_color(color_hex: Option<&str>, options: &AnsiOptions) -> String {
    let Some(hex) = color_hex else {
        return String::new();
    };

    match convert_hex_to_crossterm_color(hex, &options.color_depth) {
        Some(color) => format!("{}", SetForegroundColor(color)),
        None => String::new(),
    }
}

fn format_ansi_reset() -> String {
    format!("{ResetColor}")
}

fn convert_hex_to_crossterm_color(hex: &str, color_depth: &ColorDepth) -> Option<CrosstermColor> {
    // Remove # if present and ensure we have 6 characters
    let hex = hex.trim_start_matches('#');
    if hex.len() != 6 {
        return None;
    }

    // Parse RGB components
    let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
    let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
    let b = u8::from_str_radix(&hex[4..6], 16).ok()?;

    match color_depth {
        ColorDepth::Monochrome => None,
        ColorDepth::Standard => {
            // Convert to 16 colors (approximation)
            let color_index = rgb_to_ansi_16(r, g, b);
            Some(CrosstermColor::AnsiValue(color_index))
        }
        ColorDepth::Extended => {
            // Convert to 256 colors
            let color_index = rgb_to_ansi_256(r, g, b);
            Some(CrosstermColor::AnsiValue(color_index))
        }
        ColorDepth::TrueColor | ColorDepth::Auto => {
            // Use full RGB
            Some(CrosstermColor::Rgb { r, g, b })
        }
    }
}

fn rgb_to_ansi_16(r: u8, g: u8, b: u8) -> u8 {
    // Simple mapping to 16 colors
    let r_bright = r > 127;
    let g_bright = g > 127;
    let b_bright = b > 127;

    let base = match (r > 64, g > 64, b > 64) {
        (false, false, false) => 0, // Black
        (false, false, true) => 4,  // Blue
        (false, true, false) => 2,  // Green
        (false, true, true) => 6,   // Cyan
        (true, false, false) => 1,  // Red
        (true, false, true) => 5,   // Magenta
        (true, true, false) => 3,   // Yellow
        (true, true, true) => 7,    // White
    };

    // Add 8 for bright colors if any component is very bright
    if r_bright || g_bright || b_bright {
        base + 8
    } else {
        base
    }
}

fn rgb_to_ansi_256(r: u8, g: u8, b: u8) -> u8 {
    // 256-color conversion
    if r == g && g == b {
        // Grayscale
        if r < 8 {
            16
        } else if r > 247 {
            231
        } else {
            232 + (r - 8) / 10
        }
    } else {
        // Color cube: 16 + 36*r + 6*g + b
        let r_index = (r as f32 / 255.0 * 5.0) as u8;
        let g_index = (g as f32 / 255.0 * 5.0) as u8;
        let b_index = (b as f32 / 255.0 * 5.0) as u8;
        16 + 36 * r_index + 6 * g_index + b_index
    }
}
