use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serializer};

/// Serialize/deserialize `DateTime<Utc>` as BSON DateTime.
/// Handles both BSON DateTime objects and RFC 3339 strings on deserialization.
pub mod bson_datetime {
    use super::*;
    use serde::Serialize as _;

    pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let bson_dt: bson::DateTime = (*dt).into();
        bson_dt.serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let bson_val = bson::Bson::deserialize(deserializer)?;
        match bson_val {
            bson::Bson::DateTime(dt) => Ok(dt.into()),
            bson::Bson::String(s) => s.parse::<DateTime<Utc>>().map_err(serde::de::Error::custom),
            other => Err(serde::de::Error::custom(format!(
                "expected DateTime or string, got: {other:?}"
            ))),
        }
    }
}

/// Serialize/deserialize `Option<DateTime<Utc>>` as BSON DateTime.
pub mod opt_bson_datetime {
    use super::*;
    use serde::Serialize as _;

    pub fn serialize<S>(dt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match dt {
            Some(dt) => {
                let bson_dt: bson::DateTime = (*dt).into();
                bson_dt.serialize(serializer)
            }
            None => serializer.serialize_none(),
        }
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let bson_val = Option::<bson::Bson>::deserialize(deserializer)?;
        match bson_val {
            Some(bson::Bson::DateTime(dt)) => Ok(Some(dt.into())),
            Some(bson::Bson::String(s)) => s
                .parse::<DateTime<Utc>>()
                .map(Some)
                .map_err(serde::de::Error::custom),
            Some(bson::Bson::Null) | None => Ok(None),
            Some(other) => Err(serde::de::Error::custom(format!(
                "expected DateTime, string, or null, got: {other:?}"
            ))),
        }
    }
}
