pub(crate) mod bucket;

use std::sync::Arc;

use reqwest::{Method, Url};

use crate::{errors::Error, token_provider::TokenProvider};

const GCS_API_URL: &str = "https://www.googleapis.com/storage/v1/";

/// A Google Cloud Storage client.
/// Note: This type does not need to be wrapped in an `Arc` as it uses it internally.
#[derive(Clone)]
pub struct Client {
    token_provider: Arc<dyn TokenProvider>,
    client: reqwest::Client,
    api_url: Arc<Url>,
}

impl Client {
    /// Creates a new GCS client.
    pub fn new(token_provider: impl TokenProvider + 'static) -> Self {
        Self::new_with_client(token_provider, Default::default())
    }

    /// Creates a new GCS client using a provided reqwest::Client.
    pub fn new_with_client(
        token_provider: impl TokenProvider + 'static,
        client: reqwest::Client,
    ) -> Self {
        Self {
            token_provider: Arc::new(token_provider),
            client,
            api_url: Arc::new(Url::parse(GCS_API_URL).unwrap()),
        }
    }

    /// Returns a `BucketClient` scoped to the provided bucket.
    pub fn into_bucket_client(self, bucket_name: String) -> bucket::GcsBucketClient {
        bucket::GcsBucketClient::new(self, bucket_name)
    }

    async fn build_request(
        &self,
        method: Method,
        path: &str,
    ) -> Result<reqwest::RequestBuilder, Error> {
        self.build_request_with_url(method, self.api_url.join(path).expect("malformed url"))
            .await
    }

    async fn build_request_with_url(
        &self,
        method: Method,
        url: Url,
    ) -> Result<reqwest::RequestBuilder, Error> {
        let auth_token = self
            .token_provider
            .get_token()
            .await
            .map_err(Error::TokenFetch)?;
        Ok(self
            .client
            .request(method, url)
            .bearer_auth(&auth_token.token))
    }
}
