use git2::Repository;

use crate::error::MirrorError;

/// Returns true if a commit with the given jti already exists in the HEAD ancestor chain.
/// Searches commit messages for the marker `jti=<value>` added by commit_writer.
///
/// This is the idempotency guard required by ADR-038 constraint audit-mirror-idempotent-on-jti.
pub fn already_committed(repo: &Repository, jti: &str) -> Result<bool, MirrorError> {
    let head = match repo.head() {
        Ok(r) => r,
        Err(e) if e.code() == git2::ErrorCode::UnbornBranch => return Ok(false),
        Err(e) => return Err(MirrorError::Git(e.to_string())),
    };

    let head_commit = head
        .peel_to_commit()
        .map_err(|e| MirrorError::Git(e.to_string()))?;

    let marker = format!("jti={jti}");

    let mut revwalk = repo
        .revwalk()
        .map_err(|e| MirrorError::Git(e.to_string()))?;
    revwalk
        .push(head_commit.id())
        .map_err(|e| MirrorError::Git(e.to_string()))?;
    revwalk.set_sorting(git2::Sort::TOPOLOGICAL | git2::Sort::TIME)
        .map_err(|e| MirrorError::Git(e.to_string()))?;

    for oid in revwalk {
        let oid = oid.map_err(|e| MirrorError::Git(e.to_string()))?;
        let commit = repo
            .find_commit(oid)
            .map_err(|e| MirrorError::Git(e.to_string()))?;
        if commit.message().unwrap_or("").contains(&marker) {
            return Ok(true);
        }
    }

    Ok(false)
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    fn init_bare_repo_with_commit(message: &str) -> (TempDir, Repository) {
        let dir = TempDir::new().unwrap();
        let repo = Repository::init(dir.path()).unwrap();

        {
            let mut config = repo.config().unwrap();
            config.set_str("user.name", "test").unwrap();
            config.set_str("user.email", "test@test.com").unwrap();
        }

        let sig = git2::Signature::now("test", "test@test.com").unwrap();
        {
            let tree_id = {
                let mut index = repo.index().unwrap();
                index.write_tree().unwrap()
            };
            let tree = repo.find_tree(tree_id).unwrap();
            repo.commit(Some("HEAD"), &sig, &sig, message, &tree, &[])
                .unwrap();
            // tree drops here so repo can be moved below
        }

        (dir, repo)
    }

    #[test]
    fn detects_existing_jti_in_head_commit() {
        let (_dir, repo) = init_bare_repo_with_commit("audit: jti=abc-123\n\npayload");
        assert!(already_committed(&repo, "abc-123").unwrap());
    }

    #[test]
    fn returns_false_for_unknown_jti() {
        let (_dir, repo) = init_bare_repo_with_commit("audit: jti=abc-123\n\npayload");
        assert!(!already_committed(&repo, "unknown-jti").unwrap());
    }

    #[test]
    fn returns_false_for_empty_repo() {
        let dir = TempDir::new().unwrap();
        let repo = Repository::init(dir.path()).unwrap();
        assert!(!already_committed(&repo, "any-jti").unwrap());
    }
}
