use super::message_ops::Sealed;
use super::{
    Message, MessageDeserializeError, MessageKind, MessageOps, MessageSerializeError,
    MessageSerializer, MessageWithValueDeserializer,
};
use crate::tags::{self, PrimaryTag, Tag};
use crate::{
    Deserialize, DeserializeError, DeserializePrimary, Deserializer, Serialize, SerializeError,
    SerializePrimary, SerializedValue, SerializedValueSlice, Serializer,
};
use bytes::BytesMut;
use num_enum::{IntoPrimitive, TryFromPrimitive};

#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))]
pub struct ConnectData {
    pub user: Option<SerializedValue>,
}

impl ConnectData {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn serialize_user_as<T: Tag>(
        &mut self,
        user: impl Serialize<T>,
    ) -> Result<&mut Self, SerializeError> {
        self.user = SerializedValue::serialize_as(user).map(Some)?;
        Ok(self)
    }

    pub fn serialize_user(
        &mut self,
        user: impl SerializePrimary,
    ) -> Result<&mut Self, SerializeError> {
        self.serialize_user_as(user)
    }

    pub fn deserialize_user_as<T: Tag, U: Deserialize<T>>(
        &self,
    ) -> Option<Result<U, DeserializeError>> {
        self.user
            .as_deref()
            .map(SerializedValueSlice::deserialize_as)
    }

    pub fn deserialize_user<T: DeserializePrimary>(&self) -> Option<Result<T, DeserializeError>> {
        self.deserialize_user_as()
    }
}

impl Tag for ConnectData {}

impl PrimaryTag for ConnectData {
    type Tag = Self;
}

#[derive(IntoPrimitive, TryFromPrimitive)]
#[repr(u32)]
enum ConnectDataField {
    User = 0,
}

impl Serialize<Self> for ConnectData {
    fn serialize(self, serializer: Serializer) -> Result<(), SerializeError> {
        serializer.serialize(&self)
    }
}

impl Serialize<ConnectData> for &ConnectData {
    fn serialize(self, serializer: Serializer) -> Result<(), SerializeError> {
        let mut serializer = serializer.serialize_struct2()?;

        serializer
            .serialize_if_some::<tags::Option<tags::Value>>(ConnectDataField::User, &self.user)?;

        serializer.finish()
    }
}

impl Deserialize<Self> for ConnectData {
    fn deserialize(deserializer: Deserializer) -> Result<Self, DeserializeError> {
        let mut deserializer = deserializer.deserialize_struct()?;

        let mut user = None;

        while let Some(deserializer) = deserializer.deserialize()? {
            match deserializer.try_id() {
                Ok(ConnectDataField::User) => user = deserializer.deserialize()?,
                Err(_) => deserializer.skip()?,
            }
        }

        deserializer.finish(Self { user })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))]
pub struct Connect2 {
    pub major_version: u32,
    pub minor_version: u32,
    pub value: SerializedValue,
}

impl MessageOps for Connect2 {
    fn kind(&self) -> MessageKind {
        MessageKind::Connect2
    }

    fn serialize_message(self) -> Result<BytesMut, MessageSerializeError> {
        let mut serializer = MessageSerializer::with_value(self.value, MessageKind::Connect2)?;

        serializer.put_varint_u32_le(self.major_version);
        serializer.put_varint_u32_le(self.minor_version);

        serializer.finish()
    }

    fn deserialize_message(buf: BytesMut) -> Result<Self, MessageDeserializeError> {
        let mut deserializer = MessageWithValueDeserializer::new(buf, MessageKind::Connect2)?;

        let major_version = deserializer.try_get_varint_u32_le()?;
        let minor_version = deserializer.try_get_varint_u32_le()?;
        let value = deserializer.finish()?;

        Ok(Self {
            major_version,
            minor_version,
            value,
        })
    }

    fn value(&self) -> Option<&SerializedValueSlice> {
        Some(&self.value)
    }

    fn value_mut(&mut self) -> Option<&mut SerializedValue> {
        Some(&mut self.value)
    }
}

impl Sealed for Connect2 {}

impl From<Connect2> for Message {
    fn from(msg: Connect2) -> Self {
        Self::Connect2(msg)
    }
}

#[cfg(test)]
mod test {
    use super::super::test::{assert_deserialize_eq_with_value, assert_serialize_eq};
    use super::super::Message;
    use super::{Connect2, ConnectData};
    use crate::SerializedValue;

    #[test]
    fn connect2() {
        let serialized = [13, 0, 0, 0, 46, 2, 0, 0, 0, 65, 0, 1, 2];
        let value = ConnectData::new();

        let msg = Connect2 {
            major_version: 1,
            minor_version: 2,
            value: SerializedValue::serialize(&value).unwrap(),
        };
        assert_serialize_eq(&msg, serialized);
        assert_deserialize_eq_with_value(&msg, serialized, &value);

        let msg = Message::Connect2(msg);
        assert_serialize_eq(&msg, serialized);
        assert_deserialize_eq_with_value(&msg, serialized, &value);
    }
}
