use std::sync::Arc;

use tonic::{Request, Response, Status};

use crate::rpc::{
    error::{spawn_blocking_error, to_status},
    proto as p,
    registry::RepoRegistry,
};

pub struct MergeServiceImpl {
    pub registry: Arc<RepoRegistry>,
}

#[tonic::async_trait]
impl p::merge_service_server::MergeService for MergeServiceImpl {
    async fn merge_base(
        &self,
        req: Request<p::MergeBaseRequest>,
    ) -> Result<Response<p::MergeBaseResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let a: crate::cmd::oid::ObjectId =
            inner.oid_a.unwrap_or_default().into();
        let b: crate::cmd::oid::ObjectId =
            inner.oid_b.unwrap_or_default().into();
        let result = tokio::task::spawn_blocking(move || bare.merge_base(a, b))
            .await
            .map_err(spawn_blocking_error)?
            .map_err(to_status)?;
        Ok(Response::new(p::MergeBaseResponse {
            base_oid: Some(result.into()),
        }))
    }

    async fn merge_base_many(
        &self,
        req: Request<p::MergeBaseManyRequest>,
    ) -> Result<Response<p::MergeBaseManyResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let oids = inner.oids.into_iter().map(Into::into).collect::<Vec<_>>();
        let result =
            tokio::task::spawn_blocking(move || bare.merge_base_many(oids))
                .await
                .map_err(spawn_blocking_error)?
                .map_err(to_status)?;
        Ok(Response::new(p::MergeBaseManyResponse {
            base_oid: Some(result.into()),
        }))
    }

    async fn merge_base_octopus(
        &self,
        req: Request<p::MergeBaseOctopusRequest>,
    ) -> Result<Response<p::MergeBaseOctopusResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let oids = inner.oids.into_iter().map(Into::into).collect::<Vec<_>>();
        let result =
            tokio::task::spawn_blocking(move || bare.merge_base_octopus(oids))
                .await
                .map_err(spawn_blocking_error)?
                .map_err(to_status)?;
        Ok(Response::new(p::MergeBaseOctopusResponse {
            base_oid: Some(result.into()),
        }))
    }

    async fn merge_analysis(
        &self,
        req: Request<p::MergeAnalysisRequest>,
    ) -> Result<Response<p::MergeAnalysisResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let their_commit: crate::cmd::oid::ObjectId =
            inner.oid_a.unwrap_or_default().into();
        let result = tokio::task::spawn_blocking(move || {
            bare.merge_analysis(their_commit)
        })
        .await
        .map_err(spawn_blocking_error)?
        .map_err(to_status)?;
        Ok(Response::new(p::MergeAnalysisResponse {
            analysis: Some(result.0.into()),
            preference: Some(result.1.into()),
        }))
    }

    async fn merge_analysis_for_ref(
        &self,
        req: Request<p::MergeAnalysisForRefRequest>,
    ) -> Result<Response<p::MergeAnalysisForRefResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let ref_name = inner.ref_name.clone();
        let result = tokio::task::spawn_blocking(move || {
            bare.merge_analysis_for_ref(ref_name)
        })
        .await
        .map_err(spawn_blocking_error)?
        .map_err(to_status)?;
        Ok(Response::new(p::MergeAnalysisForRefResponse {
            analysis: Some(result.0.into()),
            preference: Some(result.1.into()),
        }))
    }

    async fn merge_is_conflicted(
        &self,
        req: Request<p::MergeIsConflictedRequest>,
    ) -> Result<Response<p::MergeIsConflictedResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let result =
            tokio::task::spawn_blocking(move || bare.merge_is_conflicted())
                .await
                .map_err(spawn_blocking_error)?
                .map_err(to_status)?;
        Ok(Response::new(p::MergeIsConflictedResponse {
            is_conflicted: result,
        }))
    }

    async fn mergehead_list(
        &self,
        req: Request<p::MergeheadListRequest>,
    ) -> Result<Response<p::MergeheadListResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let result = tokio::task::spawn_blocking(move || bare.mergehead_list())
            .await
            .map_err(spawn_blocking_error)?
            .map_err(to_status)?;
        Ok(Response::new(p::MergeheadListResponse {
            oids: result.into_iter().map(Into::into).collect(),
        }))
    }

    async fn merge_tree(
        &self,
        req: Request<p::MergeTreeRequest>,
    ) -> Result<Response<p::MergeTreeResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let ours: crate::cmd::oid::ObjectId =
            inner.ours.unwrap_or_default().into();
        let theirs: crate::cmd::oid::ObjectId =
            inner.theirs.unwrap_or_default().into();
        let options = inner.options.map(Into::into);
        let result = tokio::task::spawn_blocking(move || {
            bare.merge_tree(ours, theirs, options)
        })
        .await
        .map_err(spawn_blocking_error)?
        .map_err(to_status)?;
        Ok(Response::new(p::MergeTreeResponse {
            result: Some(result.into()),
        }))
    }

    async fn merge_commit(
        &self,
        req: Request<p::MergeCommitRequest>,
    ) -> Result<Response<p::MergeCommitResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let params = inner.params.unwrap_or_default().into();
        let result =
            tokio::task::spawn_blocking(move || bare.merge_commit(params))
                .await
                .map_err(spawn_blocking_error)?
                .map_err(to_status)?;
        Ok(Response::new(p::MergeCommitResponse {
            oid: Some(result.into()),
        }))
    }

    async fn squash_commit(
        &self,
        req: Request<p::SquashCommitRequest>,
    ) -> Result<Response<p::SquashCommitResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let params = inner.params.unwrap_or_default().into();
        let result =
            tokio::task::spawn_blocking(move || bare.squash_commit(params))
                .await
                .map_err(spawn_blocking_error)?
                .map_err(to_status)?;
        Ok(Response::new(p::SquashCommitResponse {
            oid: Some(result.into()),
        }))
    }

    async fn merge_abort(
        &self,
        req: Request<p::MergeAbortRequest>,
    ) -> Result<Response<p::MergeAbortResponse>, Status> {
        let inner = req.into_inner();
        let bare = self.registry.get(&inner.repo_id).await?;
        let _result = tokio::task::spawn_blocking(move || bare.merge_abort())
            .await
            .map_err(spawn_blocking_error)?
            .map_err(to_status)?;
        Ok(Response::new(p::MergeAbortResponse {}))
    }
}
