use jiff::{civil::Date, ToSpan, Unit};
use lazy_static::lazy_static;
use regex::Regex;

lazy_static! {
    static ref PARTIAL_DATE_REGEX: Regex = Regex::new(r"^[12]\d{3}(?:-(?:0\d|1[012]))?$").unwrap();
}

pub fn is_partial_date(string: &str) -> bool {
    PARTIAL_DATE_REGEX.is_match(string)
}

pub fn parse_partial_date(string: &str) -> Option<(Unit, Date)> {
    Some(match string.len() {
        4 => (
            Unit::Year,
            Date::new(string.parse::<i16>().ok()?, 1, 1).ok()?,
        ),
        7 => (
            Unit::Month,
            Date::new(
                string[..4].parse::<i16>().ok()?,
                string[5..].parse::<i8>().ok()?,
                1,
            )
            .ok()?,
        ),
        10 => (
            Unit::Day,
            Date::new(
                string[..4].parse::<i16>().ok()?,
                string[5..7].parse::<i8>().ok()?,
                string[8..].parse::<i8>().ok()?,
            )
            .ok()?,
        ),
        _ => return None,
    })
}

pub fn next_partial_date(unit: Unit, date: &Date) -> Date {
    match unit {
        Unit::Year => date.checked_add(1.year()).unwrap(),
        Unit::Month => date.checked_add(1.month()).unwrap(),
        Unit::Day => date.checked_add(1.day()).unwrap(),
        _ => unimplemented!(),
    }
}

pub fn format_partial_date(unit: Unit, date: &Date) -> String {
    match unit {
        Unit::Year => date.strftime("%Y").to_string(),
        Unit::Month => date.strftime("%Y-%m").to_string(),
        Unit::Day => date.strftime("%Y-%m-%d").to_string(),
        _ => unimplemented!(),
    }
}

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

    #[test]
    fn test_is_partial_date() {
        let tests = [
            ("2023", true),
            ("999", false),
            ("3412", false),
            ("2023-01", true),
            ("999-45", false),
            ("2024-45", false),
            ("1999-01", true),
        ];

        for (string, expected) in tests {
            assert_eq!(is_partial_date(string), expected, "{}", string);
        }
    }

    #[test]
    fn test_parse_partial_date() {
        let tests = [
            ("oucuoh", None),
            ("2023", Some((Unit::Year, date(2023, 1, 1)))),
            ("1998-13", None),
            ("1998-10", Some((Unit::Month, date(1998, 10, 1)))),
            ("1998-10-34", None),
            ("1998-10-22", Some((Unit::Day, date(1998, 10, 22)))),
        ];

        for (string, expected) in tests {
            assert_eq!(parse_partial_date(string), expected, "{}", string);
        }
    }
}