use crate::types::validation_errors::ValidationError;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum CardBrand {
    Visa,
    Mastercard,
    #[serde(rename = "American Express")]
    AmericanExpress,
    Elo,
    Hipercard,
    Diners,
    #[serde(untagged)]
    Other(String),
}

impl TryFrom<&str> for CardBrand {
    type Error = ValidationError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        let trimmed = value.trim();

        if trimmed.is_empty() {
            return Err(ValidationError::EmptyField("card_brand".to_string()));
        }

        let brand = match trimmed.to_lowercase().as_str() {
            "visa" => CardBrand::Visa,
            "mastercard" | "master card" | "master" => CardBrand::Mastercard,
            "american express" | "americanexpress" | "amex" => CardBrand::AmericanExpress,
            "elo" => CardBrand::Elo,
            "hipercard" | "hiper" => CardBrand::Hipercard,
            "diners" | "diners club" => CardBrand::Diners,
            _ => CardBrand::Other(trimmed.to_string()),
        };

        Ok(brand)
    }
}

impl TryFrom<String> for CardBrand {
    type Error = ValidationError;

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

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

    #[test]
    fn test_try_from_visa() {
        let brand = CardBrand::try_from("visa").unwrap();
        assert_eq!(brand, CardBrand::Visa);
    }

    #[test]
    fn test_try_from_visa_uppercase() {
        let brand = CardBrand::try_from("VISA").unwrap();
        assert_eq!(brand, CardBrand::Visa);
    }

    #[test]
    fn test_try_from_visa_mixed_case() {
        let brand = CardBrand::try_from("ViSa").unwrap();
        assert_eq!(brand, CardBrand::Visa);
    }

    #[test]
    fn test_try_from_mastercard() {
        let brand = CardBrand::try_from("mastercard").unwrap();
        assert_eq!(brand, CardBrand::Mastercard);
    }

    #[test]
    fn test_try_from_mastercard_with_space() {
        let brand = CardBrand::try_from("master card").unwrap();
        assert_eq!(brand, CardBrand::Mastercard);
    }

    #[test]
    fn test_try_from_master() {
        let brand = CardBrand::try_from("master").unwrap();
        assert_eq!(brand, CardBrand::Mastercard);
    }

    #[test]
    fn test_try_from_american_express() {
        let brand = CardBrand::try_from("american express").unwrap();
        assert_eq!(brand, CardBrand::AmericanExpress);
    }

    #[test]
    fn test_try_from_amex() {
        let brand = CardBrand::try_from("amex").unwrap();
        assert_eq!(brand, CardBrand::AmericanExpress);
    }

    #[test]
    fn test_try_from_americanexpress_no_space() {
        let brand = CardBrand::try_from("americanexpress").unwrap();
        assert_eq!(brand, CardBrand::AmericanExpress);
    }

    #[test]
    fn test_try_from_elo() {
        let brand = CardBrand::try_from("elo").unwrap();
        assert_eq!(brand, CardBrand::Elo);
    }

    #[test]
    fn test_try_from_elo_uppercase() {
        let brand = CardBrand::try_from("ELO").unwrap();
        assert_eq!(brand, CardBrand::Elo);
    }

    #[test]
    fn test_try_from_hipercard() {
        let brand = CardBrand::try_from("hipercard").unwrap();
        assert_eq!(brand, CardBrand::Hipercard);
    }

    #[test]
    fn test_try_from_hiper() {
        let brand = CardBrand::try_from("hiper").unwrap();
        assert_eq!(brand, CardBrand::Hipercard);
    }

    #[test]
    fn test_try_from_diners() {
        let brand = CardBrand::try_from("diners").unwrap();
        assert_eq!(brand, CardBrand::Diners);
    }

    #[test]
    fn test_try_from_diners_club() {
        let brand = CardBrand::try_from("diners club").unwrap();
        assert_eq!(brand, CardBrand::Diners);
    }

    #[test]
    fn test_try_from_unknown_brand() {
        let brand = CardBrand::try_from("discover").unwrap();
        assert_eq!(brand, CardBrand::Other("discover".to_string()));
    }

    #[test]
    fn test_try_from_custom_brand() {
        let brand = CardBrand::try_from("My Custom Brand").unwrap();
        assert_eq!(brand, CardBrand::Other("My Custom Brand".to_string()));
    }

    #[test]
    fn test_try_from_with_whitespace() {
        let brand = CardBrand::try_from("  visa  ").unwrap();
        assert_eq!(brand, CardBrand::Visa);
    }

    #[test]
    fn test_try_from_empty_string_fails() {
        let result = CardBrand::try_from("");
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ValidationError::EmptyField(_)
        ));
    }

    #[test]
    fn test_try_from_whitespace_only_fails() {
        let result = CardBrand::try_from("   ");
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ValidationError::EmptyField(_)
        ));
    }

    #[test]
    fn test_try_from_string_type() {
        let brand = CardBrand::try_from(String::from("visa")).unwrap();
        assert_eq!(brand, CardBrand::Visa);
    }

    #[test]
    fn test_try_from_string_mastercard() {
        let brand = CardBrand::try_from(String::from("mastercard")).unwrap();
        assert_eq!(brand, CardBrand::Mastercard);
    }

    #[test]
    fn test_try_from_string_empty_fails() {
        let result = CardBrand::try_from(String::from(""));
        assert!(result.is_err());
    }

    #[test]
    fn test_clone() {
        let brand1 = CardBrand::Visa;
        let brand2 = brand1.clone();
        assert_eq!(brand1, brand2);
    }

    #[test]
    fn test_debug() {
        let brand = CardBrand::Visa;
        let debug_str = format!("{:?}", brand);
        assert!(debug_str.contains("Visa"));
    }

    #[test]
    fn test_equality() {
        assert_eq!(CardBrand::Visa, CardBrand::Visa);
        assert_ne!(CardBrand::Visa, CardBrand::Mastercard);
    }

    #[test]
    fn test_other_equality() {
        let brand1 = CardBrand::Other("discover".to_string());
        let brand2 = CardBrand::Other("discover".to_string());
        let brand3 = CardBrand::Other("jcb".to_string());

        assert_eq!(brand1, brand2);
        assert_ne!(brand1, brand3);
    }
}
