use crate::cli::OutputFormat;
use crate::config::PipConfig;
use crate::discover::ConfigLocation;
use crate::schema::KeySchema;
use colored::*;

pub fn print_config(cfg: &PipConfig, format: &OutputFormat, no_color: bool) {
    match format {
        OutputFormat::Json => {
            let json = cfg.to_json();
            println!(
                "{}",
                serde_json::to_string_pretty(&json).unwrap_or_default()
            );
        }
        OutputFormat::Ini | OutputFormat::Plain => {
            println!("{}", cfg.to_ini());
        }
        OutputFormat::Toml => {
            print_as_toml(cfg);
        }
        OutputFormat::Table => {
            print_as_table(cfg, no_color);
        }
    }
}

fn print_as_table(cfg: &PipConfig, no_color: bool) {
    if cfg.sections.is_empty() {
        println!(
            "{}",
            if no_color {
                "No configuration found.".to_string()
            } else {
                "No configuration found.".yellow().to_string()
            }
        );
        return;
    }

    for (section, keys) in &cfg.sections {
        if keys.is_empty() {
            continue;
        }

        let header = format!(" [{}] ", section);
        if no_color {
            println!("\n{}", header);
            println!("{}", "─".repeat(70));
        } else {
            println!("\n{}", header.bold().cyan());
            println!("{}", "─".repeat(70).dimmed());
        }

        let key_w = keys.keys().map(|k| k.len()).max().unwrap_or(20).max(20);

        for (key, value) in keys {
            let display_val = if value.contains('\n') {
                let lines: Vec<&str> = value.lines().collect();
                let first = lines[0];
                let rest_indent = " ".repeat(key_w + 5);
                let rest: Vec<String> = lines[1..]
                    .iter()
                    .map(|l| format!("{}{}", rest_indent, l.trim()))
                    .collect();
                if rest.is_empty() {
                    first.to_string()
                } else {
                    format!("{}\n{}", first, rest.join("\n"))
                }
            } else {
                value.clone()
            };

            if no_color {
                println!("  {:<width$}  {}", key, display_val, width = key_w);
            } else {
                println!(
                    "  {:<width$}  {}",
                    key.green(),
                    display_val.white(),
                    width = key_w
                );
            }
        }
    }
    println!();
}

fn print_as_toml(cfg: &PipConfig) {
    for (section, keys) in &cfg.sections {
        if keys.is_empty() {
            continue;
        }
        println!("[{}]", section);
        for (key, value) in keys {
            let toml_key = key.replace('-', "_");
            if value.contains('\n') {
                let items: Vec<String> =
                    value.lines().map(|l| format!("\"{}\"", l.trim())).collect();
                println!("{} = [{}]", toml_key, items.join(", "));
            } else if value == "true" || value == "false" {
                println!("{} = {}", toml_key, value);
            } else {
                println!("{} = \"{}\"", toml_key, value);
            }
        }
        println!();
    }
}

pub fn print_locations(locs: &[ConfigLocation], no_color: bool) {
    let header_fmt = |s: &str| {
        if no_color {
            s.to_string()
        } else {
            s.bold().cyan().to_string()
        }
    };
    let path_fmt = |s: &str, exists: bool| {
        if no_color {
            s.to_string()
        } else if exists {
            s.green().to_string()
        } else {
            s.dimmed().to_string()
        }
    };
    let tag_fmt = |s: &str| {
        if no_color {
            format!("[{}]", s)
        } else {
            format!("[{}]", s).yellow().to_string()
        }
    };

    println!("\n{}", header_fmt("Pip Configuration File Locations"));
    println!("{}", "─".repeat(70));

    for loc in locs {
        let exists_tag = if loc.exists {
            if no_color {
                "[exists]".to_string()
            } else {
                "[exists]".green().to_string()
            }
        } else if no_color {
            "[missing]".to_string()
        } else {
            "[missing]".dimmed().to_string()
        };
        let write_tag = if loc.writable {
            if no_color {
                "[writable]".to_string()
            } else {
                "[writable]".blue().to_string()
            }
        } else if no_color {
            "[readonly]".to_string()
        } else {
            "[readonly]".red().to_string()
        };
        println!(
            "  {} {} {}  {}",
            tag_fmt(&loc.level.to_string()),
            exists_tag,
            write_tag,
            path_fmt(&loc.path.display().to_string(), loc.exists),
        );
        if no_color {
            println!("    {}", loc.description);
        } else {
            println!("    {}", loc.description.dimmed());
        }
    }
    println!();
}

pub fn print_schema(keys: &[KeySchema], no_color: bool) {
    let current_section = std::cell::RefCell::new("".to_string());

    for key in keys {
        if *current_section.borrow() != key.section {
            let sec_header = format!("\n[{}]", key.section);
            if no_color {
                println!("{}", sec_header);
                println!("{}", "─".repeat(60));
            } else {
                println!("{}", sec_header.bold().cyan());
                println!("{}", "─".repeat(60).dimmed());
            }
            *current_section.borrow_mut() = key.section.to_string();
        }

        let key_str = if no_color {
            key.key.to_string()
        } else {
            key.key.green().bold().to_string()
        };
        let type_str = format!("{:?}", key.value_type);
        let type_str = if no_color {
            format!("({})", type_str)
        } else {
            format!("({})", type_str).yellow().to_string()
        };
        let default_str = key
            .default
            .map(|d| {
                if no_color {
                    format!("default: {}", d)
                } else {
                    format!("default: {}", d).dimmed().to_string()
                }
            })
            .unwrap_or_default();

        println!("  {} {} {}", key_str, type_str, default_str);

        if no_color {
            println!("    {}", key.description);
        } else {
            println!("    {}", key.description.white());
        }

        if let Some(example) = key.example {
            if no_color {
                println!("    example: {}", example);
            } else {
                println!("    {}: {}", "example".dimmed(), example.italic());
            }
        }

        if let Some(env) = key.env_var {
            if no_color {
                println!("    env: {}", env);
            } else {
                println!("    {}: {}", "env".dimmed(), env.blue());
            }
        }

        if key.deprecated {
            let dep_msg = key.deprecated_msg.unwrap_or("deprecated");
            if no_color {
                println!("    ⚠ DEPRECATED: {}", dep_msg);
            } else {
                println!("    {}: {}", "⚠ DEPRECATED".red().bold(), dep_msg.red());
            }
        }
        println!();
    }
}

pub fn print_env_vars(vars: &[(&str, &str)], no_color: bool) {
    println!(
        "\n{}",
        if no_color {
            "Pip-related Environment Variables".to_string()
        } else {
            "Pip-related Environment Variables"
                .bold()
                .cyan()
                .to_string()
        }
    );
    println!("{}", "─".repeat(70));

    let key_w = vars.iter().map(|(k, _)| k.len()).max().unwrap_or(30);

    for (name, desc) in vars {
        let val = std::env::var(name).ok();
        let name_str = if no_color {
            name.to_string()
        } else {
            name.green().to_string()
        };
        let val_str = match &val {
            Some(v) => {
                if no_color {
                    format!("= {}", v)
                } else {
                    format!("= {}", v.yellow())
                }
            }
            None => {
                if no_color {
                    "(unset)".to_string()
                } else {
                    "(unset)".dimmed().to_string()
                }
            }
        };
        println!(
            "  {:<width$}  {}  {}",
            name_str,
            val_str,
            if no_color {
                desc.to_string()
            } else {
                desc.dimmed().to_string()
            },
            width = key_w
        );
    }
    println!();
}

pub fn print_diff(
    file_a: &str,
    cfg_a: &PipConfig,
    file_b: &str,
    cfg_b: &PipConfig,
    no_color: bool,
) {
    println!("Diff: {} vs {}", file_a, file_b);
    println!("{}", "─".repeat(70));

    let all_sections: std::collections::BTreeSet<String> = cfg_a
        .sections
        .keys()
        .chain(cfg_b.sections.keys())
        .cloned()
        .collect();

    let mut found_diff = false;

    for section in &all_sections {
        let keys_a = cfg_a.sections.get(section);
        let keys_b = cfg_b.sections.get(section);

        let all_keys: std::collections::BTreeSet<String> = keys_a
            .map(|m| m.keys().cloned().collect::<std::collections::BTreeSet<_>>())
            .unwrap_or_default()
            .union(
                &keys_b
                    .map(|m| m.keys().cloned().collect::<std::collections::BTreeSet<_>>())
                    .unwrap_or_default(),
            )
            .cloned()
            .collect();

        for key in &all_keys {
            let va = keys_a.and_then(|m| m.get(key));
            let vb = keys_b.and_then(|m| m.get(key));

            match (va, vb) {
                (Some(a), Some(b)) if a == b => {}
                (Some(a), None) => {
                    found_diff = true;
                    let line = format!("- [{section}] {key} = {a}");
                    if no_color {
                        println!("{}", line)
                    } else {
                        println!("{}", line.red())
                    };
                }
                (None, Some(b)) => {
                    found_diff = true;
                    let line = format!("+ [{section}] {key} = {b}");
                    if no_color {
                        println!("{}", line)
                    } else {
                        println!("{}", line.green())
                    };
                }
                (Some(a), Some(b)) => {
                    found_diff = true;
                    let line_a = format!("- [{section}] {key} = {a}");
                    let line_b = format!("+ [{section}] {key} = {b}");
                    if no_color {
                        println!("{}", line_a)
                    } else {
                        println!("{}", line_a.red())
                    };
                    if no_color {
                        println!("{}", line_b)
                    } else {
                        println!("{}", line_b.green())
                    };
                }
                _ => {}
            }
        }
    }

    if !found_diff {
        println!("No differences found.");
    }
}

pub fn success(msg: &str, no_color: bool) {
    if no_color {
        println!("✓ {}", msg);
    } else {
        println!("{} {}", "✓".green().bold(), msg.green());
    }
}

pub fn warn(msg: &str, no_color: bool) {
    if no_color {
        eprintln!("⚠ {}", msg);
    } else {
        eprintln!("{} {}", "⚠".yellow().bold(), msg.yellow());
    }
}

pub fn error_msg(msg: &str, no_color: bool) {
    if no_color {
        eprintln!("✗ {}", msg);
    } else {
        eprintln!("{} {}", "✗".red().bold(), msg.red());
    }
}

pub fn info(msg: &str, no_color: bool) {
    if no_color {
        println!("• {}", msg);
    } else {
        println!("{} {}", "•".blue(), msg);
    }
}
