use super::{Plaintext, PlaintextNullVariant};
use chrono::{DateTime, NaiveDate, Utc};
use rust_decimal::Decimal;

macro_rules! impl_from {
    ($ty:ty, $variant:ident) => {
        impl From<$ty> for Plaintext {
            fn from(value: $ty) -> Self {
                Plaintext::$variant(Some(value))
            }
        }
    };
}

impl<T> From<Option<T>> for Plaintext
where
    T: Into<Plaintext> + PlaintextNullVariant,
{
    fn from(value: Option<T>) -> Self {
        if let Some(value) = value {
            value.into()
        } else {
            T::null()
        }
    }
}

impl From<&str> for Plaintext {
    fn from(value: &str) -> Self {
        value.to_string().into()
    }
}

macro_rules! to_conversions_and_tests {
    (impls => { $(($($value:expr),*) => $ty:ty => $variant:ident),* } extra_tests => { $($test_tokens:tt)* }) => {
        // Implement the traits for all variants
        $(
            impl_from!($ty, $variant);
        )*

        // This method is used to throw a compile error when the from impls aren't implemented
        // for all variants of the plaintext type. The message is going to be a little weird but at
        // least it'll fail.
        #[allow(dead_code)]
        fn exhaustive_check(value: Plaintext) {
            match value {
                $(
                    Plaintext::$variant(_) => {},
                )*
            }
        }

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

            $(
                paste::paste! {
                    #[test]
                    fn [<test_to_ $variant:snake>]() {
                        $(
                            assert_eq!(
                                Plaintext::from($ty::from($value)),
                                Plaintext::$variant(Some($ty::from($value)))
                            );
                        )*
                    }

                    #[test]
                    fn [<test_to_ $variant:snake _option>]() {
                        $(
                            assert_eq!(
                                Plaintext::from(Some($ty::from($value))),
                                Plaintext::$variant(Some($ty::from($value)))
                            );
                        )*
                    }

                    #[test]
                    fn [<test_to_ $variant:snake _option_none>]() {
                        assert_eq!(
                            Plaintext::from(Option::<$ty>::None),
                            Plaintext::$variant(None)
                        );
                    }
                }
            )*

            $($test_tokens)*
        }


    }
}

to_conversions_and_tests! {
    impls => {
        (10_i64, -10_i64) => i64 => BigInt,
        (10_u64, 100_u64) => u64 => BigUInt,
        (10_i32, -10_i32) => i32 => Int,
        (10_i16, -10_i16) => i16 => SmallInt,
        (0., -1., 100.) => f64 => Float,
        (Decimal::new(10, 0), Decimal::new(-10, 0)) => Decimal => Decimal,
        (NaiveDate::from_ymd_opt(2020, 1, 1).expect("Expected date to create")) => NaiveDate => NaiveDate,
        (DateTime::<Utc>::from_timestamp(1000, 0).expect("Expected timestamp to create")) => DateTime::<Utc> => Timestamp,
        (true, false) => bool => Boolean,
        ("hello", "") => String => Text,
        (serde_json::json!({"hello": "world"}), serde_json::json!({})) => serde_json::Value => Json
    }

    extra_tests => {
        #[test]
        fn test_to_text_from_str() {
            assert_eq!(
                Plaintext::from("hello"),
                Plaintext::Text(Some("hello".to_string()))
            );

            assert_eq!(
                Plaintext::from(Some("hello")),
                Plaintext::Text(Some("hello".to_string()))
            );

            assert_eq!(
                Plaintext::from(Option::<&str>::None),
                Plaintext::Text(None)
            );
        }
    }
}
