use chrono::Timelike;
use chrono::{Datelike, TimeZone, Utc};
use chrono_tz::{Europe::Stockholm, Tz};
use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub struct DateTime(chrono::DateTime<Tz>);

static DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S%:z";

impl DateTime {
    pub fn ymd_hms(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32) -> Self {
        Self(
            Stockholm
                .ymd(year, month, day)
                .and_hms_milli(hour, minute, second, 0),
        )
    }

    pub fn ymd(year: i32, month: u32, day: u32) -> Self {
        Self::ymd_hms(year, month, day, 0, 0, 0)
    }

    pub fn and_hms(&self, hour: u32, minute: u32, second: u32) -> Self {
        Self(self.0.date().and_hms(hour, minute, second))
    }

    pub fn year(&self) -> i32 {
        self.0.year()
    }

    pub fn month(&self) -> u32 {
        self.0.month()
    }

    pub fn date(&self) -> chrono::NaiveDate {
        self.0.date().naive_local()
    }

    pub fn utc(&self) -> chrono::DateTime<chrono::Utc> {
        self.0.with_timezone(&chrono::Utc)
    }

    /// Returns the start of the day in the month before.
    pub fn prev_month_start(&self) -> Self {
        let (mut year, mut month) = (self.year(), self.month());

        if month == 1 {
            month = 12;
            year -= 1;
        } else {
            month -= 1;
        }

        Self::ymd(year, month, 1)
    }

    pub fn now() -> Self {
        Self(Stockholm.from_utc_datetime(&Utc::now().naive_utc()))
    }

    pub fn end_of_month(&self) -> DateTime {
        let date = self.0.date();

        let mut day = 31;
        let last_day_in_month = loop {
            if let Some(date) = date.with_day(day) {
                break date;
            }
            day -= 1;
        };

        Self(last_day_in_month.and_hms_milli(23, 59, 59, 999))
    }

    pub fn beginning_of_month(&self) -> DateTime {
        let new_date = self.0.date().with_day(1).unwrap();
        Self(new_date.and_hms_milli(0, 0, 0, 0))
    }
}

impl std::ops::Add<chrono::Duration> for DateTime {
    type Output = Self;

    fn add(self, rhs: chrono::Duration) -> Self::Output {
        Self(self.0.add(rhs))
    }
}

impl std::ops::Add<std::time::Duration> for DateTime {
    type Output = Self;

    fn add(self, rhs: std::time::Duration) -> Self::Output {
        self + chrono::Duration::from_std(rhs).expect("Duration to big")
    }
}

impl std::ops::Sub<std::time::Duration> for DateTime {
    type Output = Self;

    fn sub(self, rhs: std::time::Duration) -> Self::Output {
        self - chrono::Duration::from_std(rhs).expect("Duration to big")
    }
}

impl std::ops::Sub<chrono::Duration> for DateTime {
    type Output = Self;

    fn sub(self, rhs: chrono::Duration) -> Self::Output {
        Self(self.0.sub(rhs))
    }
}

impl fmt::Display for DateTime {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0.format(DATE_TIME_FORMAT))
    }
}

impl From<chrono::NaiveDateTime> for DateTime {
    fn from(dt: chrono::NaiveDateTime) -> Self {
        Self(Stockholm.from_local_datetime(&dt).unwrap())
    }
}

impl Serialize for DateTime {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let s = self.0.format(DATE_TIME_FORMAT).to_string();
        serializer.serialize_str(&s)
    }
}

struct Visitor;

impl<'de> serde::de::Visitor<'de> for Visitor {
    type Value = DateTime;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str(r#"a datetime string on the form: "2013-12-30 20:59:59+01:00""#)
    }

    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        let mut res = parse_from_date_time_format(s);

        if res.is_err() {
            res = parse_from_date_format(s);
        }

        if res.is_err() {
            res = parse_from_naive_date_time_format(s);
        }

        return res;
    }
}

fn parse_from_date_time_format<E>(s: &str) -> Result<DateTime, E>
where
    E: serde::de::Error,
{
    chrono::DateTime::parse_from_str(s, DATE_TIME_FORMAT)
        .map_err(|err| serde::de::Error::custom(format!("Invalid DateTime `{}`: {}", s, err)))
        .map(|utc_dt| utc_dt.with_timezone(&Stockholm))
        .map(|dt| DateTime(dt))
}

fn parse_from_date_format<E>(s: &str) -> Result<DateTime, E>
where
    E: serde::de::Error,
{
    let plain_format = "%Y-%m-%dT%H:%M:%S";
    chrono::NaiveDateTime::parse_from_str(s, plain_format)
        .map_err(|err| serde::de::Error::custom(format!("Invalid DateTime `{}`: {}", s, err)))
        .map(|naive_dt| Stockholm.from_local_datetime(&naive_dt).unwrap())
        .map(|dt| DateTime(dt))
}

fn parse_from_naive_date_time_format<E>(s: &str) -> Result<DateTime, E>
where
    E: serde::de::Error,
{
    let plain_format = "%Y-%m-%dT%H:%M:%S%.6f";
    chrono::NaiveDateTime::parse_from_str(s, plain_format)
        .map_err(|err| serde::de::Error::custom(format!("Invalid DateTime `{}`: {}", s, err)))
        .map(|naive_dt| {
            Stockholm
                .from_local_datetime(&naive_dt)
                .unwrap()
                .with_nanosecond(0)
                .unwrap()
        })
        .map(|dt| DateTime(dt))
}

impl<'de> Deserialize<'de> for DateTime {
    fn deserialize<D>(deserializer: D) -> Result<DateTime, D::Error>
    where
        D: serde::de::Deserializer<'de>,
    {
        deserializer.deserialize_str(Visitor)
    }
}

#[cfg(test)]
mod tests {

    use super::*;
    use serde_json as js;

    #[test]
    fn serialize_date_time() {
        assert_eq!(
            js::to_string(&DateTime::ymd_hms(2019, 01, 23, 12, 30, 0)).expect("Serializing"),
            r#""2019-01-23 12:30:00+01:00""#,
        );
        assert_eq!(
            js::to_string(&DateTime::ymd_hms(2019, 12, 31, 23, 59, 59)).expect("Serializing"),
            r#""2019-12-31 23:59:59+01:00""#,
        );
    }

    #[test]
    fn deserialize_date_time() {
        // assert_eq!(
        //     js::from_str::<DateTime>(r#""2019-01-07 15:41:26+01:00""#).expect("deserialize"),
        //     DateTime::ymd_hms(2019, 01, 07, 15, 41, 26),
        // );
        assert_eq!(
            js::from_str::<DateTime>(r#""2019-02-26 23:59:59+01:00""#).expect("deserialize"),
            DateTime::ymd_hms(2019, 02, 26, 23, 59, 59),
        );
        assert_eq!(
            js::from_str::<DateTime>(r#""2019-02-27 00:00:00+00:00""#).expect("deserialize"),
            DateTime::ymd_hms(2019, 02, 27, 1, 0, 0),
        );
        assert_eq!(
            js::from_str::<DateTime>(r#""2019-05-30 13:40:13+02:00""#).expect("deserialize"),
            DateTime::ymd_hms(2019, 05, 30, 13, 40, 13),
        );
        assert_eq!(
            js::from_str::<DateTime>(r#""2019-05-30T00:00:00""#).expect("deserialize"),
            DateTime::ymd_hms(2019, 05, 30, 00, 00, 00),
        );
        assert_eq!(
            js::from_str::<DateTime>(r#""2019-11-08T10:46:29.700928""#).expect("deserialize"),
            DateTime::ymd_hms(2019, 11, 08, 10, 46, 29),
        );
    }
}