use crate::Client;
use crate::error::Error;
use crate::mailbox::Mailbox;
use bytes::Bytes;
use futures::Stream;
use jiff::Timestamp;
use serde::Deserialize;

#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MessagePreviewData {
    #[serde(rename = "_id")]
    id: String,
    #[serde(with = "jiff::fmt::serde::timestamp::second::required")]
    received_at: Timestamp,
    from: String,
    subject: String,
    body_preview: String,
    attachments_count: u32,
}

/// Full message data returned by the API.
#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MessageData {
    #[serde(flatten)]
    preview: MessagePreviewData,

    user_id: String,
    mailbox: String,
    body_html: String,
    attachments: Vec<AttachmentMetadata>,
    created_at: Timestamp,
}

/// Metadata describing a message attachment.
#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
pub struct AttachmentMetadata {
    #[serde(rename = "_id")]
    id: u32,
    filename: String,
    size: u64,
    #[serde(rename = "mimetype")]
    mime_type: String,
    cid: String,
}

impl AttachmentMetadata {
    /// Returns the attachment id.
    pub fn id(&self) -> u32 {
        self.id
    }

    /// Returns the attachment filename.
    pub fn filename(&self) -> &str {
        &self.filename
    }

    /// Returns the attachment size in bytes.
    pub fn size(&self) -> u64 {
        self.size
    }

    /// Returns the attachment MIME type.
    pub fn mime_type(&self) -> &str {
        &self.mime_type
    }

    /// Returns the attachment content ID.
    pub fn cid(&self) -> &str {
        &self.cid
    }
}

/// A lightweight view of a message returned by [`Mailbox::list_messages`](crate::Mailbox::list_messages).
pub struct MessagePreview<'a> {
    client: &'a Client,
    mailbox: &'a Mailbox<'a>,
    data: MessagePreviewData,
}

impl<'a> MessagePreview<'a> {
    pub(crate) fn new(
        client: &'a Client,
        mailbox: &'a Mailbox<'a>,
        data: MessagePreviewData,
    ) -> Self {
        Self {
            client,
            mailbox,
            data,
        }
    }

    /// Returns the message id.
    pub fn id(&self) -> &str {
        &self.data.id
    }

    /// Returns when the message was received.
    pub fn received_at(&self) -> Timestamp {
        self.data.received_at
    }

    /// Returns the sender address.
    pub fn from(&self) -> &str {
        &self.data.from
    }

    /// Returns the message subject.
    pub fn subject(&self) -> &str {
        &self.data.subject
    }

    /// Returns the preview text for the message body.
    pub fn body_preview(&self) -> &str {
        &self.data.body_preview
    }

    /// Returns the number of attachments.
    pub fn attachments_count(&self) -> u32 {
        self.data.attachments_count
    }

    /// Fetches the full message body for this preview.
    ///
    /// This function takes the ownership of self, so the [`MessagePreview`] cannot be used after
    /// this function call.
    pub async fn to_message(self) -> Result<Message<'a>, Error> {
        let data = self
            .client
            .get_message(self.mailbox.token(), &self.data.id)
            .await?;
        Ok(Message::new(self.client, self.mailbox, data))
    }
}

/// A full message received by a [`Mailbox`].
pub struct Message<'a> {
    client: &'a Client,
    mailbox: &'a Mailbox<'a>,
    data: MessageData,
}

impl<'a> Message<'a> {
    fn new(client: &'a Client, mailbox: &'a Mailbox<'a>, data: MessageData) -> Self {
        Self {
            client,
            mailbox,
            data,
        }
    }

    /// Returns the message id.
    pub fn id(&self) -> &str {
        &self.data.preview.id
    }

    /// Returns when the message was received.
    pub fn received_at(&self) -> Timestamp {
        self.data.preview.received_at
    }

    /// Returns the sender address.
    pub fn from(&self) -> &str {
        &self.data.preview.from
    }

    /// Returns the message subject.
    pub fn subject(&self) -> &str {
        &self.data.preview.subject
    }

    /// Returns the preview text for the message body.
    pub fn body_preview(&self) -> &str {
        &self.data.preview.body_preview
    }

    /// Returns the number of attachments.
    pub fn attachments_count(&self) -> u32 {
        self.data.preview.attachments_count
    }

    /// Returns the HTML body of the message.
    pub fn body_html(&self) -> &str {
        &self.data.body_html
    }

    /// Returns all attachment metadata for the message.
    pub fn attachments(&self) -> &[AttachmentMetadata] {
        &self.data.attachments
    }

    /// Returns when the message was created.
    pub fn created_at(&self) -> Timestamp {
        self.data.created_at
    }

    /// Downloads a single attachment, the data is returned as a byte stream.
    pub async fn get_attachment(
        &self,
        id: u32,
    ) -> Result<impl Stream<Item = Result<Bytes, Error>>, Error> {
        self.client
            .get_attachment(self.mailbox.token(), &self.data.preview.id, id)
            .await
    }
}
