use crate::change::{canonical_path, parse_name_status_output};
use crate::git_cmd::run_git;
use crate::print::emit_file_from_commit;
use crate::progress::ProgressRunner;
use crate::render::{color_reset_for_commit, kind_color_for_commit, render_tree_for_commit};
use anyhow::Result;
use std::collections::HashMap;

pub fn render_commit(
    commit: &str,
    parent_selector: Option<&str>,
    no_color: bool,
    print: bool,
    progress_opt_in: bool,
) -> Result<()> {
    print_commit_metadata(commit, no_color)?;
    print_commit_message(commit)?;

    let files = render_commit_files(commit, parent_selector, no_color)?;

    if print && !files.is_empty() {
        println!();
        println!("📦 Printing file contents:");

        let progress = ProgressRunner::new(files.len() as u64, progress_opt_in);

        for file in files {
            progress.run(&file, || -> Result<()> {
                emit_file_from_commit(commit, &file)?;
                println!();
                Ok(())
            })?;
        }

        progress.finish();
    }

    Ok(())
}

fn print_commit_metadata(commit: &str, no_color: bool) -> Result<()> {
    println!();
    if no_color {
        run_git(&[
            "log",
            "-1",
            "--color=never",
            "--date=format:%Y-%m-%d %H:%M:%S %z",
            "--pretty=format:🔖 %h %s%n👤 %an <%ae>%n📅 %ad",
            commit,
        ])?;
    } else {
        run_git(&[
            "log",
            "-1",
            "--date=format:%Y-%m-%d %H:%M:%S %z",
            "--pretty=format:🔖 %C(bold #82aaff)%h%C(reset) %C(#d6deeb)%s%C(reset)%n👤 %C(#7fdbca)%an%C(reset) <%C(#d6deeb)%ae%C(reset)>%n📅 %C(#ecc48d)%ad%C(reset)",
            commit,
        ])?;
    }
    Ok(())
}

fn print_commit_message(commit: &str) -> Result<()> {
    println!("\n📝 Commit Message:");
    let output = run_git(&["log", "-1", "--pretty=format:%B", commit])?;
    for (idx, line) in output.lines().enumerate() {
        if idx == 0 {
            println!("   {line}");
        } else if line.trim().is_empty() {
            println!();
        } else {
            println!("   {line}");
        }
    }
    Ok(())
}

fn render_commit_files(
    commit: &str,
    parent_selector: Option<&str>,
    no_color: bool,
) -> Result<Vec<String>> {
    let parents = commit_parents(commit)?;
    let parent_count = parents.len();
    let is_merge = parent_count > 1;

    let mut preface_lines: Vec<String> = Vec::new();
    let mut selected_index = 1usize;

    if is_merge {
        if let Some(selector) = parent_selector {
            match selector.parse::<usize>() {
                Ok(value) => selected_index = value,
                Err(_) => {
                    preface_lines.push(format!(
                        "  ⚠️  Invalid --parent value '{selector}' — falling back to parent #1"
                    ));
                    selected_index = 1;
                }
            }
        }

        if selected_index < 1 || selected_index > parent_count {
            preface_lines.push(format!(
                "  ⚠️  Parent index {} out of range (1-{}) — falling back to parent #1",
                selected_index, parent_count
            ));
            selected_index = 1;
        }
    }

    let ns_lines: String;
    let numstat_lines: String;
    let mut selected_parent_short = String::new();

    if is_merge {
        let selected_parent_hash = &parents[selected_index - 1];
        selected_parent_short = run_git(&["rev-parse", "--short", selected_parent_hash])?
            .trim()
            .to_string();

        ns_lines = run_git(&["diff", "--name-status", selected_parent_hash, commit])?;
        numstat_lines = run_git(&["diff", "--numstat", selected_parent_hash, commit])?;

        if ns_lines.trim().is_empty() {
            println!("\n📄 Changed files:");
            println!(
                "  ℹ️  Merge commit vs parent #{} ({}) has no file-level changes",
                selected_index,
                if selected_parent_short.is_empty() {
                    selected_parent_hash
                } else {
                    &selected_parent_short
                }
            );
            return Ok(Vec::new());
        }
    } else {
        ns_lines = run_git(&["show", "--pretty=format:", "--name-status", commit])?;
        numstat_lines = run_git(&["show", "--pretty=format:", "--numstat", commit])?;

        if ns_lines.trim().is_empty() || numstat_lines.trim().is_empty() {
            println!("\n📄 Changed files:");
            println!("  ℹ️  No file-level changes recorded for this commit");
            return Ok(Vec::new());
        }
    }

    let mut numstat_by_path: HashMap<String, (String, String)> = HashMap::new();
    for line in numstat_lines.lines() {
        let mut parts = line.split('\t');
        let add = match parts.next() {
            Some(v) => v.to_string(),
            None => continue,
        };
        let del = match parts.next() {
            Some(v) => v.to_string(),
            None => continue,
        };
        let raw_path = match parts.next() {
            Some(v) => v.to_string(),
            None => continue,
        };

        let canonical = canonical_path(&raw_path);
        numstat_by_path.insert(canonical, (add, del));
    }

    println!("\n📄 Changed files:");
    for line in preface_lines {
        println!("{line}");
    }

    if is_merge {
        println!(
            "  ℹ️  Merge commit with {} parents — showing diff against parent #{} ({})",
            parent_count,
            selected_index,
            if selected_parent_short.is_empty() {
                parents[selected_index - 1].as_str()
            } else {
                &selected_parent_short
            }
        );
    }

    let mut files: Vec<String> = Vec::new();
    let mut total_add = 0i64;
    let mut total_del = 0i64;
    let reset = color_reset_for_commit(no_color);

    for entry in parse_name_status_output(&ns_lines) {
        let display_path = entry.display_path();
        let file_path = entry.file_path();

        files.push(file_path.clone());

        let (mut add, mut del) = ("-".to_string(), "-".to_string());
        if let Some((a, d)) = numstat_by_path.get(&file_path) {
            add = a.clone();
            del = d.clone();
            if let Ok(val) = a.parse::<i64>() {
                total_add += val;
            }
            if let Ok(val) = d.parse::<i64>() {
                total_del += val;
            }
        }

        let color = kind_color_for_commit(&entry.kind, no_color);
        println!(
            "  {color}➤ [{}] {display_path}  [+{add} / -{del}]{reset}",
            entry.kind
        );
    }

    println!("\n  📊 Total: +{total_add} / -{total_del}");
    render_tree_for_commit(&files, no_color)?;

    Ok(files)
}

fn commit_parents(commit: &str) -> Result<Vec<String>> {
    let output = run_git(&["show", "-s", "--pretty=%P", commit])?;
    let parents = output
        .split_whitespace()
        .map(|v| v.to_string())
        .collect::<Vec<_>>();
    Ok(parents)
}
