use std::sync::Arc;

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

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

pub struct TreeServiceImpl {
    pub registry: Arc<RepoRegistry>,
    pub cache: AppCache,
}

#[tonic::async_trait]
impl p::tree_service_server::TreeService for TreeServiceImpl {
    async fn tree_entries(
        &self,
        req: Request<p::TreeEntriesRequest>,
    ) -> Result<Response<p::TreeEntriesResponse>, Status> {
        let inner = req.into_inner();
        let repo_id = inner.repo_id.clone();
        let oid_val = inner.oid.clone().map(|o| o.value).unwrap_or_default();
        let base_path = inner.base_path.clone();
        let want_last = inner.last;
        if !want_last {
            let bare = self.registry.get(&repo_id).await?;
            let oid = inner.oid.unwrap_or_default().into();
            let entries = tokio::task::spawn_blocking(move || {
                use crate::errors::GitError;
                bare.tree_entries(oid)
                    .map_err(|e| {
                        GitError::Internal(format!("tree_entries: {e}"))
                    })
                    .map(|e| {
                        e.into_iter()
                            .map(Into::into)
                            .collect::<Vec<p::TreeEntry>>()
                    })
            })
            .await
            .map_err(spawn_blocking_error)?
            .map_err(to_status)?;
            return Ok(Response::new(p::TreeEntriesResponse { entries }));
        }
        let cache_key = format!(
            "git:rpc:cache:tree:entries:{}:{}:{}",
            repo_id, oid_val, base_path
        );

        if let Ok(Some(cached)) =
            self.cache.get::<Vec<p::TreeEntry>>(&cache_key).await
        {
            return Ok(Response::new(p::TreeEntriesResponse {
                entries: cached,
            }));
        }

        let bare = self.registry.get(&repo_id).await?;
        let bare_bg = bare.clone();
        let oid = inner.oid.unwrap_or_default().into();
        let base_path_bg = base_path.clone();
        let cache_bg = self.cache.clone();
        let cache_key_bg = cache_key.clone();

        let entries = tokio::task::spawn_blocking(move || {
            use crate::errors::GitError;
            bare.tree_entries(oid)
                .map_err(|e| GitError::Internal(format!("tree_entries: {e}")))
                .map(|e| {
                    e.into_iter().map(Into::into).collect::<Vec<p::TreeEntry>>()
                })
        })
        .await
        .map_err(spawn_blocking_error)?
        .map_err(to_status)?;

        let mut response = entries.clone();
        for entry in &mut response {
            entry.last_commit_message.clear();
            entry.last_commit_time.clear();
            entry.last_commit_author_name.clear();
            entry.last_commit_author_email.clear();
        }

        tokio::task::spawn(async move {
            let enriched = tokio::task::spawn_blocking(move || {
                let paths: Vec<String> = entries
                    .iter()
                    .map(|e| {
                        if base_path_bg.is_empty() {
                            e.name.clone()
                        } else {
                            format!("{}/{}", base_path_bg, e.name)
                        }
                    })
                    .collect();

                let last_commits = match bare_bg.last_commits_for_paths(&paths)
                {
                    Ok(lc) => lc,
                    Err(_) => return entries,
                };

                let mut out = entries;
                for (i, info) in last_commits.into_iter().enumerate() {
                    if let Some(info) = info {
                        if let Some(e) = out.get_mut(i) {
                            e.last_commit_message = info.message;
                            e.last_commit_time = info.time;
                            e.last_commit_author_name = info.author_name;
                            e.last_commit_author_email = info.author_email;
                        }
                    }
                }
                out
            })
            .await;

            if let Ok(entries) = enriched {
                let _ = cache_bg.set(&cache_key_bg, &entries).await;
            }
        });

        Ok(Response::new(p::TreeEntriesResponse { entries: response }))
    }

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

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

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