// SPDX-FileCopyrightText: Politik im Blick developers
// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
//
// SPDX-License-Identifier: AGPL-3.0-or-later OR EUPL-1.2

use std::str::FromStr;

use snafu::{ResultExt as _, Snafu};

#[derive(
    Debug,
    Clone,
    PartialEq,
    Eq,
    Hash,
    derive_more::AsRef,
    derive_more::Display,
    derive_more::From,
    derive_more::Into,
    serde::Serialize,
    serde::Deserialize,
)]
pub struct EmailAddress(email_address::EmailAddress);

impl EmailAddress {
    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

#[derive(Debug, Snafu)]
pub enum TryFromEmailAddressError {
    #[snafu(display("Failed to parse e-mail address: {source}"))]
    Parse { source: email_address::Error },
}

impl FromStr for EmailAddress {
    type Err = TryFromEmailAddressError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let email_address: email_address::EmailAddress = s.parse().context(ParseSnafu)?;
        Ok(Self(email_address))
    }
}

impl PartialOrd for EmailAddress {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for EmailAddress {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.as_str().cmp(other.as_str())
    }
}

impl TryFrom<String> for EmailAddress {
    type Error = TryFromEmailAddressError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        value.parse()
    }
}

#[cfg(feature = "sea-orm")]
mod sea_orm_impls {
    use sea_orm::{
        ActiveValue, ColumnType, DbErr, IntoActiveValue, TryGetError, TryGetable, Value,
        sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr},
    };

    use super::EmailAddress;

    impl From<EmailAddress> for Value {
        fn from(value: EmailAddress) -> Self {
            value.as_str().into()
        }
    }

    impl TryGetable for EmailAddress {
        fn try_get_by<I: sea_orm::ColIdx>(
            res: &sea_orm::QueryResult,
            index: I,
        ) -> Result<Self, TryGetError> {
            let s = <String as TryGetable>::try_get_by(res, index)?;
            s.try_into().map_err(|e| {
                DbErr::TryIntoErr {
                    from: "String",
                    into: "EmailAddress",
                    source: Box::new(e),
                }
                .into()
            })
        }
    }

    impl ValueType for EmailAddress {
        fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
            let s = <String as ValueType>::try_from(v)?;
            s.try_into().map_err(|_| ValueTypeErr)
        }

        fn type_name() -> String {
            stringify!(String).to_owned()
        }

        fn array_type() -> ArrayType {
            <String as ValueType>::array_type()
        }

        fn column_type() -> ColumnType {
            <String as ValueType>::column_type()
        }
    }

    impl Nullable for EmailAddress {
        fn null() -> Value {
            Value::String(None)
        }
    }

    impl IntoActiveValue<EmailAddress> for EmailAddress {
        fn into_active_value(self) -> ActiveValue<EmailAddress> {
            ActiveValue::Set(self)
        }
    }
}

#[cfg(test)]
mod tests {
    use pretty_assertions::assert_eq;

    use super::EmailAddress;

    #[test]
    fn from_str() {
        assert_eq!(
            EmailAddress(
                "hello@example.com"
                    .parse()
                    .expect("value must be a parseable EmailAddress")
            ),
            "hello@example.com"
                .parse()
                .expect("value must be a parseable EmailAddress")
        );
    }
}

#[cfg(test)]
mod serde_tests {
    use pretty_assertions::assert_eq;
    use serde_json::json;

    use super::EmailAddress;

    #[test]
    fn serialize() {
        assert_eq!(
            json!(EmailAddress(
                "hello@example.com"
                    .parse()
                    .expect("value must be a parseable EmailAddress")
            )),
            json!("hello@example.com")
        );
    }

    #[test]
    fn deserialize_good() {
        let deserialized: EmailAddress = serde_json::from_value(json!("hello@example.com"))
            .expect("value must be deserializable as EmailAddress");
        assert_eq!(
            deserialized,
            EmailAddress(
                "hello@example.com"
                    .parse()
                    .expect("value must be a parseable EmailAddress")
            )
        );
    }

    #[test]
    fn deserialize_bad() {
        assert!(serde_json::from_value::<EmailAddress>(json!("xyzabcd")).is_err());
        assert!(serde_json::from_value::<EmailAddress>(json!(true)).is_err());
        assert!(serde_json::from_value::<EmailAddress>(json!(123)).is_err());
    }
}
