use chrono::{DateTime, Datelike};
use serde::Serialize;

use crate::{Language, Timezone};

#[derive(Debug, Clone, Copy, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[repr(u8)]
#[allow(dead_code)]
pub enum Month {
    January = 0,
    February = 1,
    March = 2,
    April = 3,
    May = 4,
    June = 5,
    July = 6,
    August = 7,
    September = 8,
    October = 9,
    November = 10,
    December = 11,
}

impl Month {
    pub(crate) const fn translate(&self, language: Language) -> &'static str {
        match language {
            Language::En => match self {
                Self::January => "January",
                Self::February => "February",
                Self::March => "March",
                Self::April => "April",
                Self::May => "May",
                Self::June => "June",
                Self::July => "July",
                Self::August => "August",
                Self::September => "September",
                Self::October => "October",
                Self::November => "November",
                Self::December => "December",
            },
            Language::Sv => match self {
                Self::January => "januari",
                Self::February => "februari",
                Self::March => "mars",
                Self::April => "april",
                Self::May => "maj",
                Self::June => "juni",
                Self::July => "juli",
                Self::August => "augusti",
                Self::September => "september",
                Self::October => "oktober",
                Self::November => "november",
                Self::December => "december",
            },
        }
    }
}

/// An inclusive range of months
#[derive(Debug, Clone, Copy, Serialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Months(Month, Month, Timezone);

impl Months {
    pub const fn new(from: Month, to: Month, timezone: Timezone) -> Self {
        Self(from, to, timezone)
    }

    pub(crate) fn translate(&self, language: Language) -> String {
        format!(
            "{} - {}",
            self.0.translate(language),
            self.1.translate(language)
        )
    }

    pub(crate) fn matches<Tz: chrono::TimeZone>(&self, timestamp: DateTime<Tz>) -> bool {
        let start_month_index = self.0 as u32;
        let end_month_index = self.1 as u32;
        let month_index = &timestamp.month0();

        if start_month_index <= end_month_index {
            (start_month_index..=end_month_index).contains(month_index)
        } else {
            (start_month_index..=11).contains(month_index)
                || (0..=end_month_index).contains(month_index)
        }
    }
}

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

    use crate::Language;
    use crate::{Stockholm, Utc};

    #[test]
    fn months_matches_winter_period_start() {
        let months = Months::new(Month::November, Month::March, Stockholm);
        let timestamp = Stockholm.dt(2025, 11, 1, 0, 0, 0);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_matches_winter_period_end() {
        let months = Months::new(Month::November, Month::March, Stockholm);
        let timestamp = Stockholm.dt(2025, 3, 31, 23, 59, 59);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_matches_winter_period_middle() {
        let months = Months::new(Month::November, Month::March, Stockholm);
        let timestamp = Stockholm.dt(2025, 1, 15, 12, 0, 0);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_does_not_match_before_range() {
        let months = Months::new(Month::November, Month::March, Stockholm);
        let timestamp = Stockholm.dt(2025, 10, 31, 23, 59, 59);
        assert!(!months.matches(timestamp));
    }

    #[test]
    fn months_does_not_match_after_range() {
        let months = Months::new(Month::November, Month::March, Stockholm);
        let timestamp = Stockholm.dt(2025, 4, 1, 0, 0, 0);
        assert!(!months.matches(timestamp));
    }

    #[test]
    fn months_matches_summer_period() {
        let months = Months::new(Month::May, Month::September, Stockholm);
        let timestamp = Stockholm.dt(2025, 7, 15, 12, 0, 0);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_matches_single_month_range() {
        let months = Months::new(Month::June, Month::June, Stockholm);
        let timestamp = Stockholm.dt(2025, 6, 15, 12, 0, 0);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_matches_full_year() {
        let months = Months::new(Month::January, Month::December, Stockholm);
        let timestamp = Stockholm.dt(2025, 8, 15, 12, 0, 0);
        assert!(months.matches(timestamp));
    }

    // Tests using Utc
    #[test]
    fn month_translate_english() {
        assert_eq!(Month::January.translate(Language::En), "January");
        assert_eq!(Month::June.translate(Language::En), "June");
        assert_eq!(Month::December.translate(Language::En), "December");
    }

    #[test]
    fn month_translate_swedish() {
        assert_eq!(Month::January.translate(Language::Sv), "januari");
        assert_eq!(Month::June.translate(Language::Sv), "juni");
        assert_eq!(Month::December.translate(Language::Sv), "december");
    }

    #[test]
    fn months_translate_range_english() {
        let months = Months::new(Month::November, Month::March, Utc);
        assert_eq!(months.translate(Language::En), "November - March");
    }

    #[test]
    fn months_translate_range_swedish() {
        let months = Months::new(Month::November, Month::March, Utc);
        assert_eq!(months.translate(Language::Sv), "november - mars");
    }

    #[test]
    fn months_matches_winter_period_start_utc() {
        let months = Months::new(Month::November, Month::March, Stockholm);
        let timestamp = Utc.dt(2025, 11, 1, 0, 0, 0);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_matches_winter_period_end_utc() {
        let months = Months::new(Month::November, Month::March, Stockholm);
        let timestamp = Utc.dt(2025, 3, 31, 23, 59, 59);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_matches_winter_period_middle_utc() {
        let months = Months::new(Month::November, Month::March, Stockholm);
        let timestamp = Utc.dt(2025, 1, 15, 12, 0, 0);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_does_not_match_before_range_utc() {
        let months = Months::new(Month::November, Month::March, Stockholm);
        let timestamp = Utc.dt(2025, 10, 31, 23, 59, 59);
        assert!(!months.matches(timestamp));
    }

    #[test]
    fn months_does_not_match_after_range_utc() {
        let months = Months::new(Month::November, Month::March, Stockholm);
        let timestamp = Utc.dt(2025, 4, 1, 0, 0, 0);
        assert!(!months.matches(timestamp));
    }

    #[test]
    fn months_matches_summer_period_utc() {
        let months = Months::new(Month::May, Month::September, Stockholm);
        let timestamp = Utc.dt(2025, 7, 15, 12, 0, 0);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_matches_single_month_range_utc() {
        let months = Months::new(Month::June, Month::June, Stockholm);
        let timestamp = Utc.dt(2025, 6, 15, 12, 0, 0);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_does_not_match_outside_single_month_utc() {
        let months = Months::new(Month::June, Month::June, Stockholm);
        let timestamp = Utc.dt(2025, 7, 1, 0, 0, 0);
        assert!(!months.matches(timestamp));
    }

    #[test]
    fn months_matches_full_year_utc() {
        let months = Months::new(Month::January, Month::December, Stockholm);
        let timestamp = Utc.dt(2025, 8, 15, 12, 0, 0);
        assert!(months.matches(timestamp));
    }

    #[test]
    fn months_matches_year_wrap_december_to_february_utc() {
        let months = Months::new(Month::December, Month::February, Stockholm);

        // December should match
        assert!(months.matches(Utc.dt(2025, 12, 15, 12, 0, 0)));

        // January should match
        assert!(months.matches(Utc.dt(2025, 1, 15, 12, 0, 0)));

        // February should match
        assert!(months.matches(Utc.dt(2025, 2, 15, 12, 0, 0)));

        // November should not match
        assert!(!months.matches(Utc.dt(2025, 11, 15, 12, 0, 0)));

        // March should not match
        assert!(!months.matches(Utc.dt(2025, 3, 15, 12, 0, 0)));
    }

    #[test]
    fn months_boundary_at_midnight_utc() {
        let months = Months::new(Month::April, Month::October, Stockholm);

        // Last second of March should not match
        assert!(!months.matches(Utc.dt(2025, 3, 31, 23, 59, 59)));

        // First second of April should match
        assert!(months.matches(Utc.dt(2025, 4, 1, 0, 0, 0)));

        // Last second of October should match
        assert!(months.matches(Utc.dt(2025, 10, 31, 23, 59, 59)));

        // First second of November should not match
        assert!(!months.matches(Utc.dt(2025, 11, 1, 0, 0, 0)));
    }
}
