use git::rpc::{proto as p, proto::commit_service_client::CommitServiceClient};
use session::Session;

use crate::{AppService, error::AppError, git::rpc_err};

#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
pub struct CompareResponse {
    pub base_commit: CompareCommit,
    pub head_commit: CompareCommit,
    pub ahead_by: i32,
    pub behind_by: i32,
    pub total_commits: i32,
    pub commits: Vec<CompareCommit>,
    pub files_changed: u64,
    pub insertions: u64,
    pub deletions: u64,
}

#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
pub struct CompareCommit {
    pub sha: String,
    pub message: String,
    pub author_name: Option<String>,
    pub author_email: Option<String>,
}

impl AppService {
    pub async fn git_compare(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
        base: &str,
        head: &str,
    ) -> Result<CompareResponse, AppError> {
        let repo = self.git_require_member(ctx, wk_name, repo_name).await?;
        let mut client = CommitServiceClient::new(self.git.clone());

        fn oid(s: &str) -> p::ObjectId {
            p::ObjectId {
                value: s.to_string(),
            }
        }

        let base_info = client
            .commit_info(tonic::Request::new(p::CommitInfoRequest {
                repo_id: repo.id.to_string(),
                oid: Some(oid(base)),
            }))
            .await
            .map_err(rpc_err)?
            .into_inner();

        let head_info = client
            .commit_info(tonic::Request::new(p::CommitInfoRequest {
                repo_id: repo.id.to_string(),
                oid: Some(oid(head)),
            }))
            .await
            .map_err(rpc_err)?
            .into_inner();

        let history = client
            .commit_history(tonic::Request::new(p::CommitHistoryRequest {
                repo_id: repo.id.to_string(),
                limit: 250,
                skip: 0,
                sort: 0,
                branch: Some(format!("{base}..{head}")),
            }))
            .await
            .map_err(rpc_err)?
            .into_inner();

        let commits: Vec<CompareCommit> = history
            .commits
            .into_iter()
            .map(|c| {
                let author_name = c.author.as_ref().map(|a| a.name.clone());
                let author_email = c.author.as_ref().map(|a| a.email.clone());
                CompareCommit {
                    sha: c.oid.map(|o| o.value).unwrap_or_default(),
                    message: c.summary,
                    author_name,
                    author_email,
                }
            })
            .collect();

        let diff = crate::AppService::git_diff_stats(
            self,
            ctx,
            wk_name,
            repo_name,
            base.to_string(),
            head.to_string(),
            None,
        )
        .await?;

        let stats = diff.result.and_then(|r| r.stats);
        let files_changed =
            stats.as_ref().map(|s| s.files_changed).unwrap_or(0);
        let insertions = stats.as_ref().map(|s| s.insertions).unwrap_or(0);
        let deletions = stats.as_ref().map(|s| s.deletions).unwrap_or(0);

        Ok(CompareResponse {
            base_commit: cmt(base_info.commit),
            head_commit: cmt(head_info.commit),
            ahead_by: commits.len() as i32,
            behind_by: 0,
            total_commits: commits.len() as i32,
            commits,
            files_changed,
            insertions,
            deletions,
        })
    }
}

fn cmt(c: Option<p::CommitMeta>) -> CompareCommit {
    c.map(|c| {
        let author_name = c.author.as_ref().map(|a| a.name.clone());
        let author_email = c.author.as_ref().map(|a| a.email.clone());
        CompareCommit {
            sha: c.oid.map(|o| o.value).unwrap_or_default(),
            message: c.message,
            author_name,
            author_email,
        }
    })
    .unwrap_or_else(|| CompareCommit {
        sha: String::new(),
        message: String::new(),
        author_name: None,
        author_email: None,
    })
}
