//! Collection of helper functions for serializing to and deserializing from BSON using Serde

use std::{convert::TryFrom, result::Result};

use serde::{ser, Serialize, Serializer};

use crate::oid::ObjectId;

pub use bson_datetime_as_iso_string::{
    deserialize as deserialize_bson_datetime_from_iso_string,
    serialize as serialize_bson_datetime_as_iso_string,
};
pub use chrono_datetime_as_bson_datetime::{
    deserialize as deserialize_chrono_datetime_from_bson_datetime,
    serialize as serialize_chrono_datetime_as_bson_datetime,
};
pub use iso_string_as_bson_datetime::{
    deserialize as deserialize_iso_string_from_bson_datetime,
    serialize as serialize_iso_string_as_bson_datetime,
};
pub use timestamp_as_u32::{
    deserialize as deserialize_timestamp_from_u32,
    serialize as serialize_timestamp_as_u32,
};
pub use u32_as_timestamp::{
    deserialize as deserialize_u32_from_timestamp,
    serialize as serialize_u32_as_timestamp,
};
pub use uuid_as_binary::{
    deserialize as deserialize_uuid_from_binary,
    serialize as serialize_uuid_as_binary,
};

/// Attempts to serialize a u32 as an i32. Errors if an exact conversion is not possible.
pub fn serialize_u32_as_i32<S: Serializer>(val: &u32, serializer: S) -> Result<S::Ok, S::Error> {
    match i32::try_from(*val) {
        Ok(val) => serializer.serialize_i32(val),
        Err(_) => Err(ser::Error::custom(format!("cannot convert {} to i32", val))),
    }
}

/// Serializes a u32 as an i64.
pub fn serialize_u32_as_i64<S: Serializer>(val: &u32, serializer: S) -> Result<S::Ok, S::Error> {
    serializer.serialize_i64(*val as i64)
}

/// Attempts to serialize a u64 as an i32. Errors if an exact conversion is not possible.
pub fn serialize_u64_as_i32<S: Serializer>(val: &u64, serializer: S) -> Result<S::Ok, S::Error> {
    match i32::try_from(*val) {
        Ok(val) => serializer.serialize_i32(val),
        Err(_) => Err(ser::Error::custom(format!("cannot convert {} to i32", val))),
    }
}

/// Attempts to serialize a u64 as an i64. Errors if an exact conversion is not possible.
pub fn serialize_u64_as_i64<S: Serializer>(val: &u64, serializer: S) -> Result<S::Ok, S::Error> {
    match i64::try_from(*val) {
        Ok(val) => serializer.serialize_i64(val),
        Err(_) => Err(ser::Error::custom(format!("cannot convert {} to i64", val))),
    }
}

/// Contains functions to serialize a chrono::DateTime as a bson::DateTime and deserialize a
/// chrono::DateTime from a bson::DateTime.
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use bson::serde_helpers::chrono_datetime_as_bson_datetime;
/// #[derive(Serialize, Deserialize)]
/// struct Event {
///     #[serde(with = "chrono_datetime_as_bson_datetime")]
///     pub date: chrono::DateTime<chrono::Utc>,
/// }
/// ```
pub mod chrono_datetime_as_bson_datetime {
    use crate::DateTime;
    use chrono::Utc;
    use serde::{Deserialize, Deserializer, Serialize, Serializer};
    use std::result::Result;

    /// Deserializes a chrono::DateTime from a bson::DateTime.
    pub fn deserialize<'de, D>(deserializer: D) -> Result<chrono::DateTime<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let datetime = DateTime::deserialize(deserializer)?;
        Ok(datetime.into())
    }

    /// Serializes a chrono::DateTime as a bson::DateTime.
    pub fn serialize<S: Serializer>(
        val: &chrono::DateTime<Utc>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        let datetime = DateTime::from(val.to_owned());
        datetime.serialize(serializer)
    }
}

/// Contains functions to serialize an ISO string as a bson::DateTime and deserialize an ISO string
/// from a bson::DateTime.
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use bson::serde_helpers::iso_string_as_bson_datetime;
/// #[derive(Serialize, Deserialize)]
/// struct Event {
///     #[serde(with = "iso_string_as_bson_datetime")]
///     pub date: String,
/// }
/// ```
pub mod iso_string_as_bson_datetime {
    use crate::{Bson, DateTime};
    use serde::{ser, Deserialize, Deserializer, Serialize, Serializer};
    use std::{result::Result, str::FromStr};

    /// Deserializes an ISO string from a DateTime.
    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
    where
        D: Deserializer<'de>,
    {
        let date = DateTime::deserialize(deserializer)?;
        Ok(date.to_string())
    }

    /// Serializes an ISO string as a DateTime.
    pub fn serialize<S: Serializer>(val: &str, serializer: S) -> Result<S::Ok, S::Error> {
        let date = chrono::DateTime::from_str(val).map_err(|_| {
            ser::Error::custom(format!("cannot convert {} to chrono::DateTime", val))
        })?;
        Bson::DateTime(date).serialize(serializer)
    }
}

/// Contains functions to serialize a bson::DateTime as an ISO string and deserialize a
/// bson::DateTime from an ISO string.
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use bson::serde_helpers::bson_datetime_as_iso_string;
/// #[derive(Serialize, Deserialize)]
/// struct Event {
///     #[serde(with = "bson_datetime_as_iso_string")]
///     pub date: bson::DateTime,
/// }
/// ```
pub mod bson_datetime_as_iso_string {
    use crate::DateTime;
    use serde::{de, Deserialize, Deserializer, Serializer};
    use std::{result::Result, str::FromStr};

    /// Deserializes a bson::DateTime from an ISO string.
    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime, D::Error>
    where
        D: Deserializer<'de>,
    {
        let iso = String::deserialize(deserializer)?;
        let date = chrono::DateTime::from_str(&iso).map_err(|_| {
            de::Error::custom(format!("cannot convert {} to chrono::DateTime", iso))
        })?;
        Ok(DateTime::from(date))
    }

    /// Serializes a bson::DateTime as an ISO string.
    pub fn serialize<S: Serializer>(val: &DateTime, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&val.to_string())
    }
}

/// Serializes a hex string as an ObjectId.
pub fn serialize_hex_string_as_object_id<S: Serializer>(
    val: &str,
    serializer: S,
) -> Result<S::Ok, S::Error> {
    match ObjectId::with_string(val) {
        Ok(oid) => oid.serialize(serializer),
        Err(_) => Err(ser::Error::custom(format!(
            "cannot convert {} to ObjectId",
            val
        ))),
    }
}

/// Contains functions to serialize a Uuid as a bson::Binary and deserialize a Uuid from a
/// bson::Binary.
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use uuid::Uuid;
/// # use bson::serde_helpers::uuid_as_binary;
/// #[derive(Serialize, Deserialize)]
/// struct Item {
///     #[serde(with = "uuid_as_binary")]
///     pub id: Uuid,
/// }
/// ```
pub mod uuid_as_binary {
    use crate::{spec::BinarySubtype, Binary};
    use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
    use std::result::Result;
    use uuid::Uuid;

    /// Serializes a Uuid as a Binary.
    pub fn serialize<S: Serializer>(val: &Uuid, serializer: S) -> Result<S::Ok, S::Error> {
        let binary = Binary {
            subtype: BinarySubtype::Uuid,
            bytes: val.as_bytes().to_vec(),
        };
        binary.serialize(serializer)
    }

    /// Deserializes a Uuid from a Binary.
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Uuid, D::Error>
    where
        D: Deserializer<'de>,
    {
        let binary = Binary::deserialize(deserializer)?;
        if binary.subtype == BinarySubtype::Uuid {
            if binary.bytes.len() == 16 {
                let mut bytes = [0u8; 16];
                bytes.copy_from_slice(&binary.bytes);
                Ok(Uuid::from_bytes(bytes))
            } else {
                Err(de::Error::custom(
                    "cannot convert Binary to Uuid: incorrect bytes length",
                ))
            }
        } else {
            Err(de::Error::custom(
                "cannot convert Binary to Uuid: incorrect binary subtype",
            ))
        }
    }
}

/// Contains functions to serialize a u32 as a bson::Timestamp and deserialize a u32 from a
/// bson::Timestamp. The u32 should represent seconds since the Unix epoch.
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use bson::serde_helpers::u32_as_timestamp;
/// #[derive(Serialize, Deserialize)]
/// struct Event {
///     #[serde(with = "u32_as_timestamp")]
///     pub time: u32,
/// }
/// ```
pub mod u32_as_timestamp {
    use crate::{Bson, Timestamp};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};
    use std::result::Result;

    /// Serializes a u32 as a bson::Timestamp.
    pub fn serialize<S: Serializer>(val: &u32, serializer: S) -> Result<S::Ok, S::Error> {
        let timestamp = Bson::Timestamp(Timestamp {
            time: *val,
            increment: 0,
        });
        timestamp.serialize(serializer)
    }

    /// Deserializes a u32 from a bson::Timestamp.
    pub fn deserialize<'de, D>(deserializer: D) -> Result<u32, D::Error>
    where
        D: Deserializer<'de>,
    {
        let timestamp = Timestamp::deserialize(deserializer)?;
        Ok(timestamp.time)
    }
}

/// Contains functions to serialize a bson::Timestamp as a u32 and deserialize a bson::Timestamp
/// from a u32. The u32 should represent seconds since the Unix epoch. Serialization will return an
/// error if the Timestamp has a non-zero increment.
///
/// ```rust
/// # use serde::{Serialize, Deserialize};
/// # use bson::{serde_helpers::timestamp_as_u32, Timestamp};
/// #[derive(Serialize, Deserialize)]
/// struct Item {
///     #[serde(with = "timestamp_as_u32")]
///     pub timestamp: Timestamp,
/// }
/// ```
pub mod timestamp_as_u32 {
    use crate::Timestamp;
    use serde::{ser, Deserialize, Deserializer, Serializer};
    use std::result::Result;

    /// Serializes a bson::Timestamp as a u32. Returns an error if the conversion is lossy (i.e. the
    /// Timestamp has a non-zero increment).
    pub fn serialize<S: Serializer>(val: &Timestamp, serializer: S) -> Result<S::Ok, S::Error> {
        if val.increment != 0 {
            return Err(ser::Error::custom(
                "Cannot convert Timestamp with a non-zero increment to u32",
            ));
        }
        serializer.serialize_u32(val.time)
    }

    /// Deserializes a bson::Timestamp from a u32.
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Timestamp, D::Error>
    where
        D: Deserializer<'de>,
    {
        let time = u32::deserialize(deserializer)?;
        Ok(Timestamp { time, increment: 0 })
    }
}
