use chrono::{DateTime, Datelike, Duration, FixedOffset, NaiveDateTime, TimeZone, Weekday};

use crate::entity::recurrence_rule;
use crate::entity::sea_orm_active_enums::RecurrenceType;

/// 根据循环规则计算下一次触发时间
pub fn calculate_next_due(
    rule: &recurrence_rule::Model,
    current_due: DateTime<FixedOffset>,
) -> Option<DateTime<FixedOffset>> {
    let interval = rule.interval.max(1) as i64;
    let offset = current_due.offset().clone();

    match rule.r#type {
        RecurrenceType::Hourly => {
            // 每 N 小时
            Some(current_due + Duration::hours(interval))
        }
        RecurrenceType::Daily => {
            // 每 N 天
            Some(current_due + Duration::days(interval))
        }
        RecurrenceType::Weekly => {
            // 每 N 周，可选指定星期几
            if let Some(weekday) = rule.by_weekday {
                // 找到下一个指定的星期几
                let target_weekday = num_to_weekday(weekday);
                let next = current_due + Duration::weeks(interval);

                // 调整到目标星期几
                let current_weekday = next.weekday();
                let days_ahead = (target_weekday.num_days_from_monday() as i64
                    - current_weekday.num_days_from_monday() as i64
                    + 7)
                    % 7;

                if days_ahead == 0 {
                    // 同一天，保持当前时间
                    Some(next)
                } else {
                    Some(next + Duration::days(days_ahead))
                }
            } else {
                Some(current_due + Duration::weeks(interval))
            }
        }
        RecurrenceType::Monthly => {
            // 每 N 月，可选指定几号
            let target_day = rule.by_monthday.unwrap_or(current_due.day() as i32) as u32;

            let mut year = current_due.year();
            let mut month = current_due.month() as i32 + interval as i32;

            // 处理年份进位
            while month > 12 {
                month -= 12;
                year += 1;
            }

            // 处理月份天数不足的情况（如 2 月没有 31 号）
            let day = target_day.min(days_in_month(year, month as u32));

            let naive = NaiveDateTime::new(
                chrono::NaiveDate::from_ymd_opt(year, month as u32, day)?,
                current_due.time(),
            );
            Some(offset.from_local_datetime(&naive).single()?)
        }
        RecurrenceType::Yearly => {
            // 每 N 年
            let year = current_due.year() + interval as i32;
            let month = current_due.month();
            let day = current_due.day().min(days_in_month(year, month));

            let naive = NaiveDateTime::new(
                chrono::NaiveDate::from_ymd_opt(year, month, day)?,
                current_due.time(),
            );
            Some(offset.from_local_datetime(&naive).single()?)
        }
    }
}

fn num_to_weekday(num: i32) -> Weekday {
    match num % 7 {
        0 => Weekday::Sun,
        1 => Weekday::Mon,
        2 => Weekday::Tue,
        3 => Weekday::Wed,
        4 => Weekday::Thu,
        5 => Weekday::Fri,
        6 => Weekday::Sat,
        _ => Weekday::Mon,
    }
}

fn days_in_month(year: i32, month: u32) -> u32 {
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 => {
            if is_leap_year(year) {
                29
            } else {
                28
            }
        }
        _ => 30,
    }
}

fn is_leap_year(year: i32) -> bool {
    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::{NaiveDate, Timelike};
    use uuid::Uuid;

    fn make_rule(
        rule_type: RecurrenceType,
        interval: i32,
        by_weekday: Option<i32>,
        by_monthday: Option<i32>,
    ) -> recurrence_rule::Model {
        recurrence_rule::Model {
            id: Uuid::new_v4(),
            r#type: rule_type,
            interval,
            by_weekday,
            by_monthday,
            timezone: "Asia/Shanghai".to_string(),
            created_at: chrono::Utc::now().fixed_offset(),
            updated_at: chrono::Utc::now().fixed_offset(),
        }
    }

    fn make_datetime(year: i32, month: u32, day: u32, hour: u32, min: u32) -> DateTime<FixedOffset> {
        let naive = NaiveDate::from_ymd_opt(year, month, day)
            .unwrap()
            .and_hms_opt(hour, min, 0)
            .unwrap();
        FixedOffset::east_opt(0).unwrap().from_local_datetime(&naive).unwrap()
    }

    #[test]
    fn test_hourly() {
        let rule = make_rule(RecurrenceType::Hourly, 2, None, None);
        let current = make_datetime(2024, 1, 15, 10, 30);

        let next = calculate_next_due(&rule, current).unwrap();
        assert_eq!(next.hour(), 12);
        assert_eq!(next.minute(), 30);
    }

    #[test]
    fn test_daily() {
        let rule = make_rule(RecurrenceType::Daily, 3, None, None);
        let current = make_datetime(2024, 1, 15, 10, 30);

        let next = calculate_next_due(&rule, current).unwrap();
        assert_eq!(next.day(), 18);
    }

    #[test]
    fn test_weekly() {
        let rule = make_rule(RecurrenceType::Weekly, 1, None, None);
        let current = make_datetime(2024, 1, 15, 10, 30);

        let next = calculate_next_due(&rule, current).unwrap();
        assert_eq!(next.day(), 22);
    }

    #[test]
    fn test_monthly() {
        let rule = make_rule(RecurrenceType::Monthly, 1, None, Some(15));
        let current = make_datetime(2024, 1, 15, 10, 30);

        let next = calculate_next_due(&rule, current).unwrap();
        assert_eq!(next.month(), 2);
        assert_eq!(next.day(), 15);
    }

    #[test]
    fn test_monthly_overflow() {
        // 1月31号 -> 2月没有31号，应该是28号（或29号）
        let rule = make_rule(RecurrenceType::Monthly, 1, None, Some(31));
        let current = make_datetime(2024, 1, 31, 10, 30);

        let next = calculate_next_due(&rule, current).unwrap();
        assert_eq!(next.month(), 2);
        assert_eq!(next.day(), 29); // 2024 是闰年
    }

    #[test]
    fn test_yearly() {
        let rule = make_rule(RecurrenceType::Yearly, 1, None, None);
        let current = make_datetime(2024, 6, 15, 10, 30);

        let next = calculate_next_due(&rule, current).unwrap();
        assert_eq!(next.year(), 2025);
        assert_eq!(next.month(), 6);
        assert_eq!(next.day(), 15);
    }
}
