use serde::{Deserialize, Serialize};
use std::process::Command;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitStatus {
    pub is_repo: bool,
    pub branch: Option<String>,
    pub upstream: Option<String>,
    pub ahead: u32,
    pub behind: u32,
    pub staged: Vec<GitFileChange>,
    pub unstaged: Vec<GitFileChange>,
    pub untracked: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitFileChange {
    pub path: String,
    pub status: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitBranch {
    pub name: String,
    pub is_current: bool,
    pub is_remote: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitLogEntry {
    pub hash: String,
    pub short_hash: String,
    pub author: String,
    pub date: String,
    pub message: String,
}

fn run_git_command(working_dir: &str, args: &[&str]) -> Result<String, String> {
    let output = Command::new("git")
        .args(args)
        .current_dir(working_dir)
        .output()
        .map_err(|e| format!("Failed to execute git: {}", e))?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    } else {
        Err(String::from_utf8_lossy(&output.stderr).to_string())
    }
}

#[tauri::command]
pub fn git_status(working_dir: String) -> Result<GitStatus, String> {
    // Check if it's a git repo
    let is_repo = run_git_command(&working_dir, &["rev-parse", "--git-dir"]).is_ok();

    if !is_repo {
        return Ok(GitStatus {
            is_repo: false,
            branch: None,
            upstream: None,
            ahead: 0,
            behind: 0,
            staged: vec![],
            unstaged: vec![],
            untracked: vec![],
        });
    }

    // Get current branch
    let branch = run_git_command(&working_dir, &["rev-parse", "--abbrev-ref", "HEAD"])
        .ok()
        .map(|s| s.trim().to_string());

    // Get upstream branch
    let upstream = run_git_command(
        &working_dir,
        &["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
    )
    .ok()
    .map(|s| s.trim().to_string());

    // Get ahead/behind counts
    let (ahead, behind) = if upstream.is_some() {
        let rev_list =
            run_git_command(&working_dir, &["rev-list", "--left-right", "--count", "@{u}...HEAD"])
                .unwrap_or_default();
        let parts: Vec<&str> = rev_list.trim().split('\t').collect();
        if parts.len() == 2 {
            (
                parts[1].parse().unwrap_or(0),
                parts[0].parse().unwrap_or(0),
            )
        } else {
            (0, 0)
        }
    } else {
        (0, 0)
    };

    // Get status with porcelain format
    let status_output =
        run_git_command(&working_dir, &["status", "--porcelain=v1"]).unwrap_or_default();

    let mut staged = vec![];
    let mut unstaged = vec![];
    let mut untracked = vec![];

    for line in status_output.lines() {
        if line.len() < 3 {
            continue;
        }

        let index_status = line.chars().next().unwrap_or(' ');
        let worktree_status = line.chars().nth(1).unwrap_or(' ');
        let path = line[3..].to_string();

        // Untracked files
        if index_status == '?' && worktree_status == '?' {
            untracked.push(path);
            continue;
        }

        // Staged changes (index status)
        if index_status != ' ' && index_status != '?' {
            staged.push(GitFileChange {
                path: path.clone(),
                status: match index_status {
                    'M' => "modified".to_string(),
                    'A' => "added".to_string(),
                    'D' => "deleted".to_string(),
                    'R' => "renamed".to_string(),
                    'C' => "copied".to_string(),
                    _ => "unknown".to_string(),
                },
            });
        }

        // Unstaged changes (worktree status)
        if worktree_status != ' ' && worktree_status != '?' {
            unstaged.push(GitFileChange {
                path,
                status: match worktree_status {
                    'M' => "modified".to_string(),
                    'D' => "deleted".to_string(),
                    _ => "unknown".to_string(),
                },
            });
        }
    }

    Ok(GitStatus {
        is_repo: true,
        branch,
        upstream,
        ahead,
        behind,
        staged,
        unstaged,
        untracked,
    })
}

#[tauri::command]
pub fn git_diff(working_dir: String, file_path: Option<String>, staged: bool) -> Result<String, String> {
    let mut args = vec!["diff"];

    if staged {
        args.push("--cached");
    }

    if let Some(ref path) = file_path {
        args.push("--");
        args.push(path);
    }

    run_git_command(&working_dir, &args)
}

#[tauri::command]
pub fn git_branches(working_dir: String) -> Result<Vec<GitBranch>, String> {
    let output = run_git_command(&working_dir, &["branch", "-a", "--format=%(refname:short)\t%(HEAD)"])?;

    let branches: Vec<GitBranch> = output
        .lines()
        .filter_map(|line| {
            let parts: Vec<&str> = line.split('\t').collect();
            if parts.is_empty() {
                return None;
            }

            let name = parts[0].to_string();
            let is_current = parts.get(1).map(|s| *s == "*").unwrap_or(false);
            let is_remote = name.starts_with("remotes/") || name.starts_with("origin/");

            Some(GitBranch {
                name,
                is_current,
                is_remote,
            })
        })
        .collect();

    Ok(branches)
}

#[tauri::command]
pub fn git_checkout(working_dir: String, branch: String) -> Result<String, String> {
    run_git_command(&working_dir, &["checkout", &branch])
}

#[tauri::command]
pub fn git_stage(working_dir: String, file_path: String) -> Result<String, String> {
    run_git_command(&working_dir, &["add", &file_path])
}

#[tauri::command]
pub fn git_unstage(working_dir: String, file_path: String) -> Result<String, String> {
    run_git_command(&working_dir, &["restore", "--staged", &file_path])
}

#[tauri::command]
pub fn git_stage_all(working_dir: String) -> Result<String, String> {
    run_git_command(&working_dir, &["add", "-A"])
}

#[tauri::command]
pub fn git_commit(working_dir: String, message: String) -> Result<String, String> {
    run_git_command(&working_dir, &["commit", "-m", &message])
}

#[tauri::command]
pub fn git_push(working_dir: String) -> Result<String, String> {
    run_git_command(&working_dir, &["push"])
}

#[tauri::command]
pub fn git_pull(working_dir: String) -> Result<String, String> {
    run_git_command(&working_dir, &["pull"])
}

#[tauri::command]
pub fn git_fetch(working_dir: String) -> Result<String, String> {
    run_git_command(&working_dir, &["fetch", "--all"])
}

#[tauri::command]
pub fn git_log(working_dir: String, limit: Option<u32>) -> Result<Vec<GitLogEntry>, String> {
    let limit_str = limit.unwrap_or(10).to_string();
    let output = run_git_command(
        &working_dir,
        &[
            "log",
            &format!("-{}", limit_str),
            "--pretty=format:%H\t%h\t%an\t%ar\t%s",
        ],
    )?;

    let entries: Vec<GitLogEntry> = output
        .lines()
        .filter_map(|line| {
            let parts: Vec<&str> = line.split('\t').collect();
            if parts.len() < 5 {
                return None;
            }

            Some(GitLogEntry {
                hash: parts[0].to_string(),
                short_hash: parts[1].to_string(),
                author: parts[2].to_string(),
                date: parts[3].to_string(),
                message: parts[4..].join("\t"),
            })
        })
        .collect();

    Ok(entries)
}

#[tauri::command]
pub fn git_discard(working_dir: String, file_path: String) -> Result<String, String> {
    run_git_command(&working_dir, &["checkout", "--", &file_path])
}

#[tauri::command]
pub fn git_create_branch(working_dir: String, branch_name: String) -> Result<String, String> {
    run_git_command(&working_dir, &["checkout", "-b", &branch_name])
}
