use crate::db;
use git2::{Repository, RemoteCallbacks, FetchOptions, build::RepoBuilder};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct GitStatus {
    pub branch: String,
    pub ahead: usize,
    pub behind: usize,
    pub has_changes: bool,
    pub last_commit_msg: Option<String>,
    pub last_commit_time: Option<String>,
}

/// 按 URL 前缀匹配已注册的 Gitea 实例，返回其 access_token
fn gitea_token_for_url(url: &str) -> Option<String> {
    let conn = db::pool().get().ok()?;
    let mut stmt = conn.prepare("SELECT url, access_token FROM gitea_instances").ok()?;
    let rows = stmt
        .query_map([], |r| Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?)))
        .ok()?;
    for row in rows.flatten() {
        let (base, token) = row;
        let base = base.trim_end_matches('/');
        if !base.is_empty() && url.starts_with(base) {
            return Some(token);
        }
    }
    None
}

/// 凭据链：ssh agent → git credential helper（与 CLI 同一凭据源）→ Gitea 实例 token → default
/// 不再依赖 GitHub 登录，任何 remote（Gitea / GitHub / 其他）走系统已有凭据
fn make_callbacks() -> RemoteCallbacks<'static> {
    let mut callbacks = RemoteCallbacks::new();
    callbacks.credentials(|url, username_from_url, allowed| {
        if allowed.contains(git2::CredentialType::SSH_KEY) {
            if let Ok(cred) = git2::Cred::ssh_key_from_agent(username_from_url.unwrap_or("git")) {
                return Ok(cred);
            }
        }
        if allowed.contains(git2::CredentialType::USER_PASS_PLAINTEXT) {
            if let Ok(config) = git2::Config::open_default() {
                if let Ok(cred) = git2::Cred::credential_helper(&config, url, username_from_url) {
                    return Ok(cred);
                }
            }
            if let Some(token) = gitea_token_for_url(url) {
                if let Ok(cred) = git2::Cred::userpass_plaintext(&token, "") {
                    return Ok(cred);
                }
            }
        }
        git2::Cred::default()
    });
    callbacks
}

fn make_fetch_options() -> FetchOptions<'static> {
    let mut fetch_opts = FetchOptions::new();
    fetch_opts.remote_callbacks(make_callbacks());
    fetch_opts
}

/// clone 仓库到指定本地路径
#[tauri::command]
pub fn git_clone(repo_url: String, local_path: String, upstream_url: Option<String>) -> Result<(), String> {
    let fetch_opts = make_fetch_options();

    let repo = RepoBuilder::new()
        .fetch_options(fetch_opts)
        .clone(&repo_url, std::path::Path::new(&local_path))
        .map_err(|e| format!("clone 失败: {}", e))?;

    if let Some(upstream) = upstream_url {
        if !upstream.trim().is_empty() {
            repo.remote("upstream", upstream.trim())
                .map_err(|e| format!("设置 upstream remote 失败: {}", e))?;
        }
    }

    Ok(())
}

/// 获取仓库 git 状态（分支、领先/落后、是否有未提交变更、最近 commit）
#[tauri::command]
pub fn git_status(local_path: String) -> Result<GitStatus, String> {
    let repo = Repository::open(&local_path)
        .map_err(|e| format!("无法打开仓库: {}", e))?;

    // 当前分支名
    let head = repo.head().map_err(|e| e.to_string())?;
    let branch = head
        .shorthand()
        .unwrap_or("HEAD detached")
        .to_string();

    // 领先/落后远端
    let (ahead, behind) = calc_ahead_behind(&repo, &branch);

    // 是否有未提交变更
    let statuses = repo.statuses(None).map_err(|e| e.to_string())?;
    let has_changes = statuses.iter().any(|s| {
        s.status() != git2::Status::CURRENT && s.status() != git2::Status::IGNORED
    });

    // 最近一次 commit
    let (last_commit_msg, last_commit_time) = match head.peel_to_commit() {
        Ok(commit) => {
            let msg = commit.summary().map(|s| s.to_string());
            let time = {
                let t = commit.time().seconds();
                let dt = chrono::DateTime::from_timestamp(t, 0)
                    .map(|dt| dt.format("%Y-%m-%d %H:%M").to_string());
                dt
            };
            (msg, time)
        }
        Err(_) => (None, None),
    };

    Ok(GitStatus {
        branch,
        ahead,
        behind,
        has_changes,
        last_commit_msg,
        last_commit_time,
    })
}

/// fetch 远端最新信息（不修改本地工作区）
#[tauri::command]
pub fn git_fetch(local_path: String) -> Result<(), String> {
    let repo = Repository::open(&local_path)
        .map_err(|e| format!("无法打开仓库: {}", e))?;

    let mut fetch_opts = make_fetch_options();
    repo.find_remote("origin")
        .map_err(|e| e.to_string())?
        .fetch(&[] as &[&str], Some(&mut fetch_opts), None)
        .map_err(|e| format!("fetch 失败: {}", e))?;

    Ok(())
}

/// pull（fetch + merge fast-forward）
#[tauri::command]
pub fn git_pull(local_path: String) -> Result<String, String> {
    let repo = Repository::open(&local_path)
        .map_err(|e| format!("无法打开仓库: {}", e))?;

    // fetch
    let mut fetch_opts = make_fetch_options();
    let mut remote = repo.find_remote("origin").map_err(|e| e.to_string())?;
    remote
        .fetch(&[] as &[&str], Some(&mut fetch_opts), None)
        .map_err(|e| format!("fetch 失败: {}", e))?;

    // 当前分支
    let head = repo.head().map_err(|e| e.to_string())?;
    let branch_name = head
        .shorthand()
        .ok_or("无法获取分支名")?
        .to_string();

    // 找到 FETCH_HEAD
    let fetch_head = repo
        .find_reference("FETCH_HEAD")
        .map_err(|_| "没有可以合并的远端提交".to_string())?;
    let fetch_commit = repo
        .reference_to_annotated_commit(&fetch_head)
        .map_err(|e| e.to_string())?;

    // 分析合并情况
    let (analysis, _) = repo
        .merge_analysis(&[&fetch_commit])
        .map_err(|e| e.to_string())?;

    if analysis.is_up_to_date() {
        return Ok(format!("已是最新（分支: {}）", branch_name));
    }

    if analysis.is_fast_forward() {
        let refname = format!("refs/heads/{}", branch_name);
        let mut reference = repo.find_reference(&refname).map_err(|e| e.to_string())?;
        reference
            .set_target(fetch_commit.id(), "fast-forward pull")
            .map_err(|e| e.to_string())?;
        repo.set_head(&refname).map_err(|e| e.to_string())?;
        repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))
            .map_err(|e| e.to_string())?;
        return Ok(format!("Pull 成功（fast-forward，分支: {}）", branch_name));
    }

    Err("存在冲突，需要手动合并".to_string())
}

/// 获取本地仓库当前 HEAD 的 commit hash（用于跨空间比对）
#[tauri::command]
pub fn git_head_commit(local_path: String) -> Result<Option<String>, String> {
    let repo = match Repository::open(&local_path) {
        Ok(r) => r,
        Err(_) => return Ok(None),
    };
    let result = match repo.head().and_then(|h| h.peel_to_commit()) {
        Ok(commit) => Ok(Some(commit.id().to_string())),
        Err(_) => Ok(None),
    };
    result
}

/// 检查 upstream 远端落后情况（公司仓库是否有新提交）
#[tauri::command]
pub fn git_upstream_behind(local_path: String, default_branch: String) -> Result<usize, String> {
    let repo = Repository::open(&local_path)
        .map_err(|e| format!("无法打开仓库: {}", e))?;

    // 检查是否有 upstream remote
    if repo.find_remote("upstream").is_err() {
        return Ok(0);
    }

    // fetch upstream
    let mut fetch_opts = make_fetch_options();

    if let Ok(mut remote) = repo.find_remote("upstream") {
        let _ = remote.fetch(&[&default_branch], Some(&mut fetch_opts), None);
    }

    // 比较本地 HEAD 与 upstream/{default_branch}
    let head_oid = match repo.head().ok().and_then(|h| h.target()) {
        Some(o) => o,
        None => return Ok(0),
    };
    let upstream_ref = format!("refs/remotes/upstream/{}", default_branch);
    let upstream_oid = match repo.find_reference(&upstream_ref).ok().and_then(|r| r.target()) {
        Some(o) => o,
        None => return Ok(0),
    };

    let (_, behind) = repo
        .graph_ahead_behind(head_oid, upstream_oid)
        .unwrap_or((0, 0));
    Ok(behind)
}

/// push 当前分支到 origin
#[tauri::command]
pub fn git_push(local_path: String) -> Result<String, String> {
    let repo = Repository::open(&local_path)
        .map_err(|e| format!("无法打开仓库: {}", e))?;

    let head = repo.head().map_err(|e| e.to_string())?;
    let branch = head
        .shorthand()
        .ok_or("无法获取分支名")?
        .to_string();

    let refspec = format!("refs/heads/{}:refs/heads/{}", branch, branch);

    let mut push_opts = git2::PushOptions::new();
    push_opts.remote_callbacks(make_callbacks());

    repo.find_remote("origin")
        .map_err(|e| e.to_string())?
        .push(&[refspec.as_str()], Some(&mut push_opts))
        .map_err(|e| format!("push 失败: {}", e))?;

    Ok(format!("Push 成功（分支: {}）", branch))
}

// ── 内部辅助 ────────────────────────────────────────────────

fn calc_ahead_behind(repo: &Repository, branch: &str) -> (usize, usize) {
    let local_ref = match repo.find_reference(&format!("refs/heads/{}", branch)) {
        Ok(r) => r,
        Err(_) => return (0, 0),
    };
    let remote_ref = match repo
        .find_reference(&format!("refs/remotes/origin/{}", branch))
    {
        Ok(r) => r,
        Err(_) => return (0, 0),
    };
    let local_oid = match local_ref.target() {
        Some(o) => o,
        None => return (0, 0),
    };
    let remote_oid = match remote_ref.target() {
        Some(o) => o,
        None => return (0, 0),
    };
    repo.graph_ahead_behind(local_oid, remote_oid)
        .unwrap_or((0, 0))
}

/// 创建并切换到新分支（从当前 HEAD 创建）
#[tauri::command]
pub fn git_create_branch(local_path: String, branch_name: String) -> Result<(), String> {
    let repo = Repository::open(&local_path)
        .map_err(|e| format!("无法打开仓库: {}", e))?;
    let head = repo.head().map_err(|e| e.to_string())?;
    let commit = head.peel_to_commit().map_err(|e| e.to_string())?;
    let branch = repo.branch(&branch_name, &commit, false)
        .map_err(|e| format!("创建分支失败: {}", e))?;
    let refname = branch.get().name().ok_or("无效分支名")?.to_string();
    repo.set_head(&refname).map_err(|e| format!("切换分支失败: {}", e))?;
    repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))
        .map_err(|e| format!("checkout 失败: {}", e))?;
    Ok(())
}

/// 列出本地所有分支名
#[tauri::command]
pub fn git_list_branches(local_path: String) -> Result<Vec<String>, String> {
    let repo = Repository::open(&local_path)
        .map_err(|e| format!("无法打开仓库: {}", e))?;
    let branches = repo.branches(Some(git2::BranchType::Local))
        .map_err(|e| e.to_string())?;
    let mut names = Vec::new();
    for b in branches {
        let (branch, _) = b.map_err(|e| e.to_string())?;
        if let Some(name) = branch.name().map_err(|e| e.to_string())? {
            names.push(name.to_string());
        }
    }
    Ok(names)
}

/// 切换到已有的本地分支
#[tauri::command]
pub fn git_checkout_branch(local_path: String, branch_name: String) -> Result<(), String> {
    let repo = Repository::open(&local_path)
        .map_err(|e| format!("无法打开仓库: {}", e))?;
    let refname = format!("refs/heads/{}", branch_name);
    repo.set_head(&refname).map_err(|e| format!("切换分支失败: {}", e))?;
    repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))
        .map_err(|e| format!("checkout 失败: {}", e))?;
    Ok(())
}
