//! Fuzz testing

#![allow(missing_docs, reason = "XXX")]
#![allow(unsafe_code, reason = "XXX")]
#![allow(unsafe_op_in_unsafe_fn, reason = "XXX")]
#![allow(clippy::cognitive_complexity, reason = "XXX")]
#![allow(clippy::items_after_statements, reason = "XXX")]
#![allow(clippy::missing_panics_doc, reason = "XXX")]
#![allow(clippy::too_many_lines, reason = "XXX")]

use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use core::mem::MaybeUninit;
use core::{slice, str};

use crate::backend::generic::{decode_generic_unchecked, encode_generic_unchecked};
use crate::traits::Hex;
use crate::util::{HEX_CHARS_LOWER, HEX_CHARS_UPPER};
use crate::{decode, decode_generic, encode, encode_generic};

pub fn fuzz_encode(data: &[u8]) {
    let expected = data
        .iter()
        .flat_map(|b| {
            [
                HEX_CHARS_LOWER[(*b >> 4) as usize] as char,
                HEX_CHARS_LOWER[(*b & 0b1111) as usize] as char,
            ]
        })
        .collect::<String>();

    assert!(
        data.encode_hex::<String, false>()
            .unwrap()
            .eq_ignore_ascii_case(&expected)
    );
    assert!(
        data.encode_hex::<Box<str>, false>()
            .unwrap()
            .eq_ignore_ascii_case(&expected)
    );
    assert!(
        data.encode_hex::<Arc<str>, false>()
            .unwrap()
            .eq_ignore_ascii_case(&expected)
    );
    assert!(
        data.encode_hex::<Rc<str>, false>()
            .unwrap()
            .eq_ignore_ascii_case(&expected)
    );
    assert!(
        data.encode_hex::<Vec<u8>, false>()
            .unwrap()
            .eq_ignore_ascii_case(expected.as_bytes())
    );
    assert!(
        data.encode_hex::<Box<[u8]>, false>()
            .unwrap()
            .eq_ignore_ascii_case(expected.as_bytes())
    );
    assert!(
        data.encode_hex::<Arc<[u8]>, false>()
            .unwrap()
            .eq_ignore_ascii_case(expected.as_bytes())
    );
    assert!(
        data.encode_hex::<Rc<[u8]>, false>()
            .unwrap()
            .eq_ignore_ascii_case(expected.as_bytes())
    );

    {
        let mut output = String::with_capacity(data.len() * 2);

        encode::<false>(data, unsafe { output.as_mut_vec() }).unwrap();

        assert!(
            expected.eq_ignore_ascii_case(&output),
            "expect \"{expected}\", got \"{output}\""
        );
    }

    {
        let mut output = String::with_capacity(data.len() * 2);

        encode::<true>(data, unsafe { output.as_mut_vec() }).unwrap();

        assert!(
            expected.eq_ignore_ascii_case(&output),
            "expect \"{expected}\", got \"{output}\""
        );
    }

    {
        let mut output = String::with_capacity(data.len() * 2);

        {
            let len =
                encode_generic::<false>(data, unsafe { output.as_mut_vec().spare_capacity_mut() })
                    .unwrap()
                    .len();

            unsafe {
                output.as_mut_vec().set_len(len);
            }
        }

        assert!(
            expected.eq_ignore_ascii_case(&output),
            "expect \"{expected}\", got \"{output}\""
        );
    }

    {
        let mut output = String::with_capacity(data.len() * 2);

        {
            let len =
                encode_generic::<true>(data, unsafe { output.as_mut_vec().spare_capacity_mut() })
                    .unwrap()
                    .len();

            unsafe {
                output.as_mut_vec().set_len(len);
            }
        }

        assert!(
            expected.eq_ignore_ascii_case(&output),
            "expect \"{expected}\", got \"{output}\""
        );
    }

    macro_rules! test {
        ($($f:tt)+) => {
            unsafe {
                let mut output = vec![[MaybeUninit::<u8>::uninit(); 2]; data.len()];

                $($f)+(data, &mut output);

                assert!(
                    slice::from_raw_parts(output.as_ptr().cast::<u8>(), output.len() * 2)
                        .eq_ignore_ascii_case(expected.as_bytes()),
                    "{}: expect \"{}\", got \"{}\" ({:?})",
                    stringify!($($f)+),
                    expected,
                    str::from_utf8(slice::from_raw_parts(
                        output.as_ptr().cast::<u8>(),
                        output.len() * 2
                    ))
                    .unwrap_or("<invalid utf-8>"),
                    slice::from_raw_parts(output.as_ptr().cast::<u8>(), output.len() * 2)
                );
            }
        };
    }

    #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
    {
        use crate::backend::aarch64::encode_neon_unchecked;

        test!(encode_neon_unchecked::<false>);
        test!(encode_neon_unchecked::<true>);
    }

    test!(encode_generic_unchecked::<false>);
    test!(encode_generic_unchecked::<true>);

    #[cfg(all(feature = "experimental-loongarch64-simd", target_arch = "loongarch64"))]
    {
        use crate::backend::loongarch64::{encode_lasx_unchecked, encode_lsx_unchecked};

        test!(encode_lsx_unchecked::<false>);
        test!(encode_lsx_unchecked::<true>);
        test!(encode_lasx_unchecked::<false>);
        test!(encode_lasx_unchecked::<true>);
    }

    #[cfg(feature = "portable-simd")]
    {
        use crate::backend::simd::encode_simd128_unchecked;

        test!(encode_simd128_unchecked::<false>);
        test!(encode_simd128_unchecked::<true>);
    }

    // #[cfg(target_arch = "wasm32")]
    // {
    //     use crate::backend::wasm32::encode_simd128_unchecked;
    //
    //     test!(encode_simd128_unchecked::<false>);
    //     test!(encode_simd128_unchecked::<true>);
    // }

    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    {
        use crate::backend::x86::{
            encode_avx2_unchecked, encode_avx512_unchecked, encode_ssse3_unchecked,
        };

        test!(encode_ssse3_unchecked::<false>);
        test!(encode_ssse3_unchecked::<true>);

        test!(encode_avx2_unchecked::<false>);
        test!(encode_avx2_unchecked::<true>);

        test!(encode_avx512_unchecked::<false>);
        test!(encode_avx512_unchecked::<true>);
    }
}

pub fn fuzz_decode(data: &[u8]) {
    let expected_lowercase = data
        .iter()
        .flat_map(|b| {
            [
                HEX_CHARS_LOWER[(*b >> 4) as usize] as char,
                HEX_CHARS_LOWER[(*b & 0b1111) as usize] as char,
            ]
        })
        .collect::<String>();

    let expected_uppercase = data
        .iter()
        .flat_map(|b| {
            [
                HEX_CHARS_UPPER[(*b >> 4) as usize] as char,
                HEX_CHARS_UPPER[(*b & 0b1111) as usize] as char,
            ]
        })
        .collect::<String>();

    assert_eq!(expected_lowercase.decode_hex::<Vec<u8>>().unwrap(), data);
    assert_eq!(
        &*expected_uppercase.decode_hex::<Box<[u8]>>().unwrap(),
        data
    );
    assert_eq!(
        &*expected_uppercase.decode_hex::<Arc<[u8]>>().unwrap(),
        data
    );
    assert_eq!(&*expected_uppercase.decode_hex::<Rc<[u8]>>().unwrap(), data);

    {
        let mut decoded = Vec::with_capacity(data.len());

        decode(expected_lowercase.as_bytes(), &mut decoded).unwrap();

        assert_eq!(data, decoded.as_slice());
    }

    {
        let mut decoded = Vec::with_capacity(data.len());

        decode(expected_uppercase.as_bytes(), &mut decoded).unwrap();

        assert_eq!(data, decoded.as_slice());
    }

    {
        let mut decoded = Vec::with_capacity(data.len());

        {
            let len = decode_generic(expected_lowercase.as_bytes(), decoded.spare_capacity_mut())
                .unwrap()
                .len();

            unsafe {
                decoded.set_len(len);
            }
        }

        assert_eq!(data, decoded.as_slice());
    }

    {
        let mut decoded = Vec::with_capacity(data.len());

        {
            let len = decode_generic(expected_uppercase.as_bytes(), decoded.spare_capacity_mut())
                .unwrap()
                .len();

            unsafe {
                decoded.set_len(len);
            }
        }

        assert_eq!(data, decoded.as_slice());
    }

    macro_rules! test {
        ($($f:tt)+) => {
            unsafe {
                let mut decoded = vec![MaybeUninit::<u8>::uninit(); data.len()];

                let _ = $($f)+(
                    expected_lowercase.as_bytes().as_chunks_unchecked(),
                    &mut decoded,
                );

                assert!(
                    slice::from_raw_parts(decoded.as_ptr().cast::<u8>(), decoded.len()) == data,
                    "{}: decoding \"{}\", expect {:?}, got {:?}",
                    stringify!($($f)+),
                    expected_lowercase,
                    data,
                    slice::from_raw_parts(decoded.as_ptr().cast::<u8>(), decoded.len())
                );
            }
            unsafe {
                let mut decoded = vec![MaybeUninit::<u8>::uninit(); data.len()];

                let _ = $($f)+(
                    expected_uppercase.as_bytes().as_chunks_unchecked(),
                    &mut decoded,
                );

                assert!(
                    slice::from_raw_parts(decoded.as_ptr().cast::<u8>(), decoded.len()) == data,
                    "{}: decoding \"{}\", expect {:?}, got {:?}",
                    stringify!($($f)+),
                    expected_uppercase,
                    data,
                    slice::from_raw_parts(decoded.as_ptr().cast::<u8>(), decoded.len())
                );
            }
        };
    }

    #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
    {
        use crate::backend::aarch64::decode_neon_unchecked;

        test!(decode_neon_unchecked);
    }

    test!(decode_generic_unchecked::<false>);
    test!(decode_generic_unchecked::<true>);

    #[cfg(all(feature = "experimental-loongarch64-simd", target_arch = "loongarch64"))]
    {
        use crate::backend::loongarch64::{decode_lasx_unchecked, decode_lsx_unchecked};

        test!(decode_lsx_unchecked);
        test!(decode_lasx_unchecked);
    }

    #[cfg(feature = "portable-simd")]
    {
        use crate::backend::simd::decode_simd128_unchecked;

        test!(decode_simd128_unchecked);
    }

    // #[cfg(target_arch = "wasm32")]
    // {
    //     use crate::backend::wasm32::decode_simd128_unchecked;
    //
    //     test!(decode_simd128_unchecked);
    // }

    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    {
        use crate::backend::x86::{
            decode_avx2_unchecked, decode_avx512_unchecked, decode_ssse3_unchecked,
        };

        test!(decode_ssse3_unchecked);
        test!(decode_avx2_unchecked);
        test!(decode_avx512_unchecked);
    }
}

pub fn fuzz_validate(data: &[u8]) {
    if data.is_empty() {
        return;
    }

    let to_be_injected = data
        .iter()
        .copied()
        .find(|c| !c.is_ascii_hexdigit())
        .unwrap_or(0);

    let expected_lowercase = data
        .iter()
        .flat_map(|b| {
            [
                HEX_CHARS_LOWER[(*b >> 4) as usize] as char,
                HEX_CHARS_LOWER[(*b & 0b1111) as usize] as char,
            ]
        })
        .collect::<String>();

    let expected_uppercase = data
        .iter()
        .flat_map(|b| {
            [
                HEX_CHARS_UPPER[(*b >> 4) as usize] as char,
                HEX_CHARS_UPPER[(*b & 0b1111) as usize] as char,
            ]
        })
        .collect::<String>();

    macro_rules! test {
        ($f:ident) => {
            unsafe {
                let mut injected = expected_lowercase.clone();
                let len = injected.len();

                assert!(
                    $f(injected.as_bytes().as_chunks_unchecked()).is_ok(),
                    "{}: validating \"{}\", expect validation success",
                    stringify!($f),
                    expected_lowercase,
                );

                injected.as_mut_vec()[len / 2] = to_be_injected;

                assert!(
                    $f(injected.as_bytes().as_chunks_unchecked()).is_err(),
                    "{}: validating \"{}\", injected {} at {}, expect validation failure",
                    stringify!($f),
                    expected_lowercase,
                    to_be_injected,
                    len / 2,
                );
            }
            unsafe {
                let mut injected = expected_uppercase.clone();
                let len = injected.len();

                assert!(
                    $f(injected.as_bytes().as_chunks_unchecked()).is_ok(),
                    "{}: validating \"{}\", expect validation success",
                    stringify!($f),
                    expected_uppercase,
                );

                injected.as_mut_vec()[len / 2] = to_be_injected;

                assert!(
                    $f(injected.as_bytes().as_chunks_unchecked()).is_err(),
                    "{}: validating \"{}\", injected {} at {}, expect validation failure",
                    stringify!($f),
                    expected_uppercase,
                    to_be_injected,
                    len / 2,
                );
            }
        };
    }

    fn validate(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
        decode(src.as_flattened(), &mut Vec::with_capacity(src.len())).map(|_| ())
    }

    test!(validate);

    fn validate_generic(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
        decode_generic(
            src.as_flattened(),
            Vec::with_capacity(src.len()).spare_capacity_mut(),
        )
        .map(|_| ())
    }

    test!(validate_generic);

    #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))]
    {
        use crate::backend::aarch64::decode_neon_unchecked;

        fn validate_neon(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
            unsafe { decode_neon_unchecked(src, &mut vec![MaybeUninit::<u8>::uninit(); src.len()]) }
        }

        test!(validate_neon);
    }

    #[cfg(all(feature = "experimental-loongarch64-simd", target_arch = "loongarch64"))]
    {
        use crate::backend::loongarch64::{decode_lasx_unchecked, decode_lsx_unchecked};

        fn validate_lsx(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
            unsafe { decode_lsx_unchecked(src, &mut vec![MaybeUninit::<u8>::uninit(); src.len()]) }
        }

        fn validate_lasx(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
            unsafe { decode_lasx_unchecked(src, &mut vec![MaybeUninit::<u8>::uninit(); src.len()]) }
        }

        test!(validate_lsx);
        test!(validate_lasx);
    }

    #[cfg(feature = "portable-simd")]
    {
        use crate::backend::simd::decode_simd128_unchecked;

        fn validate_simd128(src: &[[u8; 2]]) -> Result<(), crate::error::InvalidInput> {
            unsafe {
                decode_simd128_unchecked(src, &mut vec![MaybeUninit::<u8>::uninit(); src.len()])
            }
        }

        test!(validate_simd128);
    }

    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    {
        use alloc::vec::Vec;

        use crate::backend::x86::{
            decode_avx2_unchecked, decode_avx512_unchecked, decode_ssse3_unchecked,
        };
        use crate::error::InvalidInput;

        fn validate_ssse3(src: &[[u8; 2]]) -> Result<(), InvalidInput> {
            unsafe {
                decode_ssse3_unchecked(src, Vec::with_capacity(src.len()).spare_capacity_mut())
            }
        }

        fn validate_avx2(src: &[[u8; 2]]) -> Result<(), InvalidInput> {
            unsafe {
                decode_avx2_unchecked(src, Vec::with_capacity(src.len()).spare_capacity_mut())
            }
        }

        fn validate_avx512(src: &[[u8; 2]]) -> Result<(), InvalidInput> {
            unsafe {
                decode_avx512_unchecked(src, Vec::with_capacity(src.len()).spare_capacity_mut())
            }
        }

        test!(validate_ssse3);
        test!(validate_avx2);
        test!(validate_avx512);
    }
}

#[cfg(test)]
#[cfg(not(miri))]
mod smoking {
    use alloc::vec::Vec;
    use core::iter;

    use super::*;

    #[test]
    fn test_fuzz() {
        for size in 0..1024 {
            let bytes = iter::repeat_n(fastrand::u8(..), size).collect::<Vec<_>>();

            fuzz_encode(&bytes);
            fuzz_decode(&bytes);
            fuzz_validate(&bytes);
        }
    }
}
