use std::{future::Future, sync::LazyLock, time::Duration};

#[cfg(test)]
use cognite::ClientConfig;
use cognite::CogniteClient;
use rand::{distr::Alphanumeric, rng, RngExt};
use tokio::sync::Semaphore;

#[allow(dead_code)]
pub fn get_client() -> CogniteClient {
    CogniteClient::new_oidc(
        "rust_sdk_test",
        Some(ClientConfig {
            max_retries: 5,
            ..Default::default()
        }),
    )
    .unwrap()
}

#[allow(dead_code)]
pub fn get_client_for_mocking(api_base_url: &str, project_name: &str) -> CogniteClient {
    CogniteClient::new_custom_auth(
        api_base_url,
        project_name,
        cognite::AuthHeaderManager::AuthTicket("my_ticket".to_string()),
        "rust_sdk_test",
        Some(ClientConfig {
            max_retries: 5,
            ..Default::default()
        }),
    )
    .unwrap()
}

#[allow(dead_code)]
pub static PREFIX: LazyLock<String> = LazyLock::new(|| {
    format!(
        "rust-sdk-test-{}",
        rand::rng()
            .sample_iter(&Alphanumeric)
            .take(7)
            .map(char::from)
            .collect::<String>()
    )
});

#[allow(dead_code)]
pub fn get_path(base_url: &str, project: &str, endpoint: &str) -> String {
    format!("{}/api/v1/projects/{}/{}", base_url, project, endpoint)
}

#[allow(dead_code)]
pub static CDM_CONCURRENCY_PERMITS: Semaphore = Semaphore::const_new(2);

#[allow(dead_code)]
pub trait Retry<'a, T, E>: FnMut() -> <Self as Retry<'a, T, E>>::Fut {
    type Fut: Future<Output = Result<T, E>>;
}

impl<T, E, F, Fut> Retry<'_, T, E> for F
where
    F: FnMut() -> Fut,
    Fut: Future<Output = Result<T, E>>,
{
    type Fut = Fut;
}

#[allow(dead_code)]
pub struct Backoff {
    jitter: f64,
    next_backoff: f64,
    max_backoff: f64,
    multiplier: f64,
}

impl Default for Backoff {
    fn default() -> Self {
        Self {
            jitter: 0.5,
            next_backoff: 0.,
            max_backoff: 5.,
            multiplier: 3.,
        }
    }
}

impl Iterator for Backoff {
    type Item = Duration;

    fn next(&mut self) -> Option<Self::Item> {
        let jitter: f64 = rng().random_range(-self.jitter..self.jitter);
        let mut backoff = (self.next_backoff + jitter).min(self.max_backoff);
        backoff = backoff.max(0.);
        self.next_backoff = (self.next_backoff * self.multiplier).min(self.max_backoff);
        Some(Duration::from_secs_f64(backoff))
    }
}
