use crate::types::Result;
use crate::utils::log::{info, success, warning, error};
use std::process::Command;
use super::commands::RewriteIgnoreArgs;
use std::io::Write;
use tempfile::NamedTempFile;

pub async fn execute(args: RewriteIgnoreArgs) -> Result<()> {
    // 1. Check if git is available
    if Command::new("git").arg("--version").output().is_err() {
        return Err("Git is not installed or not in PATH".into());
    }

    // 2. Find git root
    let output = Command::new("git")
        .args(&["rev-parse", "--show-toplevel"])
        .output()?;
        
    if !output.status.success() {
        return Err("Not a git repository".into());
    }
    
    let root_dir = String::from_utf8_lossy(&output.stdout).trim().to_string();
    let root_path = std::path::Path::new(&root_dir);
    
    // Change to root dir
    std::env::set_current_dir(root_path)?;
    info(&format!("Working in git root: {}", root_dir));

    // 3. Get list of files that are ignored but tracked
    info("Checking for ignored files in the repository...");
    
    // Use -z for null termination to handle special characters safely? 
    // But for display we need text.
    // Let's do standard first for display.
    let output = Command::new("git")
        .args(&["ls-files", "-ci", "--exclude-standard"])
        .output()?;
        
    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(format!("Failed to list ignored files: {}", stderr).into());
    }
    
    let stdout = String::from_utf8_lossy(&output.stdout);
    let files: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect();
    
    if files.is_empty() {
        info("No ignored files found in the repository index.");
        return Ok(());
    }
    
    info(&format!("Found {} files that are ignored but tracked:", files.len()));
    for file in &files {
        info(&format!("  - {}", file));
    }
    
    // 4. Confirmation
    if !args.yes {
        warning("This operation will rewrite the git history using 'git filter-branch'.");
        warning("This process is destructive and cannot be easily undone.");
        warning("It will remove these files from ALL commits in the history.");
        print!("Do you want to proceed? [y/N] ");
        std::io::stdout().flush()?;
        
        // We need to use blocking stdin here because we are in an async function but std::io::stdin is blocking.
        // It's fine for CLI tools.
        let mut input = String::new();
        std::io::stdin().read_line(&mut input)?;
        
        if !input.trim().eq_ignore_ascii_case("y") {
            info("Operation cancelled.");
            return Ok(());
        }
    }
    
    // 5. Backup files to avoid data loss during filter-branch checkout
    info("Backing up ignored files...");
    let backup_dir = tempfile::TempDir::new()?;
    for file_path_str in &files {
        let src_path = std::path::Path::new(file_path_str);
        if src_path.exists() {
            let dest_path = backup_dir.path().join(src_path);
            if let Some(parent) = dest_path.parent() {
                std::fs::create_dir_all(parent)?;
            }
            std::fs::copy(src_path, dest_path)?;
        }
    }

    // 6. Prepare temp file with list of files to remove (null terminated for xargs -0)
    let mut temp_file = NamedTempFile::new()?;
    for file in &files {
        write!(temp_file, "{}\0", file)?;
    }
    // Flush to ensure content is written
    temp_file.flush()?;
    
    // Get absolute path of temp file
    let temp_path = temp_file.path().to_path_buf();
    let temp_path_str = temp_path.to_str().ok_or("Invalid temp path")?;
    
    info("Rewriting history...");
    
    // 6. Execute git filter-branch
    // We use cat ... | xargs -0 ... to handle spaces and special chars safely
    let filter_cmd = format!("cat \"{}\" | xargs -0 git rm --cached --ignore-unmatch", temp_path_str);
    
    let envs = [
        ("FILTER_BRANCH_SQUELCH_WARNING", "1"),
    ];
    
    // We use spawn/wait to stream output if possible, or just status to inherit stdio?
    // Command::status inherits stdio by default.
    let status = Command::new("git")
        .args(&[
            "filter-branch",
            "--force",
            "--index-filter",
            &filter_cmd,
            "--prune-empty",
            "--tag-name-filter",
            "cat",
            "--",
            "--all"
        ])
        .envs(envs)
        .status()?;
        
    if status.success() {
        success("Successfully rewrote git history.");
        info("Original refs are backed up in refs/original/.");
        info("To clean up backups, run:");
        info("  git for-each-ref --format='%(refname)' refs/original/ | xargs -n 1 git update-ref -d");
        
        // Restore files
        info("Restoring ignored files to working directory...");
        for file_path_str in &files {
            let dest_path = std::path::Path::new(file_path_str);
            if !dest_path.exists() {
                let src_path = backup_dir.path().join(dest_path);
                if src_path.exists() {
                    if let Some(parent) = dest_path.parent() {
                        std::fs::create_dir_all(parent)?;
                    }
                    std::fs::copy(src_path, dest_path)?;
                    info(&format!("Restored {}", file_path_str));
                }
            }
        }
    } else {
        error("Failed to rewrite history.");
        return Err("git filter-branch failed".into());
    }

    Ok(())
}
