use alloc::string::{String, ToString};
use core::time::Duration;

/// Carries the state of the given timestamp
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelativePart {
    FewSecs,
    AboutAMinute,
    Minutes(u64),
    AboutAnHour,
    Hours(u64),
    AboutADay,
    Days(u64),
    AboutAMonth,
    Months(u64),
    AboutAYear,
    Years(u64),
}
impl RelativePart {
    /// Generates a relative-token from the given seconds
    pub fn from_secs(secs: u64) -> Self {
        match secs / 31536000 {
            0 => match secs / 2592000 {
                0 => match secs / 86400 {
                    0 => match secs / 3600 {
                        0 => match secs / 60 {
                            0 => RelativePart::FewSecs,
                            1 => RelativePart::AboutAMinute,
                            x => RelativePart::Minutes(x),
                        },
                        1 => RelativePart::AboutAnHour,
                        x => RelativePart::Hours(x),
                    },
                    1 => RelativePart::AboutADay,
                    x => RelativePart::Days(x),
                },
                1 => RelativePart::AboutAMonth,
                x => RelativePart::Months(x),
            },
            1 => RelativePart::AboutAYear,
            x => RelativePart::Years(x),
        }
    }

    /// Gets label for the relative-timestamp-token
    pub fn get_label(&self) -> (Option<u64>, &'static str) {
        match self {
            RelativePart::FewSecs => (None, "less than a minute"),
            RelativePart::AboutAMinute => (None, "about a minute"),
            RelativePart::Minutes(x) => (Some(*x), "## minutes"),
            RelativePart::AboutAnHour => (None, "about an hour"),
            RelativePart::Hours(x) => (Some(*x), "about ## hours"),
            RelativePart::AboutADay => (None, "a day"),
            RelativePart::Days(x) => (Some(*x), "## days"),
            RelativePart::AboutAMonth => (None, "about a month"),
            RelativePart::Months(x) => (Some(*x), "## months"),
            RelativePart::AboutAYear => (None, "about a year"),
            RelativePart::Years(x) => (Some(*x), "## years"),
        }
    }

    /// Replaces the values and generates the final human-readable string
    pub fn get_label_string(&self) -> String {
        let (val, label) = self.get_label();
        if let Some(x) = val {
            label.replace("##", &x.to_string())
        } else {
            label.into()
        }
    }
}

/// Conversion which converts Duration into a relative-part-token easily
impl From<Duration> for RelativePart {
    fn from(value: Duration) -> Self {
        Self::from_secs(value.as_secs())
    }
}

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

    #[test]
    fn should_say_less_than_a_minute() {
        assert_eq!(
            RelativePart::from_secs(0).get_label_string(),
            "less than a minute"
        );
        assert_eq!(
            RelativePart::from_secs(30).get_label_string(),
            "less than a minute"
        );
        assert_eq!(
            RelativePart::from_secs(59).get_label_string(),
            "less than a minute"
        );
    }

    #[test]
    fn should_say_about_a_minute() {
        assert_eq!(
            RelativePart::from_secs(60).get_label_string(),
            "about a minute"
        );
        assert_eq!(
            RelativePart::from_secs(119).get_label_string(),
            "about a minute"
        );
    }

    #[test]
    fn should_say_n_minutes() {
        assert_eq!(RelativePart::from_secs(120).get_label_string(), "2 minutes");
        assert_eq!(RelativePart::from_secs(300).get_label_string(), "5 minutes");
        assert_eq!(
            RelativePart::from_secs(3599).get_label_string(),
            "59 minutes"
        );
    }

    #[test]
    fn should_say_about_an_hour() {
        assert_eq!(
            RelativePart::from_secs(3600).get_label_string(),
            "about an hour"
        );
        assert_eq!(
            RelativePart::from_secs(7199).get_label_string(),
            "about an hour"
        );
    }

    #[test]
    fn should_say_about_n_hours() {
        assert_eq!(
            RelativePart::from_secs(7200).get_label_string(),
            "about 2 hours"
        );
        assert_eq!(
            RelativePart::from_secs(18000).get_label_string(),
            "about 5 hours"
        );
        assert_eq!(
            RelativePart::from_secs(86399).get_label_string(),
            "about 23 hours"
        );
    }

    #[test]
    fn should_say_a_day() {
        assert_eq!(RelativePart::from_secs(86400).get_label_string(), "a day");
        assert_eq!(RelativePart::from_secs(172799).get_label_string(), "a day");
    }

    #[test]
    fn should_says_n_days() {
        assert_eq!(RelativePart::from_secs(172800).get_label_string(), "2 days");
        assert_eq!(RelativePart::from_secs(604800).get_label_string(), "7 days");
        assert_eq!(
            RelativePart::from_secs(2591999).get_label_string(),
            "29 days"
        );
    }

    #[test]
    fn should_say_about_a_month() {
        assert_eq!(
            RelativePart::from_secs(2592000).get_label_string(),
            "about a month"
        );
        assert_eq!(
            RelativePart::from_secs(5183999).get_label_string(),
            "about a month"
        );
    }

    #[test]
    fn should_say_n_months() {
        assert_eq!(
            RelativePart::from_secs(5184000).get_label_string(),
            "2 months"
        );
        assert_eq!(
            RelativePart::from_secs(15552000).get_label_string(),
            "6 months"
        );
        assert_eq!(
            RelativePart::from_secs(31535999).get_label_string(),
            "12 months"
        );
    }

    #[test]
    fn should_say_about_a_year() {
        assert_eq!(
            RelativePart::from_secs(31536000).get_label_string(),
            "about a year"
        );
        assert_eq!(
            RelativePart::from_secs(63071999).get_label_string(),
            "about a year"
        );
    }

    #[test]
    fn test_years() {
        assert_eq!(
            RelativePart::from_secs(63072000).get_label_string(),
            "2 years"
        );
        assert_eq!(
            RelativePart::from_secs(157680000).get_label_string(),
            "5 years"
        );
        assert_eq!(
            RelativePart::from_secs(315360000).get_label_string(),
            "10 years"
        );
    }

    #[test]
    fn should_convert_from_duration() {
        let dur = core::time::Duration::ZERO;
        let rel: RelativePart = dur.into();
        assert_eq!(rel.get_label_string(), "less than a minute");

        let rel: RelativePart = core::time::Duration::from_secs(61).into();
        assert_eq!(rel.get_label_string(), "about a minute");
    }
}
