use chrono::Duration;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;

#[derive(Debug, Clone, PartialEq)]
pub struct DurationFormatter(Duration);

impl DurationFormatter {
    pub fn new(duration: Duration) -> Self {
        Self(duration)
    }
}

impl From<Duration> for DurationFormatter {
    fn from(duration: Duration) -> Self {
        Self(duration)
    }
}

impl Display for DurationFormatter {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        let seconds = self.0.num_seconds() % 60;
        let minutes = self.0.num_minutes() % 60;
        let hours = self.0.num_hours() % 24;
        let days = self.0.num_days();
        let mut has_written = false;

        if days > 0 {
            if days == 1 {
                write!(f, "1 day")?;
            } else {
                write!(f, "{days} days")?;
            }
            has_written = true;
        }

        if hours > 0 {
            if has_written {
                write!(f, ", ")?;
            }
            if hours == 1 {
                write!(f, "1 hour")?;
            } else {
                write!(f, "{hours} hours")?;
            }
            has_written = true;
        }

        if minutes > 0 {
            if has_written {
                write!(f, ", ")?;
            }
            if minutes == 1 {
                write!(f, "1 minute")?;
            } else {
                write!(f, "{minutes} minutes")?;
            }
            has_written = true;
        }

        if seconds > 0 {
            if has_written {
                write!(f, ", ")?;
            }
            if seconds == 1 {
                write!(f, "1 second")?;
            } else {
                write!(f, "{seconds} seconds")?;
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod test_from {
    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn it_should_convert_duration_to_duration_formatter() {
        let duration = DurationFormatter::from(Duration::minutes(1) + Duration::seconds(19));
        let other_duration = DurationFormatter::new(Duration::seconds(79));

        assert_eq!(duration, other_duration);
    }
}

#[cfg(test)]
mod test_fmt {
    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn it_should_format_duration_with_days() {
        let duration = Duration::days(1);
        let formatter = DurationFormatter::new(duration);
        assert_eq!(formatter.to_string(), "1 day");
    }

    #[test]
    fn it_should_format_big_mix_of_values() {
        let duration =
            Duration::days(456) + Duration::hours(2) + Duration::minutes(3) + Duration::seconds(4);
        let formatter = DurationFormatter::new(duration);
        assert_eq!(
            formatter.to_string(),
            "456 days, 2 hours, 3 minutes, 4 seconds"
        );
    }

    #[test]
    fn it_should_format_big_mix_of_singular_values() {
        let duration =
            Duration::days(1) + Duration::hours(1) + Duration::minutes(1) + Duration::seconds(1);
        let formatter = DurationFormatter::new(duration);
        assert_eq!(formatter.to_string(), "1 day, 1 hour, 1 minute, 1 second");
    }

    #[test]
    fn it_should_format_minutes_and_seconds() {
        let duration = Duration::minutes(13) + Duration::seconds(4);
        let formatter = DurationFormatter::new(duration);
        assert_eq!(formatter.to_string(), "13 minutes, 4 seconds");
    }

    #[test]
    fn it_should_format_seconds() {
        let duration = Duration::seconds(4);
        let formatter = DurationFormatter::new(duration);
        assert_eq!(formatter.to_string(), "4 seconds");
    }

    #[test]
    fn it_should_format_hours() {
        let duration = Duration::minutes(60 * 2);
        let formatter = DurationFormatter::new(duration);
        assert_eq!(formatter.to_string(), "2 hours");
    }
}
