use std::env;
use std::ops::RangeInclusive;
use std::sync::LazyLock;

use libm::support::{Float, Int};
use rand::distr::uniform::SampleUniform;
use rand::distr::{Alphanumeric, StandardUniform};
use rand::prelude::Distribution;
use rand::{RngExt, SeedableRng};
use rand_chacha::ChaCha8Rng;

use crate::CheckCtx;
use crate::generate::{KnownSize, product2, product3};
use crate::num::full_range;
use crate::run_cfg::{int_range, iteration_count};

pub(crate) const SEED_ENV: &str = "LIBM_SEED";

pub static SEED: LazyLock<[u8; 32]> = LazyLock::new(|| {
    let s = env::var(SEED_ENV).unwrap_or_else(|_| {
        let mut rng = rand::rng();
        (0..32).map(|_| rng.sample(Alphanumeric) as char).collect()
    });

    s.as_bytes().try_into().unwrap_or_else(|_| {
        panic!("Seed must be 32 characters, got `{s}`");
    })
});

/// Generate a sequence of random values of this type.
pub trait RandomInput: Sized {
    fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self> + Send, u64);
}

/// Generate a sequence of deterministically random floats.
fn random_floats<F: Float>(count: u64) -> impl Iterator<Item = F> + Clone
where
    StandardUniform: Distribution<F::Int>,
{
    let mut rng = ChaCha8Rng::from_seed(*SEED);

    // Generate integers to get a full range of bitpatterns (including NaNs), then convert back
    // to the float type.
    (0..count).map(move |_| F::from_bits(rng.random::<F::Int>()))
}

/// Generate a sequence of deterministically random `i32`s within a specified range.
fn random_ints<I>(count: u64, range: RangeInclusive<I>) -> impl Iterator<Item = I> + Clone
where
    I: Int + SampleUniform,
{
    let mut rng = ChaCha8Rng::from_seed(*SEED);
    (0..count).map(move |_| rng.random_range::<I, _>(range.clone()))
}

macro_rules! impl_random_input {
    ($fty:ty) => {
        impl RandomInput for ($fty,) {
            fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
                let count = iteration_count(ctx, 0);
                let iter = random_floats(count).map(|f: $fty| (f,));
                (iter, count)
            }
        }

        impl RandomInput for ($fty, $fty) {
            fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
                let count0 = iteration_count(ctx, 0);
                let count1 = iteration_count(ctx, 1);
                let iter0 = random_floats(count0);
                let iter1 = random_floats(count1);
                let iter = product2(iter0, iter1);
                let count = count0.strict_mul(count1);
                (iter, count)
            }
        }

        impl RandomInput for ($fty, $fty, $fty) {
            fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
                let count0 = iteration_count(ctx, 0);
                let count1 = iteration_count(ctx, 1);
                let count2 = iteration_count(ctx, 2);
                let iter0 = random_floats(count0);
                let iter1 = random_floats(count1);
                let iter2 = random_floats(count2);
                let iter = product3(iter0, iter1, iter2);
                let count = count0.strict_mul(count1).strict_mul(count2);
                (iter, count)
            }
        }

        impl RandomInput for (i32, $fty) {
            fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
                let count0 = iteration_count(ctx, 0);
                let count1 = iteration_count(ctx, 1);
                let range0 = int_range::<i32>(ctx, 0).unwrap_or(full_range());
                let iter0 = random_ints(count0, range0);
                let iter1 = random_floats(count1);
                let iter = product2(iter0, iter1);
                let count = count0.strict_mul(count1);
                (iter, count)
            }
        }

        impl RandomInput for ($fty, i32) {
            fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
                let count0 = iteration_count(ctx, 0);
                let count1 = iteration_count(ctx, 1);
                let range1 = int_range::<i32>(ctx, 1).unwrap_or(full_range());
                let iter0 = random_floats(count0);
                let iter1 = random_ints(count1, range1.clone());
                let iter = product2(iter0, iter1);
                let count = count0.strict_mul(count1);
                (iter, count)
            }
        }
    };
}

#[cfg(f16_enabled)]
impl_random_input!(f16);
impl_random_input!(f32);
impl_random_input!(f64);
#[cfg(f128_enabled)]
impl_random_input!(f128);

macro_rules! impl_random_input_int {
    (@skip_u32 $ity:ty) => {
        impl RandomInput for ($ity,) {
            fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
                let count = iteration_count(ctx, 0);
                let range = int_range::<$ity>(ctx, 0).unwrap_or(full_range());
                let iter = random_ints(count, range).map(|f: $ity| (f,));
                (iter, count)
            }
        }

        impl RandomInput for ($ity, $ity) {
            fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
                let count0 = iteration_count(ctx, 0);
                let count1 = iteration_count(ctx, 1);
                let range0 = int_range::<$ity>(ctx, 0).unwrap_or(full_range());
                let range1 = int_range::<$ity>(ctx, 1).unwrap_or(full_range());
                let iter0 = random_ints(count0, range0);
                let iter1 = random_ints(count1, range1.clone());
                let iter = product2(iter0, iter1);
                (iter, count0 * count1)
            }
        }
    };
    ($ity:ty) => {
        impl_random_input_int!(@skip_u32 $ity);

        impl RandomInput for ($ity, u32) {
            fn get_cases(ctx: &CheckCtx) -> (impl Iterator<Item = Self>, u64) {
                let count0 = iteration_count(ctx, 0);
                let count1 = iteration_count(ctx, 1);
                let range0 = int_range::<$ity>(ctx, 0).unwrap_or(full_range());
                let range1 = int_range::<u32>(ctx, 1).unwrap_or(full_range());
                let iter0 = random_ints(count0, range0);
                let iter1 = random_ints(count1, range1.clone());
                let iter = product2(iter0, iter1);
                (iter, count0 * count1)
            }
        }
    };
}

impl_random_input_int!(i32);
impl_random_input_int!(i64);
impl_random_input_int!(i128);
impl_random_input_int!(@skip_u32 u32);
impl_random_input_int!(u64);
impl_random_input_int!(u128);

/// Create a test case iterator.
pub fn get_test_cases<RustArgs: RandomInput>(
    ctx: &CheckCtx,
) -> (
    impl Iterator<Item = RustArgs> + Send + use<'_, RustArgs>,
    u64,
) {
    let (iter, count) = RustArgs::get_cases(ctx);

    // Wrap in `KnownSize` so we get an assertion if the cuunt is wrong.
    (KnownSize::new(iter, count), count)
}
