use anyhow::{Result, anyhow, bail};
use chrono::{DateTime, Local, NaiveDateTime, TimeZone as _};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum XTimeStrFormat {
    YMDHMZ1, // "%Y%m%d%H%M%S"
    YMDHMZ2, // "%Y-%m-%d %H:%M:%S"
    YMDHMZ3, // "%Y-%m-%dT%H:%M:%S"
    Custom(String),
}

impl XTimeStrFormat {
    pub fn format_str(&self) -> &str {
        match self {
            XTimeStrFormat::YMDHMZ2 => "%Y-%m-%d %H:%M:%S",
            XTimeStrFormat::YMDHMZ3 => "%Y-%m-%dT%H:%M:%S",
            XTimeStrFormat::YMDHMZ1 => "%Y%m%d%H%M%S",
            XTimeStrFormat::Custom(s) => s.as_str(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct XTime {
    inner: DateTime<Local>,
}

impl XTime {
    pub fn from_now() -> Self {
        let inner = Local::now();
        Self { inner }
    }
    pub fn from_timestamp_auto(timestamp: impl TryInto<i64>) -> Result<Self> {
        let timestamp = timestamp.try_into().map_err(|_e| anyhow!("Covert to i64 fail"))?;
        if timestamp > 9999999999 {
            Self::from_timestamp_ms(timestamp)
        } else {
            Self::from_timestamp_s(timestamp)
        }
    }
    pub fn from_timestamp_s(timestamp_secs: impl TryInto<i64>) -> Result<Self> {
        let timestamp_secs = timestamp_secs.try_into().map_err(|_e| anyhow!("Covert to i64 fail"))?;
        let inner = match Local.timestamp_opt(timestamp_secs, 0) {
            chrono::offset::LocalResult::Single(dt) => dt,
            _ => bail!("timestamp_opt fail"),
        };
        Ok(Self { inner })
    }
    pub fn from_timestamp_s_nocheck(timestamp_secs: impl TryInto<i64>) -> Self {
        Self::from_timestamp_s(timestamp_secs).unwrap()
    }
    pub fn from_timestamp_ms(timestamp_millis: impl TryInto<i64>) -> Result<Self> {
        let timestamp_millis = timestamp_millis
            .try_into()
            .map_err(|_e| anyhow!("Covert to i64 fail"))?;
        let inner = match Local.timestamp_millis_opt(timestamp_millis) {
            chrono::offset::LocalResult::Single(dt) => dt,
            _ => bail!("timestamp_millis_opt fail"),
        };
        Ok(Self { inner })
    }
    //
    pub fn from_timestr(time_str: &str, time_format: XTimeStrFormat) -> Result<Self> {
        let naive = NaiveDateTime::parse_from_str(time_str, time_format.format_str())?;

        let inner = match Local.from_local_datetime(&naive) {
            chrono::offset::LocalResult::Single(dt) => dt,
            chrono::offset::LocalResult::Ambiguous(_, _) => anyhow::bail!("Ambiguous"),
            chrono::offset::LocalResult::None => anyhow::bail!("Invalid LocalTime setting in system"),
        };
        Ok(Self { inner })
    }

    pub fn from_timestamp_ms_nocheck(timestamp_millis: impl TryInto<i64>) -> Self {
        Self::from_timestamp_ms(timestamp_millis).unwrap()
    }

    pub fn format_timestr(&self, time_format: XTimeStrFormat) -> String {
        self.inner.format(time_format.format_str()).to_string()
    }
    pub fn as_timestamp_ms(&self) -> u64 {
        self.inner.timestamp_millis() as _
    }
    pub fn as_timestamp_s(&self) -> u64 {
        self.inner.timestamp() as _
    }
    pub fn after_hours(&self, hours: i64) -> Self {
        let inner = self.inner + chrono::Duration::hours(hours);
        Self { inner }
    }
    pub fn after_minutes(&self, minutes: i64) -> Self {
        let inner = self.inner + chrono::Duration::minutes(minutes);
        Self { inner }
    }
    pub fn after_seconds(&self, seconds: i64) -> Self {
        let inner = self.inner + chrono::Duration::seconds(seconds);
        Self { inner }
    }
    pub fn after_millis(&self, millis: i64) -> Self {
        let inner = self.inner + chrono::Duration::milliseconds(millis);
        Self { inner }
    }

    pub fn before_hours(&self, hours: i64) -> Self {
        let inner = self.inner - chrono::Duration::hours(hours);
        Self { inner }
    }
    pub fn before_minutes(&self, minutes: i64) -> Self {
        let inner = self.inner - chrono::Duration::minutes(minutes);
        Self { inner }
    }
    pub fn before_seconds(&self, seconds: i64) -> Self {
        let inner = self.inner - chrono::Duration::seconds(seconds);
        Self { inner }
    }
    pub fn before_millis(&self, millis: i64) -> Self {
        let inner = self.inner - chrono::Duration::milliseconds(millis);
        Self { inner }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_timestamp_ms_to_local_time() {
        let timestamp_ms = 1699598631608i64;
        let xtime = XTime::from_timestamp_ms_nocheck(timestamp_ms);

        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ1).to_string(),
            "20231110144351"
        );
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 14:43:51"
        );
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ3).to_string(),
            "2023-11-10T14:43:51"
        );
        assert_eq!(xtime.as_timestamp_ms(), timestamp_ms as _);
        assert_eq!(XTime::from_timestamp_auto(timestamp_ms).unwrap(), xtime);
        // test after
        assert_eq!(
            xtime.after_hours(1).format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 15:43:51"
        );
        assert_eq!(
            xtime.after_minutes(30).format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 15:13:51"
        );
        assert_eq!(
            xtime.after_seconds(90).format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 14:45:21"
        );
        // test before
        assert_eq!(
            xtime.before_hours(1).format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 13:43:51"
        );
        assert_eq!(
            xtime.before_minutes(30).format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 14:13:51"
        );
        assert_eq!(
            xtime.before_seconds(90).format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 14:42:21"
        );
    }

    #[test]
    fn test_timestamp_s_to_local_time() {
        let timestamp_s = 1699598631i64;

        let xtime = XTime::from_timestamp_s(timestamp_s).unwrap();

        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ1).to_string(),
            "20231110144351"
        );
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 14:43:51"
        );
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ3).to_string(),
            "2023-11-10T14:43:51"
        );
        assert_eq!(xtime.as_timestamp_s(), timestamp_s as _);
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::Custom("%Y-%m-%d/%H%M%S/996_%H%M%S%.3f.ts".to_string())),
            "2023-11-10/144351/996_144351.000.ts"
        );
        assert_eq!(XTime::from_timestamp_auto(timestamp_s).unwrap(), xtime);
    }

    #[test]
    fn test_case_xtime_from_now() {
        let xtime = XTime::from_now();
        println!("xtime now: {:?}", xtime.format_timestr(XTimeStrFormat::YMDHMZ2));
    }

    #[test]
    fn test_case_from_str() {
        let xtime = XTime::from_timestr("2023-11-10 14:43:51", XTimeStrFormat::YMDHMZ2).unwrap();
        assert_eq!(
            xtime.format_timestr(XTimeStrFormat::YMDHMZ2).to_string(),
            "2023-11-10 14:43:51"
        );

        assert_eq!(
            xtime,
            XTime::from_timestr("20231110144351", XTimeStrFormat::YMDHMZ1).unwrap()
        );
    }
}
