use chrono::{Datelike, NaiveDate};
use serde::{Deserialize, Serialize};
use std::{fmt, num::ParseIntError};
use thiserror::Error;
use typeshare::typeshare;

#[allow(dead_code)]
#[derive(Debug, Error, PartialEq)]
pub enum Error {
    #[error("Failed to parse integer {0}")]
    ParseNumberError(ParseIntError),

    #[error("Invalid date")]
    InvalidDate,
}

#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
#[typeshare]
pub enum Date {
    Year(i32),
    YearMonth(i32, u32),
    Date(chrono::NaiveDate),
}

impl fmt::Display for Date {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Date::Year(y) => write!(f, "{}", y),
            Date::YearMonth(y, m) => write!(f, "{}-{}", y, m),
            Date::Date(date) => write!(f, "{}-{}-{}", date.year(), date.month(), date.day()),
        }
    }
}

/*
impl TryFrom<&str> for Date {
    type Error = String;
    fn try_from(s: &str) -> Result<Date, Self::Error> {
        let date_parts = s.split("-").collect::<Vec<&str>>();

        if date_parts.len() >= 1 {
            let year = date_parts[0]
                .parse::<i32>()
                .map_err(|e| format!("{:?}", e))?;

            if date_parts.len() >= 2 {
                let month = date_parts[1]
                    .parse::<u32>()
                    .map_err(|e| format!("{:?}", e))?;

                if date_parts.len() >= 3 {
                    let day = date_parts[2]
                        .parse::<u32>()
                        .map_err(|e| format!("{:?}", e))?;
                    let date =
                        chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or("invalid date")?;
                    Ok(Date::Date(date))
                } else {
                    Ok(Date::YearMonth(year, month))
                }
            } else {
                Ok(Date::Year(year))
            }
        } else {
            return Err("no elements".to_owned());
        }
    }
}
*/

#[allow(dead_code)]
fn parse_numbers(s: &str) -> Result<Vec<i32>, Error> {
    s.split('-')
        .map(|s| s.parse::<i32>().map_err(Error::ParseNumberError))
        .collect::<Result<Vec<i32>, Error>>()
}

#[allow(dead_code)]
pub fn parse_date_field(s: &str) -> Result<Vec<Date>, Error> {
    let date_elements = s.split(',');
    let mut dates = Vec::new();

    let mut most_recent: Option<Date> = None;
    for element in date_elements {
        let fields = parse_numbers(element)?;

        let new_date = match fields.as_slice() {
            [] => panic!("all segments must have a field"),
            [v1] => match most_recent {
                Some(Date::Year(_)) => Date::Year(*v1),
                Some(Date::YearMonth(y, _)) => Date::YearMonth(y, *v1 as u32),
                Some(Date::Date(d)) => {
                    Date::Date(d.clone().with_day(*v1 as u32).ok_or(Error::InvalidDate)?)
                }

                None => Date::Year(*v1),
            },
            [v1, v2] => Date::YearMonth(*v1, *v2 as u32),
            [v1, v2, v3, ..] => {
                Date::Date(NaiveDate::from_ymd_opt(*v1, *v2 as u32, *v3 as u32).unwrap())
            }
        };
        dates.push(new_date.clone());
        most_recent = Some(new_date);
    }

    Ok(dates)
}

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

    #[test]
    fn it_parses_a_year() {
        assert_eq!(parse_date_field("1996"), Ok(vec![Date::Year(1996)]));
    }

    #[test]
    fn it_parses_a_month() {
        assert_eq!(
            parse_date_field("1996-12"),
            Ok(vec![Date::YearMonth(1996, 12)])
        );
    }

    #[test]
    fn it_parses_a_date() {
        assert_eq!(
            parse_date_field("1996-12-27"),
            Ok(vec![Date::Date(
                NaiveDate::from_ymd_opt(1996, 12, 27).unwrap()
            )])
        );
    }

    #[test]
    fn it_parses_date_continuation() {
        assert_eq!(
            parse_date_field("1996-12-27,28"),
            Ok(vec![
                Date::Date(NaiveDate::from_ymd_opt(1996, 12, 27).unwrap()),
                Date::Date(NaiveDate::from_ymd_opt(1996, 12, 28).unwrap())
            ])
        );
    }

    #[test]
    fn it_parses_date_crossing_year_boundary() {
        assert_eq!(
            parse_date_field("1996-12-27,28,1997-01-03,04"),
            Ok(vec![
                Date::Date(NaiveDate::from_ymd_opt(1996, 12, 27).unwrap()),
                Date::Date(NaiveDate::from_ymd_opt(1996, 12, 28).unwrap()),
                Date::Date(NaiveDate::from_ymd_opt(1997, 1, 3).unwrap()),
                Date::Date(NaiveDate::from_ymd_opt(1997, 1, 4).unwrap()),
            ])
        );
    }
}