use crate::error::Error;
use chrono::prelude::*;
use chrono_tz::Tz;
use serde::de::{self, Deserialize, Deserializer};
use std::fmt::Display;
use std::str::FromStr;

pub(crate) const DATETIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
pub(crate) const DATE_FORMAT: &str = "%Y-%m-%d";
pub(crate) const TIME_FORMAT: &str = "%H:%M";

pub(crate) fn from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
    T: FromStr,
    T::Err: Display,
    D: Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    T::from_str(&s).map_err(de::Error::custom)
}

pub(crate) fn parse_date(value: &str, time_zone: Tz) -> Result<DateTime<Tz>, Error> {
    if value.contains(':') {
        let datetime = NaiveDateTime::parse_from_str(value, DATETIME_FORMAT)?;
        time_zone
            .from_local_datetime(&datetime)
            .single()
            .ok_or_else(|| Error::ParsingError("unable to parse datetime".into()))
    } else {
        let datetime = NaiveDate::parse_from_str(value, DATE_FORMAT)
            .map(|d| d.and_hms_opt(0, 0, 0).unwrap())?;
        time_zone
            .from_local_datetime(&datetime)
            .single()
            .ok_or_else(|| Error::ParsingError("unable to parse date".into()))
    }
}

pub(crate) fn parse_time(value: &str) -> Result<NaiveTime, Error> {
    NaiveTime::parse_from_str(value, TIME_FORMAT)
        .map_err(|_| Error::ParsingError("unable to parse time".into()))
}
