use crate::types::validation_errors::ValidationError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::str::FromStr;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExpirationDate {
    month: u8,
    year: u16, // with 4 digits of year (ex: 2024)
}

impl FromStr for ExpirationDate {
    type Err = ValidationError;
    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let cleaned = value.replace(['/', ' ', '-'], "");

        if cleaned.len() != 6 {
            return Err(ValidationError::InvalidFormat {
                field: "expiration_date".to_string(),
                message: format!(
                    "expiration date should have 6 digits (MMYYYY), received {} ",
                    cleaned.len()
                ),
            });
        }
        let month: u8 = cleaned[0..2]
            .parse()
            .map_err(|_| ValidationError::InvalidFormat {
                field: "expiration_date_month".to_string(),
                message: "expiration date month should have 2 digits".to_string(),
            })?;
        let year: u16 = cleaned[2..6]
            .parse()
            .map_err(|_| ValidationError::InvalidFormat {
                field: "expiration_date_year".to_string(),
                message: "expiration date year should have 4 digits".to_string(),
            })?;

        Self::new(month, year)
    }
}
impl ExpirationDate {
    pub fn new(month: u8, year: u16) -> Result<Self, ValidationError> {
        if !(1..=12).contains(&month) {
            return Err(ValidationError::InvalidFormat {
                field: "expiration_date_month".to_string(),
                message: format!(
                    "expiration date month should be between 1 and 12, received {} ",
                    month
                ),
            });
        }

        if !(2000..=2099).contains(&year) {
            return Err(ValidationError::InvalidFormat {
                field: "expiration_date_year".to_string(),
                message: format!(
                    "expiration date year should be between 2000 and 2099, received {} ",
                    year
                ),
            });
        }

        let expiration = Self { month, year };

        if expiration.is_expired() {
            return Err(ValidationError::InvalidFormat {
                field: "expiration_date".to_string(),
                message: format!(
                    "expiration date should be in future, received {} ",
                    expiration
                ),
            });
        }

        Ok(expiration)
    }

    pub fn month(&self) -> u8 {
        self.month
    }

    pub fn year(&self) -> u16 {
        self.year
    }

    pub fn full_year(&self) -> u16 {
        self.year
    }

    pub fn is_expired(&self) -> bool {
        use chrono::Datelike;
        let now = chrono::Utc::now();
        let current_year = now.year() as u16;
        let current_month = now.month() as u8;
        self.year < current_year || (self.year == current_year && self.month < current_month)
    }

    pub fn format_slash(&self) -> String {
        format!("{:02}/{:04}", self.month, self.year)
    }

    pub fn mm_yyyy(&self) -> String {
        format!("{:02}{:04}", self.month, self.year)
    }
}

impl fmt::Display for ExpirationDate {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:02}/{:04}", self.month, self.year)
    }
}

impl Serialize for ExpirationDate {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.mm_yyyy())
    }
}

impl<'de> Deserialize<'de> for ExpirationDate {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        ExpirationDate::from_str(&s).map_err(serde::de::Error::custom)
    }
}

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

    #[test]
    fn test_new_valid_expiration_date() {
        let result = ExpirationDate::new(12, 2030);
        assert!(result.is_ok());
        let expiration = result.unwrap();
        assert_eq!(expiration.month(), 12);
        assert_eq!(expiration.year(), 2030);
    }

    #[test]
    fn test_new_invalid_month_zero() {
        let result = ExpirationDate::new(0, 2025);
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ValidationError::InvalidFormat { .. }
        ));
    }

    #[test]
    fn test_new_invalid_month_too_high() {
        let result = ExpirationDate::new(13, 2025);
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ValidationError::InvalidFormat { .. }
        ));
    }

    #[test]
    fn test_new_invalid_year_too_low() {
        let result = ExpirationDate::new(12, 1999);
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ValidationError::InvalidFormat { .. }
        ));
    }

    #[test]
    fn test_new_invalid_year_too_high() {
        let result = ExpirationDate::new(12, 2100);
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ValidationError::InvalidFormat { .. }
        ));
    }

    #[test]
    fn test_new_expired_card() {
        let result = ExpirationDate::new(1, 2020);
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ValidationError::InvalidFormat { .. }
        ));
    }

    #[test]
    fn test_from_str_valid_with_slash() {
        let result = ExpirationDate::from_str("12/2030");
        assert!(result.is_ok());
        let expiration = result.unwrap();
        assert_eq!(expiration.month(), 12);
        assert_eq!(expiration.year(), 2030);
    }

    #[test]
    fn test_from_str_valid_with_dash() {
        let result = ExpirationDate::from_str("06-2028");
        assert!(result.is_ok());
        let expiration = result.unwrap();
        assert_eq!(expiration.month(), 6);
        assert_eq!(expiration.year(), 2028);
    }

    #[test]
    fn test_from_str_valid_with_space() {
        let result = ExpirationDate::from_str("03 2027");
        assert!(result.is_ok());
        let expiration = result.unwrap();
        assert_eq!(expiration.month(), 3);
        assert_eq!(expiration.year(), 2027);
    }

    #[test]
    fn test_from_str_valid_no_separator() {
        let result = ExpirationDate::from_str("052029");
        assert!(result.is_ok());
        let expiration = result.unwrap();
        assert_eq!(expiration.month(), 5);
        assert_eq!(expiration.year(), 2029);
    }

    #[test]
    fn test_from_str_invalid_length() {
        let result = ExpirationDate::from_str("123");
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ValidationError::InvalidFormat { .. }
        ));
    }

    #[test]
    fn test_from_str_invalid_format_non_numeric() {
        let result = ExpirationDate::from_str("AB/CDEF");
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ValidationError::InvalidFormat { .. }
        ));
    }

    #[test]
    fn test_full_year() {
        let expiration = ExpirationDate::new(12, 2030).unwrap();
        assert_eq!(expiration.full_year(), 2030);
    }

    #[test]
    fn test_is_expired_past_year() {
        let now = chrono::Utc::now();
        let current_year = now.year() as u16;

        if current_year > 2000 {
            let past_year = current_year - 1;
            let expiration = ExpirationDate {
                month: 12,
                year: past_year,
            };
            assert!(expiration.is_expired());
        }
    }

    #[test]
    fn test_is_expired_current_year_past_month() {
        let now = chrono::Utc::now();
        let current_year = now.year() as u16;
        let current_month = now.month() as u8;

        if current_month > 1 {
            let past_month = current_month - 1;
            let expiration = ExpirationDate {
                month: past_month,
                year: current_year,
            };
            assert!(expiration.is_expired());
        }
    }

    #[test]
    fn test_is_not_expired_future_year() {
        let now = chrono::Utc::now();
        let current_year = now.year() as u16;
        let future_year = if current_year < 2099 {
            current_year + 1
        } else {
            2099
        };

        let expiration = ExpirationDate {
            month: 1,
            year: future_year,
        };
        assert!(!expiration.is_expired());
    }

    #[test]
    fn test_is_not_expired_current_year_future_month() {
        let now = chrono::Utc::now();
        let current_year = now.year() as u16;
        let current_month = now.month() as u8;

        if current_month < 12 {
            let future_month = current_month + 1;
            let expiration = ExpirationDate {
                month: future_month,
                year: current_year,
            };
            assert!(!expiration.is_expired());
        }
    }

    #[test]
    fn test_format_slash() {
        let now = chrono::Utc::now();
        let current_year = now.year() as u16;
        let current_month = now.month() as u8;

        // use the next month or the first month of next year when we arrived december
        let (month, year) = if current_month < 12 {
            (current_month + 1, current_year)
        } else {
            (1, current_year + 1)
        };

        let expiration = ExpirationDate::new(month, year);

        match expiration {
            Ok(exp) => assert_eq!(exp.format_slash(), format!("{:02}/{:04}", month, year)),
            Err(err) => {
                panic!("was expected success but receive error: {:?}", err);
            }
        }
    }

    #[test]
    fn test_display() {
        let expiration = ExpirationDate::new(9, 2028).unwrap();
        assert_eq!(format!("{}", expiration), "09/2028");
    }

    #[test]
    fn test_clone() {
        let expiration1 = ExpirationDate::new(6, 2027).unwrap();
        let expiration2 = expiration1.clone();
        assert_eq!(expiration1, expiration2);
    }

    #[test]
    fn test_serialize() {
        let expiration = ExpirationDate::new(11, 2026).unwrap();
        let serialized = serde_json::to_string(&expiration).unwrap();
        assert_eq!(serialized, "\"11/2026\"");
    }

    #[test]
    fn test_deserialize_valid() {
        let json = "\"12/2030\"";
        let expiration: ExpirationDate = serde_json::from_str(json).unwrap();
        assert_eq!(expiration.month(), 12);
        assert_eq!(expiration.year(), 2030);
    }

    #[test]
    fn test_deserialize_invalid() {
        let json = "\"13/2030\"";
        let result: Result<ExpirationDate, _> = serde_json::from_str(json);
        assert!(result.is_err());
    }

    #[test]
    fn test_serialize_deserialize_roundtrip() {
        let original = ExpirationDate::new(7, 2029).unwrap();
        let serialized = serde_json::to_string(&original).unwrap();
        let deserialized: ExpirationDate = serde_json::from_str(&serialized).unwrap();
        assert_eq!(original, deserialized);
    }

    #[test]
    fn test_all_valid_months() {
        for month in 1..=12 {
            let result = ExpirationDate::new(month, 2030);
            assert!(result.is_ok(), "Month {} should be valid", month);
        }
    }

    #[test]
    fn test_boundary_years_begin() {
        let exp = ExpirationDate::new(12, 1999);
        match exp {
            Ok(ex) => panic!("expected error but receive success: {:?}", ex),
            Err(err) => assert!(matches!(err, ValidationError::InvalidFormat { .. })),
        }
    }

    #[test]
    fn test_boundary_years_ends() {
        let exp = ExpirationDate::new(12, 2099);

        match exp {
            Ok(ex) => assert_eq!(ex.format_slash(), "12/2099"),
            Err(err) => panic!("expected success but receive error: {:?}", err),
        }
    }
}
