use std::time::Duration;

use chrono::{Datelike, Duration as ChronoDuration, TimeZone, Utc};

use serde::{Deserialize, Serialize};

use crate::envelope::current_time;

#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum Cycle {
    Week,
    Month,
}

impl Cycle {
    pub fn start(&self) -> Duration {
        let now = current_time();
        match self {
            Cycle::Week => beginning_of_week(now),
            Cycle::Month => beginning_of_month(now),
        }
    }

    pub fn end(&self) -> Duration {
        let now = current_time();
        match self {
            Cycle::Week => end_of_week(now),
            Cycle::Month => end_of_month(now),
        }
    }
}

pub fn beginning_of_week(current_time: Duration) -> Duration {
    let current_date = Utc.timestamp_opt(current_time.as_secs() as i64, 0).unwrap();
    let current_weekday = current_date.weekday().num_days_from_monday();
    let beginning_of_week_date = current_date - ChronoDuration::days(current_weekday as i64);
    let beginning_of_week_datetime = beginning_of_week_date
        .date_naive()
        .and_hms_opt(0, 0, 0)
        .unwrap();
    Duration::from_secs(beginning_of_week_datetime.timestamp() as u64)
}

pub fn beginning_of_month(current_time: Duration) -> Duration {
    let current_date = Utc.timestamp_opt(current_time.as_secs() as i64, 0).unwrap();
    let beginning_of_month_date = current_date.with_day(1).unwrap();
    let beginning_of_month_datetime = beginning_of_month_date
        .date_naive()
        .and_hms_opt(0, 0, 0)
        .unwrap();
    Duration::from_secs(beginning_of_month_datetime.timestamp() as u64)
}

pub fn end_of_month(current_time: Duration) -> Duration {
    let current_date = Utc.timestamp_opt(current_time.as_secs() as i64, 0).unwrap();
    let next_month = if current_date.month() == 12 {
        current_date
            .with_month(1)
            .unwrap()
            .with_year(current_date.year() + 1)
            .unwrap()
    } else {
        current_date.with_month(current_date.month() + 1).unwrap()
    };
    let end_of_month_date = next_month.with_day(1).unwrap();
    let end_of_month_datetime = end_of_month_date.date_naive().and_hms_opt(0, 0, 0).unwrap();
    Duration::from_secs(end_of_month_datetime.timestamp() as u64)
}

pub fn end_of_week(time: Duration) -> Duration {
    let beginning = beginning_of_week(time);
    beginning + Duration::from_secs(86400 * 7)
}