#[cfg(feature = "std")]
use bincode::{Decode, Encode};
#[cfg(feature = "std")]
use core::fmt::Debug;
use core::ops::Deref;
use rapira::Rapira;
#[cfg(feature = "std")]
use serde::{Deserialize, Deserializer, Serialize};
#[cfg(feature = "std")]
use serde_json::{Number, Value as JsonValue};
use specta::{DataType, DefOpts, PrimitiveType, Type};
use time::Duration as TimeDuration;
use typescript_type_def::{
    type_expr::{DefinedTypeInfo, Ident, TypeDefinition, TypeExpr, TypeInfo},
    TypeDef,
};
#[cfg(feature = "wa_proto")]
use wa_proto::{Incoming, Outcoming};

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

/// 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))]
#[repr(transparent)]
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())
    }
}

impl Type for Duration {
    const NAME: &'static str = "Duration";
    fn inline(_: DefOpts, _: &[DataType]) -> DataType {
        DataType::Primitive(PrimitiveType::String)
    }
}

impl TypeDef for Duration {
    const INFO: TypeInfo = TypeInfo::Defined(DefinedTypeInfo {
        def: TypeDefinition {
            docs: None,
            path: &[],
            name: Ident("Duration"),
            generic_vars: &[],
            def: TypeExpr::Ref(&i64::INFO),
        },
        generic_args: &[],
    });
}
