use regex::Regex;
use std::fmt::{Display, Formatter};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum EmailError {
    #[error("E-mail inválido: {0}")]
    InvalidEmail(String),
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[repr(transparent)]
#[serde(try_from = "String")]
#[serde(into = "String")]
pub struct Email(String);

impl Email {
    pub fn new(email: &str) -> Result<Self, EmailError> {
        if Self::is_valid(email) {
            Ok(Email(email.to_lowercase()))
        } else {
            Err(EmailError::InvalidEmail(email.to_string()))
        }
    }

    pub fn is_valid(email: &str) -> bool {
        match Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") {
            Ok(email_regex) => email_regex.is_match(email),
            Err(_) => false,
        }
    }

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

    pub fn domain(&self) -> &str {
        self.0
            .split('@')
            .nth(1)
            .expect("Not possible to get None here because email already validated before")
    }

    pub fn username(&self) -> &str {
        self.0
            .split('@')
            .nth(0)
            .expect("Not possible to get None here because email already validated before")
    }
}

impl TryFrom<&str> for Email {
    type Error = EmailError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Self::new(value)
    }
}

impl TryFrom<String> for Email {
    type Error = EmailError;
    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::new(&value)
    }
}

impl From<Email> for String {
    fn from(value: Email) -> Self {
        value.0
    }
}

impl Display for Email {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

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

    #[test]
    fn test_valid_email() {
        let email = Email::new("example@example.com");
        assert!(email.is_ok());

        let try_from_str = Email::try_from("example@example.com");
        assert!(try_from_str.is_ok());

        let try_from_string = Email::try_from("example@example.com".to_string());
        assert!(try_from_string.is_ok());
    }

    #[test]
    fn test_invalid_email() {
        let email = Email::new("invalid-email");
        assert!(email.is_err());

        let try_from_str = Email::try_from("invalid-email");
        assert!(try_from_str.is_err());

        let try_from_string = Email::try_from("invalid-email".to_string());
        assert!(try_from_string.is_err());
    }

    #[test]
    fn test_email_value() {
        let email = Email::new("Example@EXAMPLE.Com").unwrap();
        assert_eq!(email.as_str(), "example@example.com");

        let try_from_str = Email::try_from("Example@EXAMPLE.Com").unwrap();
        assert_eq!(try_from_str.as_str(), "example@example.com");

        let try_from_string = Email::try_from("Example@EXAMPLE.Com".to_string()).unwrap();
        assert_eq!(try_from_string.as_str(), "example@example.com");
    }

    #[test]
    fn test_email_domain() {
        let email = Email::new("Example@EXAMPLE.Com").unwrap();
        assert_eq!(email.domain(), "example.com");

        let try_from_str = Email::try_from("Example@EXAMPLE.Com").unwrap();
        assert_eq!(try_from_str.domain(), "example.com");

        let try_from_string = Email::try_from("Example@EXAMPLE.Com".to_string()).unwrap();
        assert_eq!(try_from_string.domain(), "example.com");
    }

    #[test]
    fn test_email_username() {
        let email = Email::new("Example@EXAMPLE.Com").unwrap();
        assert_eq!(email.username(), "example");

        let try_from_str = Email::try_from("Example@EXAMPLE.Com").unwrap();
        assert_eq!(try_from_str.username(), "example");

        let try_from_string = Email::try_from("Example@EXAMPLE.Com".to_string()).unwrap();
        assert_eq!(try_from_string.username(), "example");
    }
}
