use std::borrow::Cow;

use bytes::{Buf, BufMut};

use crate::protocol::{BinaryPayload, ParseError};

/// Message data message.
///
/// Spec: <https://github.com/foxglove/ws-protocol/blob/main/docs/spec.md#message-data>
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MessageData<'a> {
    /// Channel ID.
    pub channel_id: u64,
    /// Log time.
    pub log_time: u64,
    /// Message data.
    pub data: Cow<'a, [u8]>,
}

impl<'a> MessageData<'a> {
    /// Creates a new message data message.
    pub fn new(channel_id: u64, log_time: u64, data: impl Into<Cow<'a, [u8]>>) -> Self {
        Self {
            channel_id,
            log_time,
            data: data.into(),
        }
    }

    /// Returns an owned version of this message.
    pub fn into_owned(self) -> MessageData<'static> {
        MessageData {
            channel_id: self.channel_id,
            log_time: self.log_time,
            data: Cow::Owned(self.data.into_owned()),
        }
    }
}

impl<'a> BinaryPayload<'a> for MessageData<'a> {
    fn parse_payload(mut data: &'a [u8]) -> Result<Self, ParseError> {
        if data.len() < 8 + 8 {
            return Err(ParseError::BufferTooShort);
        }
        let channel_id = data.get_u64_le();
        let log_time = data.get_u64_le();
        Ok(Self {
            channel_id,
            log_time,
            data: Cow::Borrowed(data),
        })
    }

    fn payload_size(&self) -> usize {
        8 + 8 + self.data.len()
    }

    fn write_payload(&self, buf: &mut impl BufMut) {
        buf.put_u64_le(self.channel_id);
        buf.put_u64_le(self.log_time);
        buf.put_slice(&self.data);
    }
}

#[cfg(test)]
mod tests {
    use crate::protocol::v2::message::BinaryMessage;
    use crate::protocol::v2::server::ServerMessage;

    use super::*;

    fn message() -> MessageData<'static> {
        MessageData {
            channel_id: 30,
            log_time: 1234,
            data: br#"{"key": "value"}"#.into(),
        }
    }

    #[test]
    fn test_encode() {
        insta::assert_snapshot!(format!("{:#04x?}", message().to_bytes()));
    }

    #[test]
    fn test_roundtrip() {
        let orig = message();
        let buf = orig.to_bytes();
        let msg = ServerMessage::parse_binary(&buf).unwrap();
        assert_eq!(msg, ServerMessage::MessageData(orig));
    }
}
