use crate::types::Result;
use crate::utils::log::{info, success, warning, error};
use std::process::Command;
use super::commands::BakRemotesArgs;
use chrono::Local;

fn get_remote_branches(remote: &str) -> Result<Vec<String>> {
    let output = Command::new("git")
        .args(["branch", "-r", "--list", &format!("{}/*", remote)])
        .output()?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(format!("Failed to list remote branches: {}", stderr).into());
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    let branches: Vec<String> = stdout
        .lines()
        .map(|l| l.trim().to_string())
        .filter(|l| !l.is_empty() && !l.contains("->"))
        .collect();

    Ok(branches)
}

fn strip_remote_prefix(branch: &str, remote: &str) -> String {
    let prefix = format!("{}/", remote);
    branch.strip_prefix(&prefix).unwrap_or(branch).to_string()
}

fn get_commit_sha(refspec: &str) -> Option<String> {
    let output = Command::new("git")
        .args(["rev-parse", refspec])
        .output()
        .ok()?;
    if output.status.success() {
        Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
    } else {
        None
    }
}

fn find_latest_backup(branch_name: &str) -> Option<String> {
    let pattern = format!("bak-*/{}", branch_name);
    let output = Command::new("git")
        .args(["branch", "--list", &pattern, "--sort=-creatordate"])
        .output()
        .ok()?;
    if !output.status.success() {
        return None;
    }
    let stdout = String::from_utf8_lossy(&output.stdout);
    stdout
        .lines()
        .map(|l| l.trim().trim_start_matches("* ").to_string())
        .find(|l| !l.is_empty())
}

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

    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();
    std::env::set_current_dir(&root_dir)?;
    info(&format!("Working in git root: {}", root_dir));

    let remote = args.remote.as_deref().unwrap_or("origin");

    info(&format!("Fetching latest from remote '{}'...", remote));
    let fetch_status = Command::new("git")
        .args(["fetch", remote, "--prune"])
        .status()?;

    if !fetch_status.success() {
        warning(&format!("Failed to fetch from '{}', using existing local data", remote));
    }

    let remote_branches = get_remote_branches(remote)?;

    if remote_branches.is_empty() {
        info(&format!("No remote branches found for '{}'", remote));
        return Ok(());
    }

    let date_str = args.date.clone().unwrap_or_else(|| {
        Local::now().format("%Y-%m-%d").to_string()
    });

    let prefix = format!("bak-{}", date_str);

    info(&format!("Found {} remote branches to backup", remote_branches.len()));
    info(&format!("Backup prefix: {}", prefix));

    let mut backed_up = 0u32;
    let mut skipped = 0u32;
    let mut unchanged = 0u32;
    let mut failed = 0u32;

    for remote_branch in &remote_branches {
        let branch_name = strip_remote_prefix(remote_branch, remote);
        let bak_branch = format!("{}/{}", prefix, branch_name);

        if args.only_updated {
            if let Some(latest_bak) = find_latest_backup(&branch_name) {
                let remote_sha = get_commit_sha(remote_branch);
                let bak_sha = get_commit_sha(&latest_bak);
                if remote_sha.is_some() && remote_sha == bak_sha {
                    info(&format!("  [unchanged] {} (same as {})", remote_branch, latest_bak));
                    unchanged += 1;
                    continue;
                }
            }
        }

        let check = Command::new("git")
            .args(["rev-parse", "--verify", &format!("refs/heads/{}", bak_branch)])
            .output()?;

        if check.status.success() {
            if args.force {
                let status = Command::new("git")
                    .args(["branch", "-f", &bak_branch, remote_branch])
                    .status()?;

                if status.success() {
                    info(&format!("  [overwrite] {} -> {}", remote_branch, bak_branch));
                    backed_up += 1;
                } else {
                    error(&format!("  [failed] {} -> {}", remote_branch, bak_branch));
                    failed += 1;
                }
            } else {
                info(&format!("  [skip] {} already exists", bak_branch));
                skipped += 1;
            }
            continue;
        }

        let status = Command::new("git")
            .args(["branch", &bak_branch, remote_branch])
            .status()?;

        if status.success() {
            info(&format!("  [ok] {} -> {}", remote_branch, bak_branch));
            backed_up += 1;
        } else {
            error(&format!("  [failed] {} -> {}", remote_branch, bak_branch));
            failed += 1;
        }
    }

    println!();
    success(&format!("Backup complete!"));
    println!("  Backed up: {}", backed_up);
    if skipped > 0 {
        println!("  Skipped:   {} (already exist, use --force to overwrite)", skipped);
    }
    if unchanged > 0 {
        println!("  Unchanged: {} (no new commits since last backup)", unchanged);
    }
    if failed > 0 {
        println!("  Failed:    {}", failed);
    }
    println!("  Prefix:    {}", prefix);
    println!();
    info("These branches are local only — nothing was pushed.");
    info(&format!("To list backup branches: git branch --list '{}/*'", prefix));
    info(&format!("To delete all backups:   git branch -D $(git branch --list '{}/*')", prefix));

    Ok(())
}
