use std::any::type_name;

use bilrost::DecodeError;

use crate::core::zerocopy_ext::ZerocopyErr;

pub(crate) fn map_zerocopy_err<T>(err: ZerocopyErr) -> bilrost::DecodeError {
    let mut e = DecodeError::new(bilrost::DecodeErrorKind::InvalidValue);
    e.push(
        match err {
            ZerocopyErr::InvalidAlignment => {
                "source alignment does not match destination alignment"
            }
            ZerocopyErr::InvalidSize => "source size does not match destination size",
            ZerocopyErr::InvalidData => {
                "source bytes are not a valid value of the destination type"
            }
        },
        type_name::<T>(),
    );
    e
}

#[macro_export]
macro_rules! derive_zerocopy_encoding {
    (
        encode type ($ty:ty)
        with size ($size:expr)
        with empty ($empty:expr)
        $(with generics ($($impl_generics:tt)*))?
    ) => {
        const _:() = {
            use $crate::core::bilrost_util::zerocopy_encoding::map_zerocopy_err;
            use ::bilrost::encoding::{
                Wiretyped, WireType, ForOverwrite, GeneralGeneric, ValueEncoder,
                PlainBytes, encoded_len_varint, ValueDecoder, Capped, DecodeContext,
                EmptyState,
            };
            use ::bilrost::DecodeError;
            use ::bilrost::buf::ReverseBuf;
            use ::bytes::{BufMut, Buf};
            use ::zerocopy::{TryFromBytes, IntoBytes, Immutable, KnownLayout};

            #[allow(dead_code, reason="type assertion improves macro errors")]
            #[doc(hidden)]
            trait AssertIsZerocopy: IntoBytes + TryFromBytes + Immutable + KnownLayout {}

            #[doc(hidden)]
            impl$(<$($impl_generics)*>)? AssertIsZerocopy for $ty {}

            const WIRE_SIZE: usize = $size + encoded_len_varint($size as u64);

            impl$(<$($impl_generics)*>)? ForOverwrite<(), $ty> for () {
                #[inline]
                fn for_overwrite() -> $ty {
                    $empty
                }
            }

            impl$(<$($impl_generics)*>)? EmptyState<(), $ty> for () {
                #[inline]
                fn empty() -> $ty
                where
                    $ty: Sized,
                {
                    $empty
                }

                #[inline]
                fn is_empty(val: &$ty) -> bool {
                    *val == $empty
                }

                #[inline]
                fn clear(val: &mut $ty) {
                    *val = $empty;
                }
            }

            impl<const __G:u8 $(,$($impl_generics)*)?> Wiretyped<GeneralGeneric<__G>, $ty> for () {
                const WIRE_TYPE: WireType = WireType::LengthDelimited;
            }

            impl<const __G:u8 $(,$($impl_generics)*)?> ValueEncoder<GeneralGeneric<__G>, $ty> for () {
                #[inline]
                fn encode_value<B: BufMut + ?Sized>(value: &$ty, buf: &mut B) {
                    <() as ValueEncoder<PlainBytes, _>>::encode_value(&value.as_bytes(), buf)
                }

                #[inline]
                fn prepend_value<B: ReverseBuf + ?Sized>(value: &$ty, buf: &mut B) {
                    <() as ValueEncoder<PlainBytes, _>>::prepend_value(&value.as_bytes(), buf)
                }

                #[inline]
                fn value_encoded_len(value: &$ty) -> usize {
                    debug_assert_eq!(
                        <() as ValueEncoder<PlainBytes, _>>::value_encoded_len(&value.as_bytes()),
                        WIRE_SIZE,
                        concat!("Invalid size in derive_zerocopy_encoding for ", stringify!($ty))
                    );
                    WIRE_SIZE
                }

                #[inline]
                fn many_values_encoded_len<I>(values: I) -> usize
                where
                    I: ExactSizeIterator,
                    I::Item: std::ops::Deref<Target = $ty>,
                {
                    let many_size = WIRE_SIZE
                        .checked_mul(values.len())
                        .expect(concat!("Overflow in many_values_encoded_len for ", stringify!($ty)));
                    debug_assert_eq!(
                        values
                            .map(|val| Self::value_encoded_len(&val.as_bytes()))
                            .sum::<usize>(),
                        many_size,
                        concat!("Invalid size in derive_zerocopy_encoding for ", stringify!($ty))
                    );
                    many_size
                }
            }

            impl<const __G:u8 $(,$($impl_generics)*)?> ValueDecoder<GeneralGeneric<__G>, $ty> for () {
                fn decode_value<B: Buf + ?Sized>(
                    value: &mut $ty,
                    mut buf: Capped<B>,
                    _ctx: DecodeContext,
                ) -> Result<(), DecodeError> {
                    let buf = buf.take_length_delimited()?;
                    let mut bytes = [0u8; $size];
                    bytes.as_mut_slice().put(buf.take_all());
                    *value = <$ty>::try_read_from_bytes(&bytes)
                        .map_err(|e| map_zerocopy_err::<$ty>(e.into()))?;
                    Ok(())
                }
            }
        };
    };
}
