use git2::{Repository, Signature};

use crate::error::MirrorError;

/// Writes a single commit into the HEAD of `repo` embedding `jti` in the message.
///
/// The commit message format is:
///   audit: jti=<jti>
///
///   <json_payload>
///
/// This is the canonical format that `jti_check::already_committed` searches for.
/// An empty tree is used because the audit repo contains only commit-message blobs;
/// no working-tree files are modified.
pub fn write_audit_commit(
    repo: &Repository,
    jti: &str,
    json_payload: &str,
    author_name: &str,
    author_email: &str,
) -> Result<git2::Oid, MirrorError> {
    let sig = Signature::now(author_name, author_email)
        .map_err(|e| MirrorError::Git(e.to_string()))?;

    let tree_id = {
        let mut index = repo.index().map_err(|e| MirrorError::Git(e.to_string()))?;
        // Write tree from current index (may be empty — no working-tree blobs needed)
        index.write_tree().map_err(|e| MirrorError::Git(e.to_string()))?
    };

    let tree = repo
        .find_tree(tree_id)
        .map_err(|e| MirrorError::Git(e.to_string()))?;

    let message = format!("audit: jti={jti}\n\n{json_payload}");

    // Parent is HEAD if it exists; first commit for an empty (unborn) branch has no parent.
    let parent_commit;
    let parents: Vec<&git2::Commit<'_>>;
    match repo.head() {
        Ok(head_ref) => {
            parent_commit = head_ref
                .peel_to_commit()
                .map_err(|e| MirrorError::Git(e.to_string()))?;
            parents = vec![&parent_commit];
        }
        Err(e) if e.code() == git2::ErrorCode::UnbornBranch => {
            parents = vec![];
        }
        Err(e) => return Err(MirrorError::Git(e.to_string())),
    }

    let oid = repo
        .commit(Some("HEAD"), &sig, &sig, &message, &tree, &parents)
        .map_err(|e| MirrorError::Git(e.to_string()))?;

    Ok(oid)
}

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

    fn init_repo() -> (TempDir, Repository) {
        let dir = TempDir::new().unwrap();
        let repo = Repository::init(dir.path()).unwrap();
        let mut cfg = repo.config().unwrap();
        cfg.set_str("user.name", "test").unwrap();
        cfg.set_str("user.email", "test@test.com").unwrap();
        (dir, repo)
    }

    #[test]
    fn writes_commit_to_empty_repo() {
        let (_dir, repo) = init_repo();
        let oid = write_audit_commit(&repo, "jti-001", r#"{"event":"test"}"#, "ci", "ci@ci.local").unwrap();
        let commit = repo.find_commit(oid).unwrap();
        assert!(commit.message().unwrap().contains("jti=jti-001"));
        assert!(commit.message().unwrap().contains(r#"{"event":"test"}"#));
    }

    #[test]
    fn writes_second_commit_with_correct_parent() {
        let (_dir, repo) = init_repo();
        let first = write_audit_commit(&repo, "jti-001", "{}", "ci", "ci@ci.local").unwrap();
        let second = write_audit_commit(&repo, "jti-002", "{}", "ci", "ci@ci.local").unwrap();
        let commit = repo.find_commit(second).unwrap();
        assert_eq!(commit.parent_id(0).unwrap(), first);
    }

    #[test]
    fn message_contains_jti_marker() {
        let (_dir, repo) = init_repo();
        let oid = write_audit_commit(&repo, "abc-xyz", r#"{"op":"deploy"}"#, "keeper", "keeper@ops").unwrap();
        let msg = repo.find_commit(oid).unwrap().message().unwrap().to_string();
        assert!(msg.starts_with("audit: jti=abc-xyz\n\n"));
    }
}
