use serde::{Deserialize, Serialize};

use crate::{
    bare::GitBare,
    cmd::{command::GitCommandParams, oid::ObjectId, tagger::GitTagger},
    errors::{GitError, GitResult},
};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TagInitParams {
    pub name: String,
    pub target: ObjectId,
    pub message: Option<String>,
    pub tagger: Option<GitTagger>,
    pub force: bool,
}

impl GitBare {
    pub fn tag_init(&self, params: TagInitParams) -> GitResult<ObjectId> {
        if let Some(message) = &params.message {
            let mut cmd_args = vec!["tag".to_string(), "-a".to_string()];
            if params.force {
                cmd_args.push("--force".to_string());
            }
            cmd_args.push(params.name.clone());
            cmd_args.push(params.target.as_str().to_string());
            cmd_args.push("-F".to_string());
            cmd_args.push("-".to_string());

            let mut cmd_params = GitCommandParams::new(cmd_args)
                .trusted()
                .with_stdin(message.as_bytes().to_vec());

            if let Some(tagger) = &params.tagger {
                cmd_params = cmd_params
                    .with_env(
                        "GIT_COMMITTER_NAME".to_string(),
                        tagger.name.clone(),
                    )
                    .with_env(
                        "GIT_COMMITTER_EMAIL".to_string(),
                        tagger.email.clone(),
                    );
            }

            let output = self.git_command_with(cmd_params)?;
            if !output.success {
                return Err(GitError::CommandFailed {
                    status_code: output.status_code,
                    stderr: output.stderr_lossy(),
                });
            }
        } else {
            let mut cmd_args = vec!["tag".to_string()];
            if params.force {
                cmd_args.push("--force".to_string());
            }
            cmd_args.push(params.name.clone());
            cmd_args.push(params.target.as_str().to_string());

            let output = self.git_command_trusted(cmd_args)?;
            if !output.success {
                return Err(GitError::CommandFailed {
                    status_code: output.status_code,
                    stderr: output.stderr_lossy(),
                });
            }
        }
        let repo = self.gix_repo()?;
        let refname = format!("refs/tags/{}", params.name);
        let ref_name_gix = gix::refs::PartialName::try_from(refname.as_str())
            .map_err(|e| GitError::Gix(e.to_string()))?;
        let tag_ref = repo
            .find_reference(&ref_name_gix)
            .map_err(|e| GitError::Gix(e.to_string()))?;
        let target = tag_ref.target();
        let target_id = target.try_id().ok_or_else(|| {
            GitError::Gix(format!("tag ref '{}' has no direct target", refname))
        })?;

        Ok(ObjectId::new(target_id.to_hex().to_string()))
    }
}
