use getrandom::fill;

use crate::logging::KameleoonLogger;

// MARK: - generate_string

pub(crate) fn generate_string(charset: &str, len: usize) -> String {
    let charset_bytes = charset.as_bytes();
    let mut random_bytes = vec![0u8; len];

    let _ = fill(&mut random_bytes).inspect_err(|_e| KameleoonLogger::error("getrandom::fill failed on the OS"));

    // Map random bytes to charset indices and collect to String
    random_bytes
        .into_iter()
        .map(|byte| {
            let idx = (byte as usize) % charset_bytes.len();
            charset_bytes[idx] as char
        })
        .collect()
}

// MARK: - NonceGenerator

pub(crate) struct NonceGenerator;

impl NonceGenerator {
    pub fn generate() -> String {
        const HEXADECIMAL_ALPHABET: &str = "0123456789ABCDEF";
        const NONCE_LENGTH: usize = 16;
        generate_string(HEXADECIMAL_ALPHABET, NONCE_LENGTH)
    }
}

// MARK: - VisitorCodeGenerator

pub(crate) struct VisitorCodeGenerator;

pub(crate) const VISITOR_CODE_LENGTH: usize = 16;

impl VisitorCodeGenerator {
    pub fn generate() -> String {
        const VISITOR_CODE_ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz0123456789";
        generate_string(VISITOR_CODE_ALPHABET, VISITOR_CODE_LENGTH)
    }
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use super::*;
    use rstest::rstest;

    #[rstest]
    #[case("abc", 0)]
    #[case("abc", 5)]
    #[case("1234567890", 16)]
    #[case("xyz", 100)]
    #[case("abcdefghijklmnopqrstuvwxyz0123456789", 1024)]
    fn test_generate_string_length_and_charset(#[case] charset: &str, #[case] len: usize) {
        let result = generate_string(charset, len);
        assert_eq!(result.len(), len);
        assert!(result.chars().all(|c| charset.contains(c)));
    }

    #[test]
    fn test_nonce_generator_length_and_charset() {
        let result = NonceGenerator::generate();
        assert_eq!(result.len(), 16);
        assert!(result.chars().all(|c| "0123456789ABCDEF".contains(c)));
    }

    #[test]
    fn test_visitor_code_generator_length_and_charset() {
        let result = VisitorCodeGenerator::generate();
        assert_eq!(result.len(), 16);
        assert!(result.chars().all(|c| "abcdefghijklmnopqrstuvwxyz0123456789".contains(c)));
    }

    #[test]
    #[should_panic(expected = "attempt to calculate the remainder with a divisor of zero")]
    fn test_string_generator_empty_charset_should_panic() {
        // Panics due to index out of bounds when charset is empty
        generate_string("", 10);
    }
}
