use serde::{Deserialize, Serialize};

use crate::{
    bare::GitBare,
    cmd::{
        commit::CommitSignature, oid::ObjectId, parse::format_git_timestamp,
    },
    errors::{GitError, GitResult},
};

#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct CommitCherryPickParams {
    pub cherrypick_oid: ObjectId,
    pub author: CommitSignature,
    pub committer: CommitSignature,
    pub message: Option<String>,
    pub mainline: u32,
    pub update_ref: Option<String>,
}

#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct CommitCherryPickSequence {
    pub cherrypick_oids: Vec<ObjectId>,
    pub author: CommitSignature,
    pub committer: CommitSignature,
    pub update_ref: Option<String>,
}

impl GitBare {
    pub fn commit_pick(
        &self,
        params: CommitCherryPickParams,
    ) -> GitResult<ObjectId> {
        let cherry_info = self.commit_info(params.cherrypick_oid.clone())?;
        let their_tree = cherry_info.tree_id;
        let repo = self.gix_repo()?;
        let head_id =
            repo.head_id().map_err(|e| GitError::Gix(e.to_string()))?;
        let head_oid = ObjectId::new(head_id.detach().to_hex().to_string());
        let head_info = self.commit_info(head_oid.clone())?;
        let our_tree = head_info.tree_id;
        let base_result =
            self.merge_base(head_oid.clone(), params.cherrypick_oid.clone());
        let base_tree = match base_result {
            Ok(base_oid) => {
                let base_info = self.commit_info(base_oid)?;
                base_info.tree_id.as_str().to_string()
            }
            Err(_) => {
                let empty_tree = "4b825dc642cb6eb9a060e54bf899d15363c725d7";
                let empty_tree_gix_id: gix::hash::ObjectId =
                    gix::hash::ObjectId::from_hex(empty_tree.as_bytes())
                        .map_err(|e| GitError::Gix(e.to_string()))?;
                if repo.has_object(empty_tree_gix_id) {
                    empty_tree.to_string()
                } else {
                    let mktree_output =
                        self.git_command_trusted_stdout(vec![
                            "mktree".to_string(),
                        ])?;
                    mktree_output.trim().to_string()
                }
            }
        };
        let merge_tree_args = vec![
            "merge-tree".to_string(),
            "--write-tree".to_string(),
            "--merge-base".to_string(),
            base_tree.clone(),
            our_tree.as_str().to_string(),
            their_tree.as_str().to_string(),
        ];

        let merge_output = self.git_command_trusted(merge_tree_args);

        let merged_tree_id = match merge_output {
            Ok(output) => {
                if output.success {
                    ObjectId::new(output.stdout_lossy().trim())
                } else {
                    return Err(GitError::CommandFailed {
                        status_code: output.status_code,
                        stderr: output.stderr_lossy(),
                    });
                }
            }
            Err(err) => return Err(err),
        };
        let message = params
            .message
            .clone()
            .unwrap_or(cherry_info.message.clone());
        let mut commit_args = vec![
            "commit-tree".to_string(),
            merged_tree_id.as_str().to_string(),
            "-p".to_string(),
            head_oid.as_str().to_string(),
        ];
        if cherry_info.parent_ids.len() > 1 {
            commit_args.push("-m".to_string());
            commit_args.push(format!(
                "cherry picked from commit {}",
                params.cherrypick_oid.as_str()
            ));
            if params.mainline > 0 {
                commit_args.push(format!("(mainline {})", params.mainline));
            }
        }
        let cmd_params =
            crate::cmd::command::GitCommandParams::new(commit_args)
                .trusted()
                .with_env(
                    "GIT_AUTHOR_NAME".to_string(),
                    params.author.name.clone(),
                )
                .with_env(
                    "GIT_AUTHOR_EMAIL".to_string(),
                    params.author.email.clone(),
                )
                .with_env(
                    "GIT_AUTHOR_DATE".to_string(),
                    format_git_timestamp(
                        params.author.time_secs,
                        params.author.offset_minutes,
                    ),
                )
                .with_env(
                    "GIT_COMMITTER_NAME".to_string(),
                    params.committer.name.clone(),
                )
                .with_env(
                    "GIT_COMMITTER_EMAIL".to_string(),
                    params.committer.email.clone(),
                )
                .with_env(
                    "GIT_COMMITTER_DATE".to_string(),
                    format_git_timestamp(
                        params.committer.time_secs,
                        params.committer.offset_minutes,
                    ),
                )
                .with_stdin(message.as_bytes().to_vec());
        let commit_output = self.git_command_with(cmd_params)?;

        if !commit_output.success {
            return Err(GitError::CommandFailed {
                status_code: commit_output.status_code,
                stderr: commit_output.stderr_lossy(),
            });
        }

        let new_oid = ObjectId::new(commit_output.stdout_lossy().trim());
        if let Some(ref_name) = &params.update_ref {
            self.git_command_trusted(vec![
                "update-ref".to_string(),
                ref_name.clone(),
                new_oid.as_str().to_string(),
            ])?;
        }

        Ok(new_oid)
    }
    pub fn commit_cherry_pick_sequence(
        &self,
        params: CommitCherryPickSequence,
    ) -> GitResult<ObjectId> {
        let mut last_oid: Option<ObjectId> = None;

        for cherry_oid in params.cherrypick_oids {
            let pick_params = CommitCherryPickParams {
                cherrypick_oid: cherry_oid,
                author: params.author.clone(),
                committer: params.committer.clone(),
                message: None, // Use original commit message
                mainline: 1,   // Default mainline
                update_ref: params.update_ref.clone(),
            };

            last_oid = Some(self.commit_pick(pick_params)?);
        }

        last_oid.ok_or_else(|| {
            GitError::ParseError("cherry-pick sequence was empty".to_string())
        })
    }
}
