//! Merge conflict resolution helpers.

use std::path::Path;
use std::process::Command;

pub(super) fn try_resolve_conflicts(worktree: &Path) -> Result<(bool, String), String> {
    let mut log = String::new();

    // List conflicted files.
    let ls = Command::new("git")
        .args(["diff", "--name-only", "--diff-filter=U"])
        .current_dir(worktree)
        .output()
        .map_err(|e| format!("Failed to list conflicted files: {e}"))?;

    let file_list = String::from_utf8_lossy(&ls.stdout);
    let conflicted_files: Vec<&str> = file_list.lines().filter(|l| !l.is_empty()).collect();

    if conflicted_files.is_empty() {
        log.push_str("No conflicted files found (conflict may be index-only).\n");
        return Ok((false, log));
    }

    log.push_str(&format!("Conflicted files ({}):\n", conflicted_files.len()));
    for f in &conflicted_files {
        log.push_str(&format!("  - {f}\n"));
    }

    // First pass: check that all files can be resolved before touching any.
    let mut resolutions: Vec<(&str, String)> = Vec::new();
    for file in &conflicted_files {
        let file_path = worktree.join(file);
        let content = std::fs::read_to_string(&file_path)
            .map_err(|e| format!("Failed to read conflicted file '{file}': {e}"))?;

        match resolve_simple_conflicts(&content) {
            Some(resolved) => {
                log.push_str(&format!("  [auto-resolve] {file}\n"));
                resolutions.push((file, resolved));
            }
            None => {
                log.push_str(&format!("  [COMPLEX — cannot auto-resolve] {file}\n"));
                return Ok((false, log));
            }
        }
    }

    // Second pass: write resolved content and stage.
    for (file, resolved) in &resolutions {
        let file_path = worktree.join(file);
        std::fs::write(&file_path, resolved)
            .map_err(|e| format!("Failed to write resolved file '{file}': {e}"))?;

        let add = Command::new("git")
            .args(["add", file])
            .current_dir(worktree)
            .output()
            .map_err(|e| format!("Failed to stage resolved file '{file}': {e}"))?;
        if !add.status.success() {
            return Err(format!(
                "git add failed for '{file}': {}",
                String::from_utf8_lossy(&add.stderr)
            ));
        }
    }

    Ok((true, log))
}

fn resolve_simple_conflicts(content: &str) -> Option<String> {
    // Quick check: if there are no conflict markers at all, nothing to do.
    if !content.contains("<<<<<<<") {
        return Some(content.to_string());
    }

    let mut result = String::new();
    let mut lines = content.lines().peekable();

    while let Some(line) = lines.next() {
        if line.starts_with("<<<<<<<") {
            // Collect the "ours" side (between <<<<<<< and =======).
            let mut ours = Vec::new();
            let mut found_separator = false;
            for next_line in lines.by_ref() {
                if next_line.starts_with("=======") {
                    found_separator = true;
                    break;
                }
                ours.push(next_line);
            }
            if !found_separator {
                return None; // Malformed conflict block.
            }

            // Collect the "theirs" side (between ======= and >>>>>>>).
            let mut theirs = Vec::new();
            let mut found_end = false;
            for next_line in lines.by_ref() {
                if next_line.starts_with(">>>>>>>") {
                    found_end = true;
                    break;
                }
                theirs.push(next_line);
            }
            if !found_end {
                return None; // Malformed conflict block.
            }

            // Both sides must be non-empty additions to be considered simple.
            // If either side is empty, it means one side deleted something — complex.
            if ours.is_empty() && theirs.is_empty() {
                // Both empty — nothing to add, skip.
                continue;
            }

            // Accept both: ours first, then theirs.
            for l in &ours {
                result.push_str(l);
                result.push('\n');
            }
            for l in &theirs {
                result.push_str(l);
                result.push('\n');
            }
        } else {
            result.push_str(line);
            result.push('\n');
        }
    }

    // Preserve trailing newline consistency: if original ended without
    // newline, strip the trailing one we added.
    if !content.ends_with('\n') && result.ends_with('\n') {
        result.pop();
    }

    Some(result)
}

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

    #[test]
    fn resolve_simple_conflicts_no_markers() {
        let input = "line 1\nline 2\nline 3\n";
        let result = resolve_simple_conflicts(input);
        assert_eq!(result, Some(input.to_string()));
    }

    #[test]
    fn resolve_simple_conflicts_additive() {
        let input = "\
before
<<<<<<< HEAD
ours line 1
ours line 2
=======
theirs line 1
theirs line 2
>>>>>>> feature
after
";
        let result = resolve_simple_conflicts(input).unwrap();
        assert!(
            !result.contains("<<<<<<<"),
            "should not contain conflict markers"
        );
        assert!(
            !result.contains(">>>>>>>"),
            "should not contain conflict markers"
        );
        assert!(result.contains("ours line 1"));
        assert!(result.contains("ours line 2"));
        assert!(result.contains("theirs line 1"));
        assert!(result.contains("theirs line 2"));
        assert!(result.contains("before"));
        assert!(result.contains("after"));
        // Ours comes before theirs
        let ours_pos = result.find("ours line 1").unwrap();
        let theirs_pos = result.find("theirs line 1").unwrap();
        assert!(ours_pos < theirs_pos, "ours should come before theirs");
    }

    #[test]
    fn resolve_simple_conflicts_multiple_blocks() {
        let input = "\
header
<<<<<<< HEAD
ours block 1
=======
theirs block 1
>>>>>>> feature
middle
<<<<<<< HEAD
ours block 2
=======
theirs block 2
>>>>>>> feature
footer
";
        let result = resolve_simple_conflicts(input).unwrap();
        assert!(!result.contains("<<<<<<<"));
        assert!(result.contains("ours block 1"));
        assert!(result.contains("theirs block 1"));
        assert!(result.contains("ours block 2"));
        assert!(result.contains("theirs block 2"));
        assert!(result.contains("header"));
        assert!(result.contains("middle"));
        assert!(result.contains("footer"));
    }

    #[test]
    fn resolve_simple_conflicts_malformed_no_separator() {
        let input = "\
<<<<<<< HEAD
ours
>>>>>>> feature
";
        let result = resolve_simple_conflicts(input);
        assert!(
            result.is_none(),
            "malformed conflict (no separator) should return None"
        );
    }

    #[test]
    fn resolve_simple_conflicts_malformed_no_end() {
        let input = "\
<<<<<<< HEAD
ours
=======
theirs
";
        let result = resolve_simple_conflicts(input);
        assert!(
            result.is_none(),
            "malformed conflict (no end marker) should return None"
        );
    }

    #[test]
    fn resolve_simple_conflicts_preserves_no_trailing_newline() {
        let input = "before\n<<<<<<< HEAD\nours\n=======\ntheirs\n>>>>>>> branch\nafter";
        let result = resolve_simple_conflicts(input).unwrap();
        assert!(
            !result.ends_with('\n'),
            "should not add trailing newline if original lacks one"
        );
        assert!(result.ends_with("after"));
    }

    #[test]
    fn resolve_simple_conflicts_real_markers_additive_both_sides() {
        // The most common real-world case: both branches add different content
        // (e.g. different functions) to the same region of a file.
        let input = "// shared code\n\
<<<<<<< HEAD\n\
fn master_fn() { println!(\"from master\"); }\n\
=======\n\
fn feature_fn() { println!(\"from feature\"); }\n\
>>>>>>> feature/story-42\n\
// end\n";
        let result = resolve_simple_conflicts(input).unwrap();
        assert!(!result.contains("<<<<<<<"), "no conflict markers in output");
        assert!(!result.contains(">>>>>>>"), "no conflict markers in output");
        assert!(!result.contains("======="), "no separator in output");
        assert!(
            result.contains("fn master_fn()"),
            "master (ours) side must be preserved"
        );
        assert!(
            result.contains("fn feature_fn()"),
            "feature (theirs) side must be preserved"
        );
        assert!(
            result.contains("// shared code"),
            "context before conflict preserved"
        );
        assert!(
            result.contains("// end"),
            "context after conflict preserved"
        );
        // ours (master) must appear before theirs (feature)
        assert!(
            result.find("master_fn").unwrap() < result.find("feature_fn").unwrap(),
            "master side must appear before feature side"
        );
    }

    #[test]
    fn resolve_simple_conflicts_real_markers_multiple_conflict_blocks() {
        // Two separate conflict blocks in the same file — as happens when two
        // feature branches both add imports AND test suites to the same file.
        let input = "// imports\n\
<<<<<<< HEAD\n\
import { A } from './a';\n\
=======\n\
import { B } from './b';\n\
>>>>>>> feature/story-43\n\
// implementation\n\
<<<<<<< HEAD\n\
export function masterImpl() {}\n\
=======\n\
export function featureImpl() {}\n\
>>>>>>> feature/story-43\n";
        let result = resolve_simple_conflicts(input).unwrap();
        assert!(!result.contains("<<<<<<<"), "no conflict markers in output");
        assert!(
            result.contains("import { A }"),
            "first block ours preserved"
        );
        assert!(
            result.contains("import { B }"),
            "first block theirs preserved"
        );
        assert!(result.contains("masterImpl"), "second block ours preserved");
        assert!(
            result.contains("featureImpl"),
            "second block theirs preserved"
        );
        assert!(
            result.contains("// imports"),
            "surrounding context preserved"
        );
        assert!(
            result.contains("// implementation"),
            "surrounding context preserved"
        );
    }

    #[test]
    fn resolve_simple_conflicts_real_markers_one_side_empty() {
        // Ours (master) has no content in the conflicted region; theirs (feature)
        // adds new content. Resolution: keep theirs.
        let input = "before\n\
<<<<<<< HEAD\n\
=======\n\
feature_addition\n\
>>>>>>> feature/story-44\n\
after\n";
        let result = resolve_simple_conflicts(input).unwrap();
        assert!(!result.contains("<<<<<<<"), "no conflict markers");
        assert!(
            result.contains("feature_addition"),
            "non-empty side preserved"
        );
        assert!(result.contains("before"), "context preserved");
        assert!(result.contains("after"), "context preserved");
    }
}
