use std::path::PathBuf;

use crate::errors::GitResult;

#[derive(Clone)]
pub struct GitBare {
    pub bare_dir: PathBuf,
}

#[derive(Debug, Clone)]
pub struct LastCommitInfo {
    pub message: String,
    pub time: String,
    pub author_name: String,
    pub author_email: String,
}

impl GitBare {
    pub fn gix_repo(&self) -> crate::errors::GitResult<gix::Repository> {
        gix::open(&self.bare_dir).map_err(|e| {
            crate::errors::GitError::Internal(format!(
                "failed to open gix repository: {e}"
            ))
        })
    }
    pub fn set_default_branch(&self, branch_name: &str) -> GitResult<()> {
        let output = self.git_command_trusted(vec![
            "symbolic-ref".to_string(),
            "HEAD".to_string(),
            format!("refs/heads/{}", branch_name),
        ])?;
        if !output.success {
            return Err(crate::errors::GitError::CommandFailed {
                status_code: output.status_code,
                stderr: output.stderr_lossy(),
            });
        }
        Ok(())
    }
    pub fn last_commits_for_paths(
        &self,
        paths: &[String],
    ) -> GitResult<Vec<Option<LastCommitInfo>>> {
        use gix::traverse::commit::simple::CommitTimeOrder;

        if paths.is_empty() {
            return Ok(vec![]);
        }

        let repo = self.gix_repo()?;
        let head_id = repo.head_id().map_err(|e| {
            crate::errors::GitError::Internal(format!("no HEAD: {e}"))
        })?;

        let walk = repo
            .rev_walk(vec![head_id.detach()])
            .sorting(gix::revision::walk::Sorting::ByCommitTime(
                CommitTimeOrder::NewestFirst,
            ))
            .first_parent_only()
            .all()
            .map_err(|e| {
                crate::errors::GitError::Internal(format!("rev_walk: {e}"))
            })?;

        let mut result: Vec<Option<LastCommitInfo>> = vec![None; paths.len()];
        let mut remaining: std::collections::HashSet<usize> =
            (0..paths.len()).collect();

        for walk_item in walk {
            if remaining.is_empty() {
                break;
            }
            let info = match walk_item {
                Ok(i) => i,
                Err(_) => continue,
            };
            let oid = info.id().detach();
            let commit = match repo.find_commit(oid) {
                Ok(c) => c,
                Err(_) => continue,
            };
            let decoded = match commit.decode() {
                Ok(d) => d,
                Err(_) => continue,
            };

            let current_tree = match repo.find_tree(decoded.tree()) {
                Ok(t) => t,
                Err(_) => continue,
            };

            let parent_tree = decoded.parents().next().and_then(|pid| {
                let hex = pid.to_hex().to_string();
                let gid = gix::hash::ObjectId::from_hex(hex.as_bytes()).ok()?;
                let pc = repo.find_commit(gid).ok()?;
                let p_decoded = pc.decode().ok()?;
                repo.find_tree(p_decoded.tree()).ok()
            });

            let mut diff_opts = gix::diff::Options::default();
            diff_opts.track_path();
            let changes = match repo.diff_tree_to_tree(
                parent_tree.as_ref(),
                Some(&current_tree),
                Some(diff_opts),
            ) {
                Ok(d) => d,
                Err(_) => continue,
            };

            let changed: std::collections::HashSet<String> =
                changes.iter().map(|c| c.location().to_string()).collect();

            let is_root = parent_tree.is_none();
            let author_sig = match decoded.author() {
                Ok(s) => s,
                Err(_) => continue,
            };
            let time = author_sig.time().unwrap_or(gix::date::Time {
                seconds: 0,
                offset: 0,
            });
            let msg = commit
                .message_raw()
                .map(|r| r.to_string().trim_end_matches('\n').to_string())
                .unwrap_or_default();
            let summary = msg.lines().next().unwrap_or("").to_string();

            let matched: Vec<usize> = remaining
                .iter()
                .copied()
                .filter(|&idx| is_root || changed.contains(&paths[idx]))
                .collect();

            for idx in matched {
                result[idx] = Some(LastCommitInfo {
                    message: summary.clone(),
                    time: format!("{}", time.seconds),
                    author_name: author_sig.name.to_string(),
                    author_email: author_sig.email.to_string(),
                });
                remaining.remove(&idx);
            }
        }

        Ok(result)
    }
}
