use bincode::BorrowDecode;
#[cfg(feature = "std")]
use bincode::{Decode, Encode};
#[cfg(feature = "std")]
use core::fmt::{Debug, Display, Formatter};
use core::ops::Deref;
use rapira::{Rapira, RapiraError};
#[cfg(feature = "std")]
use serde::{Deserialize, Deserializer, Serialize};
#[cfg(feature = "std")]
use serde_json::{Number, Value as JsonValue};
#[cfg(feature = "std")]
use time::format_description::well_known::Rfc3339;
use time::{Duration as TimeDuration, OffsetDateTime};
#[cfg(feature = "wa_proto")]
use wa_proto::{Incoming, Outcoming};

#[cfg(feature = "std")]
use thiserror::Error;

use crate::{GetType, Typ, Value};

#[cfg_attr(feature = "std", derive(Error, Debug))]
pub enum DatetimeError {
    #[cfg_attr(feature = "std", error("string parse error"))]
    ParseError,
    #[cfg_attr(feature = "std", error("json parse error"))]
    JsonError,
    #[cfg(feature = "std")]
    #[cfg_attr(feature = "std", error(transparent))]
    OtherError(#[from] anyhow::Error),
}

/// i64 stored type
#[derive(PartialEq, Clone, Copy, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "std", derive(Deserialize, Debug))]
#[cfg_attr(feature = "wa_proto", derive(Outcoming, Incoming))]
#[repr(transparent)]
pub struct Datetime(OffsetDateTime);

impl Datetime {
    pub fn new(dt: OffsetDateTime) -> Self {
        Self(dt)
    }

    #[cfg(feature = "std")]
    pub fn now() -> Self {
        Self(OffsetDateTime::now_utc())
    }

    pub fn get_native_dt(&self) -> OffsetDateTime {
        self.0
    }

    /// 1985-04-12T23:20:50.52Z
    #[cfg(feature = "std")]
    pub fn to_str(self) -> String {
        let dt = self.0.replace_millisecond(0).unwrap();
        dt.format(&Rfc3339).expect("cannot format datetime")
    }

    /// json string "1985-04-12T23:20:50.52Z"
    #[cfg(feature = "std")]
    pub fn to_json(self) -> JsonValue {
        JsonValue::String(self.to_str())
    }

    /// from string "1985-04-12T23:20:50.52Z"
    #[cfg(feature = "std")]
    pub fn from_string(value: &str) -> Result<Self, DatetimeError> {
        OffsetDateTime::parse(value, &Rfc3339)
            .map(Datetime)
            .map_err(|_| DatetimeError::ParseError)
    }

    /// from json string "1985-04-12T23:20:50.52Z"
    #[cfg(feature = "std")]
    pub fn from_json(value: JsonValue) -> Result<Self, DatetimeError> {
        if let JsonValue::String(s) = value {
            Self::from_string(&s)
        } else {
            Err(DatetimeError::JsonError)
        }
    }

    /// milliseconds timestamp
    #[inline]
    pub fn to_i64(self) -> i64 {
        // unix_timestamp - seconds * 100 = milliseconds
        let ts = self.0.unix_timestamp() * 1000;
        let ms = self.0.millisecond();
        ts + ms as i64
    }

    /// from milliseconds timestamp
    #[inline]
    pub fn from_i64(i: i64) -> Result<Self, time::error::ComponentRange> {
        // milliseconds
        let ms = i % 1000;
        // seconds
        let ts = i / 1000;
        let dt = OffsetDateTime::from_unix_timestamp(ts)? + TimeDuration::milliseconds(ms);
        Ok(Datetime(dt))
    }
}

impl Default for Datetime {
    fn default() -> Self {
        #[cfg(feature = "std")]
        let created = OffsetDateTime::now_utc();

        #[cfg(not(feature = "std"))]
        let created = OffsetDateTime::UNIX_EPOCH;

        Datetime(created)
    }
}

#[cfg(feature = "std")]
impl Serialize for Datetime {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let s = self.to_str();
        serializer.serialize_str(&s)
    }
}

#[cfg(feature = "std")]
impl Display for Datetime {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.to_str())
    }
}

impl Deref for Datetime {
    type Target = OffsetDateTime;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[cfg(feature = "std")]
impl Encode for Datetime {
    fn encode<E: bincode::enc::Encoder>(
        &self,
        encoder: &mut E,
    ) -> core::result::Result<(), bincode::error::EncodeError> {
        let tsm = self.to_i64();
        bincode::Encode::encode(&tsm, encoder)?;
        Ok(())
    }
}

#[cfg(feature = "std")]
impl Decode for Datetime {
    fn decode<D: bincode::de::Decoder>(
        decoder: &mut D,
    ) -> Result<Self, bincode::error::DecodeError> {
        let tsm: i64 = bincode::de::Decode::decode(decoder)?;
        Self::from_i64(tsm).map_err(|_| {
            bincode::error::DecodeError::OtherString(
                "OffsetDateTime::from_unix_timestamp error".to_string(),
            )
        })
    }
}

impl<'de> BorrowDecode<'de> for Datetime {
    fn borrow_decode<D: bincode::de::BorrowDecoder<'de>>(
        decoder: &mut D,
    ) -> Result<Self, bincode::error::DecodeError> {
        let tsm: i64 = bincode::de::Decode::decode(decoder)?;
        Self::from_i64(tsm).map_err(|_| {
            bincode::error::DecodeError::OtherString(
                "OffsetDateTime::from_unix_timestamp error".to_string(),
            )
        })
    }
}

impl Rapira for Datetime {
    const STATIC_SIZE: Option<usize> = Some(8);

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> Result<Self, rapira::RapiraError>
    where
        Self: Sized,
    {
        let i = i64::from_slice(slice)?;
        Self::from_i64(i).map_err(|_| RapiraError::DatetimeError)
    }

    #[inline]
    fn check_bytes(slice: &mut &[u8]) -> Result<(), rapira::RapiraError>
    where
        Self: Sized,
    {
        let i = i64::from_slice(slice)?;
        // seconds
        let ts = i / 1000;
        OffsetDateTime::from_unix_timestamp(ts).map_err(|_| RapiraError::DatetimeError)?;
        Ok(())
    }

    #[inline]
    unsafe fn from_slice_unsafe(slice: &mut &[u8]) -> Result<Self, rapira::RapiraError>
    where
        Self: Sized,
    {
        let u = i64::from_slice_unsafe(slice)?;
        Self::from_i64(u).map_err(|_| RapiraError::DatetimeError)
    }

    #[inline]
    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        let tsm = self.to_i64();
        tsm.convert_to_bytes(slice, cursor);
    }

    #[inline]
    fn size(&self) -> usize {
        8
    }
}

// #[cfg(feature = "pg")]
// impl From<Datetime> for PgTimestamp {
//     fn from(v: Datetime) -> Self {
//         let unix = v.0.unix_timestamp();
//         PgTimestamp(unix * 1_000_000)
//     }
// }

impl GetType for Datetime {
    // TODO: datetime type?
    const TYPE: Option<Typ> = Some(Typ::Datetime);
}

// impl TryFrom<Value> for Datetime {
//     type Error = FromValueError;
//     fn try_from(value: Value) -> Result<Self, Self::Error> {
//         <OffsetDateTime as TryFrom<Value>>::try_from(value)
//             .map(Datetime)
//             .map_err(|_| FromValueError::TimeParseError)
//     }
// }

// impl TryFromValue for Datetime {
//     fn try_from(value: Value) -> Result<Self, FromValueError> {
//         <OffsetDateTime as TryFromValue>::try_from(value).map(Datetime)
//     }
// }

impl From<Datetime> for Value {
    fn from(val: Datetime) -> Self {
        Value::from(val.get_native_dt())
    }
}

/// u32 in stored bytes (seconds), i64 for json (seconds)
#[derive(PartialEq, Clone, Copy, Eq, Default, PartialOrd, Ord)]
#[cfg_attr(feature = "std", derive(Debug))]
#[cfg_attr(feature = "wa_proto", derive(Outcoming, Incoming))]
pub struct Duration(TimeDuration);

impl Duration {
    pub fn new(td: TimeDuration) -> Self {
        Self(td)
    }
    pub fn get_native_duration(self) -> TimeDuration {
        self.0
    }

    /// from seconds
    #[cfg(feature = "std")]
    pub fn from_number(value: i64) -> Self {
        Duration(TimeDuration::seconds(value))
    }

    /// from number seconds
    #[cfg(feature = "std")]
    pub fn from_json(value: JsonValue) -> Result<Self, DatetimeError> {
        if let JsonValue::Number(v) = &value {
            match v.as_i64() {
                None => Err(DatetimeError::JsonError),
                Some(v) => Ok(Self::from_number(v)),
            }
        } else {
            Err(DatetimeError::JsonError)
        }
    }

    /// to seconds
    #[cfg(feature = "std")]
    pub fn to_number(self) -> i64 {
        self.0.whole_seconds()
    }

    /// to number seconds
    #[cfg(feature = "std")]
    pub fn to_json(self) -> JsonValue {
        JsonValue::Number(Number::from(self.to_number()))
    }

    /// to milliseconds
    #[cfg(feature = "std")]
    pub fn to_millis(self) -> i128 {
        self.0.whole_milliseconds()
    }
}

impl Deref for Duration {
    type Target = TimeDuration;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[cfg(feature = "std")]
impl Serialize for Duration {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let seconds = self.0.whole_seconds();
        serializer.serialize_i64(seconds)
    }
}

impl<'de> Deserialize<'de> for Duration {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let seconds: i64 = Deserialize::deserialize(deserializer)?;
        let a = Duration(TimeDuration::seconds(seconds));
        Ok(a)
    }
}

impl Encode for Duration {
    fn encode<E: bincode::enc::Encoder>(
        &self,
        encoder: &mut E,
    ) -> core::result::Result<(), bincode::error::EncodeError> {
        let seconds = self.0.whole_seconds() as u32;
        bincode::Encode::encode(&seconds, encoder)?;
        Ok(())
    }
}

impl Decode for Duration {
    fn decode<D: bincode::de::Decoder>(
        decoder: &mut D,
    ) -> Result<Self, bincode::error::DecodeError> {
        let seconds: u32 = bincode::de::Decode::decode(decoder)?;
        let d = TimeDuration::seconds(seconds as i64);
        Ok(Duration(d))
    }
}

impl Rapira for Duration {
    const STATIC_SIZE: Option<usize> = Some(4);

    #[inline]
    fn from_slice(slice: &mut &[u8]) -> Result<Self, rapira::RapiraError>
    where
        Self: Sized,
    {
        let i = u32::from_slice(slice)? as i64;
        let duration = time::Duration::seconds(i);
        Ok(Duration::new(duration))
    }

    #[inline]
    fn check_bytes(slice: &mut &[u8]) -> Result<(), rapira::RapiraError>
    where
        Self: Sized,
    {
        *slice = slice.get(4..).ok_or(rapira::RapiraError::SliceLenError)?;
        Ok(())
    }

    #[inline]
    unsafe fn from_slice_unsafe(slice: &mut &[u8]) -> Result<Self, rapira::RapiraError>
    where
        Self: Sized,
    {
        let i = u32::from_slice_unsafe(slice)? as i64;
        let duration = time::Duration::seconds(i);
        Ok(Duration::new(duration))
    }

    #[inline]
    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        let secs = self.whole_seconds() as u32;
        secs.convert_to_bytes(slice, cursor);
    }

    #[inline]
    fn size(&self) -> usize {
        4
    }
}

impl GetType for Duration {
    // TODO: duration type?
    const TYPE: Option<Typ> = Some(Typ::U32);
}

// impl TryFrom<Value> for Duration {
//     type Error = FromValueError;
//     fn try_from(value: Value) -> Result<Self, Self::Error> {
//         <TimeDuration as TryFrom<Value>>::try_from(value).map(Duration)
//     }
// }

// impl TryFromValue for Duration {
//     fn try_from(value: Value) -> Result<Self, FromValueError> {
//         <TimeDuration as TryFromValue>::try_from(value).map(Duration)
//     }
// }

impl From<Duration> for Value {
    fn from(val: Duration) -> Self {
        Value::from(val.get_native_duration())
    }
}
