use libsignal_service::content::ContentBody;
use presage::proto::{GroupContextV2, data_message::quote::QuotedAttachment};

use crate::{
    AmendmentMessageContent, Attachment, AttachmentOrPointer, AttachmentPointer, DeletionMessage,
    GroupId, GroupSecrets, MainMessageContent, ReactionMessage, ReactionOperation,
    ReceivedMessageContent, Result, TextMessage,
};

pub trait MessageConversionAdditionalDataProvider {
    fn upload_attachment(
        &self,
        attachment: Attachment,
    ) -> impl Future<Output = Result<AttachmentPointer>>;
}

pub async fn message(
    message: &ReceivedMessageContent,
    group: Option<(GroupId, GroupSecrets)>,
    data_provider: &impl MessageConversionAdditionalDataProvider,
) -> Result<ContentBody> {
    Ok(match message {
        ReceivedMessageContent::Main(MainMessageContent::Text(msg)) => {
            text_message(msg, group, data_provider).await?
        }
        ReceivedMessageContent::Amendment(AmendmentMessageContent::Reaction(msg)) => {
            reaction(msg, group)
        }
        ReceivedMessageContent::Amendment(AmendmentMessageContent::Deletion(msg)) => {
            deletion(msg, group)
        }
        _ => todo!(),
    })
}

async fn text_message(
    message: &TextMessage,
    group: Option<(GroupId, GroupSecrets)>,
    data_provider: &impl MessageConversionAdditionalDataProvider,
) -> Result<ContentBody> {
    Ok(ContentBody::DataMessage(presage::proto::DataMessage {
        body: message.body.clone(),
        group_v2: group.map(|(_id, secrets)| GroupContextV2 {
            master_key: Some(secrets.master_key.into()),
            revision: Some(secrets.revision),
            group_change: None,
        }),
        quote: message
            .quote
            .as_ref()
            .map(|q| libsignal_service::proto::data_message::Quote {
                id: Some(q.quoted_message.timestamp.timestamp_millis() as u64),
                author_aci: Some(q.quoted_message.sender.0.service_id_string()),
                author_aci_binary: Some(q.quoted_message.sender.0.service_id_binary()),
                text: q.body.clone(),
                attachments: q
                    .attachments
                    .clone()
                    .into_iter()
                    .map(|_| QuotedAttachment {
                        content_type: None,
                        file_name: None,
                        // TODO
                        thumbnail: None,
                    })
                    .collect(),
                r#type: match q.kind {
                    crate::QuoteKind::Normal => Some(0),
                    crate::QuoteKind::GiftBadge => Some(1),
                    crate::QuoteKind::Poll => Some(2),
                },
                // TODO.
                body_ranges: vec![],
            }),
        is_view_once: Some(message.is_view_once),
        attachments: futures::future::join_all(
            message
                .attachments
                .clone()
                .into_iter()
                .map(|a| attachment(a, data_provider)),
        )
        .await
        .into_iter()
        .collect::<Result<Vec<_>>>()?,
        // TODO.
        body_ranges: vec![],
        contact: vec![],
        preview: vec![],
        ..Default::default()
    }))
}

pub async fn attachment(
    attachment: AttachmentOrPointer,
    data_provider: &impl MessageConversionAdditionalDataProvider,
) -> Result<libsignal_service::proto::AttachmentPointer> {
    match attachment {
        AttachmentOrPointer::Attachment(attachment) => Ok(attachment_pointer(
            data_provider.upload_attachment(attachment).await?,
        )),
        AttachmentOrPointer::Pointer(attachment) => Ok(attachment_pointer(attachment)),
    }
}

pub fn attachment_pointer(
    attachment: AttachmentPointer,
) -> libsignal_service::proto::AttachmentPointer {
    libsignal_service::proto::AttachmentPointer {
        client_uuid: attachment.secrets.client_uuid,
        content_type: attachment.properties.content_type,
        key: attachment.secrets.key,
        size: attachment.properties.size,
        thumbnail: attachment.properties.thumbnail,
        digest: attachment.secrets.digest,
        incremental_mac: attachment.secrets.incremental_mac,
        chunk_size: attachment.secrets.chunk_size,
        file_name: attachment.properties.file_name,
        flags: attachment.secrets.flags,
        width: attachment.properties.width,
        height: attachment.properties.height,
        caption: attachment.properties.caption,
        blur_hash: attachment.properties.blur_hash,
        upload_timestamp: attachment
            .properties
            .uploaded
            .map(|d| d.timestamp_millis() as u64),
        cdn_number: attachment.secrets.cdn_number,
        attachment_identifier: Some(attachment.id),
    }
}

fn reaction(message: &ReactionMessage, group: Option<(GroupId, GroupSecrets)>) -> ContentBody {
    ContentBody::DataMessage(presage::proto::DataMessage {
        group_v2: group.map(|(_id, secrets)| GroupContextV2 {
            master_key: Some(secrets.master_key.into()),
            revision: Some(secrets.revision),
            group_change: None,
        }),

        reaction: Some(libsignal_service::content::Reaction {
            emoji: Some(message.emoji.clone()),
            remove: Some(message.operation == ReactionOperation::Remove),
            target_author_aci: Some(message.reaction_to_message.sender.0.service_id_string()),
            target_author_aci_binary: Some(
                message.reaction_to_message.sender.0.service_id_binary(),
            ),
            target_sent_timestamp: Some(
                message.reaction_to_message.timestamp.timestamp_millis() as u64
            ),
        }),
        ..Default::default()
    })
}

fn deletion(message: &DeletionMessage, group: Option<(GroupId, GroupSecrets)>) -> ContentBody {
    ContentBody::DataMessage(presage::proto::DataMessage {
        group_v2: group.map(|(_id, secrets)| GroupContextV2 {
            master_key: Some(secrets.master_key.into()),
            revision: Some(secrets.revision),
            group_change: None,
        }),

        delete: Some(libsignal_service::proto::data_message::Delete {
            target_sent_timestamp: Some(message.deleted_message.timestamp.timestamp_millis() as u64),
        }),
        ..Default::default()
    })
}
