use std::sync::Once;

use tracing_subscriber::EnvFilter;

static INIT: Once = Once::new();

pub fn init_test_tracing() {
    INIT.call_once(|| {
        let filter = EnvFilter::try_from_default_env()
            .unwrap_or_else(|_| EnvFilter::new("bone_types=debug"));
        let _ = tracing_subscriber::fmt()
            .with_env_filter(filter)
            .with_test_writer()
            .try_init();
    });
}

#[derive(Debug, thiserror::Error)]
#[error("non-deterministic output across two invocations of the same closure")]
pub struct NonDeterministic;

pub fn check_bit_identical<T: Eq, F: Fn() -> T>(f: F) -> Result<(), NonDeterministic> {
    let first = f();
    let second = f();
    if first == second {
        Ok(())
    } else {
        Err(NonDeterministic)
    }
}

#[cfg(test)]
mod tests {
    use super::{check_bit_identical, init_test_tracing};

    #[test]
    fn deterministic_closure_is_ok() {
        assert!(check_bit_identical(|| 42_u64).is_ok());
        assert!(check_bit_identical(|| (1_u32, 2_u32, 3_u32)).is_ok());
    }

    #[test]
    fn non_deterministic_closure_is_err() {
        let counter = std::sync::atomic::AtomicU64::new(0);
        let result =
            check_bit_identical(|| counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed));
        assert!(result.is_err());
    }

    #[test]
    fn init_test_tracing_idempotent() {
        init_test_tracing();
        init_test_tracing();
    }
}
