// Copyright 2025 Georges Racinet <georges.racinet@cloudcrane.io>
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
//
// SPDX-License-Identifier: GPL-2.0-or-later
use std::future::Future;
use std::path::PathBuf;

use tokio::task::JoinError;
use tonic::{metadata::MetadataMap, Extensions, Request, Status};

use crate::config::Config;
use crate::gitaly::Repository;
use crate::hgitaly::mercurial_operations_service_client::MercurialOperationsServiceClient;
use crate::hgitaly::{GetWorkingDirectoryRequest, ReleaseWorkingDirectoryRequest};
use crate::repository::blocking_join_error_status;
use crate::sidecar;

/// Execute the given async function in a working directory
///
/// The working directory is properly reserved and released at the end of the processing.
/// The `action` function is supposed to use channels for communication with the caller if
/// needed (streaming responses…).
///
/// The signature of the function is what is needed to be async and created
/// by `spawn_blocking`. Other cases can manage, even if it means an artificial additional
/// `Ok(_)` for the outer `Result`.
pub async fn with_workdir<R, Fut: Future<Output = Result<Result<R, Status>, JoinError>>>(
    config: &Config,
    gl_repo: &Repository,
    revision: &[u8],
    sidecar_servers: &sidecar::Servers,
    metadata: &MetadataMap,
    action: impl FnOnce(PathBuf) -> Fut + Send + 'static,
) -> Result<R, Status> {
    let channel = sidecar_servers
        .available_channel()
        .await
        .map_err(|e| Status::internal(format!("Could not initiate the sidecar channel: {}", e)))?;

    let mut client = MercurialOperationsServiceClient::new(channel);
    let inner_req = GetWorkingDirectoryRequest {
        repository: Some(gl_repo.clone()),
        client_id: config.client_id.clone(),
        incarnation_id: config.incarnation_id.clone(),
        revision: revision.to_vec(),
    };
    let request = Request::from_parts(metadata.clone(), Extensions::default(), inner_req);
    let resp = client.get_working_directory(request).await?.into_inner();
    let mut path = config.repositories_root.clone();
    path.push(resp.relative_path);

    let result = action(path).await.map_err(blocking_join_error_status)?;

    let inner_req = ReleaseWorkingDirectoryRequest {
        repository: Some(gl_repo.clone()),
        client_id: config.client_id.clone(),
        incarnation_id: config.incarnation_id.clone(),
        working_directory_id: resp.working_directory_id,
    };
    let request = Request::from_parts(metadata.clone(), Extensions::default(), inner_req);
    client.release_working_directory(request).await?;
    result
}
