use std::{cell::RefCell, collections::HashMap};

use chrono::{TimeDelta, TimeZone};
use libsignal_service::{
    Profile,
    content::{ContentBody, Metadata},
    prelude::{Content, GroupMasterKey, GroupSecretParams, ProfileKey},
    protocol::{Aci, ServiceId},
    push_service::DEFAULT_DEVICE_ID,
    zkgroup::GroupMasterKeyBytes,
};
use presage::proto::{
    DataMessage, SyncMessage, receipt_message::Type, sync_message::Sent, typing_message::Action,
};

use crate::{
    AccountId, AmendmentMessageContent, AssociatedValue, AttachmentOrPointer, AttachmentPointer,
    BodyRange, BodyStyle, Contact, ContactId, ContactSecrets, DateTime, DeviceInfo, EditMessage,
    ErrorKind, Group, GroupId, GroupSecrets, MainMessageContent, Member, MemberLabel, Message,
    MessageId, MessageMetadata, MessageSubId, MetaMessageContent, Quote, QuoteKind,
    ReactionMessage, ReceiptKind, ReceiptMessage, ReceivedMessage, ReceivedMessageContent,
    RegistrationData, Result, Role, TextMessage, ThreadId, Timer, TypingAction, TypingMessage, Utc,
};

// Messages

trait ContentBodyExt {
    fn data_message(&self) -> Option<&DataMessage>;
}

impl ContentBodyExt for ContentBody {
    fn data_message(&self) -> Option<&DataMessage> {
        match self {
            libsignal_service::content::ContentBody::DataMessage(msg)
            | libsignal_service::content::ContentBody::SynchronizeMessage(SyncMessage {
                sent: Some(Sent {
                    message: Some(msg), ..
                }),
                ..
            }) => Some(msg),
            _ => None,
        }
    }
}
pub trait MessageConversionAdditionalDataProvider {
    fn thread_id_from_message_sender_timestamps(
        &self,
        sender: &AccountId,
        timestamp: &[DateTime<Utc>],
    ) -> impl std::future::Future<Output = Result<Vec<Option<ThreadId>>>> + Send;

    fn profile_updated(
        &self,
        sender: &ContactId,
        profile_key: &ProfileKey,
    ) -> impl Future<Output = Result<bool>> + Send;

    fn expire_timer_updated(
        &self,
        thread: &ThreadId,
        timer: Timer,
    ) -> impl Future<Output = Result<bool>> + Send;

    fn group_updated(
        &self,
        _group_id: &GroupId,
        _secrets: &GroupSecrets,
    ) -> impl Future<Output = Result<bool>> + Send;

    // In case the message referenced by this message is an edit message, return the original message (following edit messages as long as needed).
    fn original_message_id(
        &self,
        message_id: &MessageId,
    ) -> impl Future<Output = Result<MessageId>> + Send;
}

// Note: This should not be used, as it will cause some messages (receipt messages) to not be emitted correctly. Instead, implement a provider based on some database.
#[derive(Clone)]
pub struct DummyMessageConversionAdditionalDataProvider;

impl MessageConversionAdditionalDataProvider for DummyMessageConversionAdditionalDataProvider {
    fn thread_id_from_message_sender_timestamps(
        &self,
        _sender: &AccountId,
        timestamp: &[DateTime<Utc>],
    ) -> impl std::future::Future<Output = Result<Vec<Option<ThreadId>>>> + Send {
        std::future::ready(Ok(vec![None; timestamp.len()]))
    }

    fn profile_updated(
        &self,
        _sender: &ContactId,
        _profile_key: &ProfileKey,
    ) -> impl Future<Output = Result<bool>> + Send {
        std::future::ready(Ok(false))
    }

    fn group_updated(
        &self,
        _group_id: &GroupId,
        _secrets: &GroupSecrets,
    ) -> impl Future<Output = Result<bool>> + Send {
        std::future::ready(Ok(false))
    }

    fn original_message_id(
        &self,
        message_id: &MessageId,
    ) -> impl Future<Output = Result<MessageId>> + Send {
        std::future::ready(Ok(*message_id))
    }

    fn expire_timer_updated(
        &self,
        _thread: &ThreadId,
        _timer: Timer,
    ) -> impl Future<Output = Result<bool>> + Send {
        std::future::ready(Ok(false))
    }
}

struct MessageConversionData<P: MessageConversionAdditionalDataProvider> {
    message: Content,
    metadata: MessageMetadata,
    thread: ThreadId,
    message_id: MessageId,
    data_provider: P,
}

pub enum MessageConversionResult {
    Messages(Vec<ReceivedMessage>),
    WaitingFor(MessageId),
}

impl MessageConversionResult {
    pub const NONE: MessageConversionResult = MessageConversionResult::Messages(vec![]);

    pub fn single(m: ReceivedMessage) -> Self {
        Self::Messages(vec![m])
    }
}

impl FromIterator<ReceivedMessage> for MessageConversionResult {
    fn from_iter<T: IntoIterator<Item = ReceivedMessage>>(iter: T) -> Self {
        Self::Messages(iter.into_iter().collect())
    }
}

impl From<MessageConversionResult> for Vec<ReceivedMessage> {
    fn from(value: MessageConversionResult) -> Self {
        match value {
            MessageConversionResult::Messages(messages) => messages,
            MessageConversionResult::WaitingFor(_) => vec![],
        }
    }
}

impl IntoIterator for MessageConversionResult {
    type Item = MessageConversionResult;

    type IntoIter = std::vec::IntoIter<MessageConversionResult>;

    fn into_iter(self) -> Self::IntoIter {
        vec![self].into_iter()
    }
}

impl FromIterator<MessageConversionResult> for MessageConversionResult {
    fn from_iter<T: IntoIterator<Item = MessageConversionResult>>(iter: T) -> Self {
        let iter = iter.into_iter();
        let mut messages = Vec::with_capacity(iter.size_hint().0);

        for r in iter {
            match r {
                MessageConversionResult::Messages(m) => {
                    messages.extend(m);
                }
                MessageConversionResult::WaitingFor(message_id) => {
                    return MessageConversionResult::WaitingFor(message_id);
                }
            }
        }

        MessageConversionResult::Messages(messages)
    }
}

pub async fn message(
    message: Content,
    data_provider: impl MessageConversionAdditionalDataProvider,
) -> Result<MessageConversionResult> {
    let metadata = message_metadata(&message.metadata);
    let thread = content_thread(&message)?;
    let initial_message_id = message_id(&message)?;

    let mut data = MessageConversionData {
        message,
        metadata,
        thread,
        message_id: initial_message_id,
        data_provider,
    };

    // TODO: Others
    Ok(std::iter::empty()
        .chain(text_message(&mut data)?)
        .chain(reaction_message(&mut data).await?)
        .chain(receipt_message(&mut data).await?)
        .chain(typing_message(&mut data)?)
        .chain(edit_message(&mut data).await?)
        .chain(delete_message(&mut data).await?)
        .chain(delete_for_me_message(&mut data).await?)
        .chain(admin_delete_message(&mut data).await?)
        .chain(poll_create(&mut data).await?)
        .chain(story(&mut data).await?)
        .chain(call(&mut data).await?)
        .chain(payment(&mut data).await?)
        .chain(vote_poll(&mut data).await?)
        .chain(terminate_poll(&mut data).await?)
        .chain(contact_changed(&mut data).await?)
        .chain(contact_changed_expire_timer(&mut data).await?)
        .chain(group_changed(&mut data).await?)
        .collect())
}

fn message_metadata(metadata: &Metadata) -> MessageMetadata {
    MessageMetadata {
        needs_receipt: metadata.needs_receipt,
        unidentified_sender: metadata.unidentified_sender,
        was_plaintext: metadata.was_plaintext,
        server_guid: metadata.server_guid,
        sender_device_id: metadata.sender_device,
    }
}

fn content_thread(message: &Content) -> Result<ThreadId> {
    // Incorrectly handled by presage (TODO: Can potentially upstream?)
    if let libsignal_service::content::ContentBody::TypingMessage(typing_message) = &message.body
        && typing_message.group_id.is_some()
    {
        return Ok(ThreadId::Group(GroupId(
            typing_message.group_id().try_into().map_err(|e| {
                ErrorKind::Parse
                    .caused_by(e)
                    .with_message("invalid group id")
                    .add_field_context("group-id")
                    .add_value_dbg_context(typing_message.group_id())
            })?,
        )));
    }
    // Note: The guessed thread will be wrong for receipt messages; parsing those compute the thread itself though.

    // Rest is correctly handled by presage; use their logic.
    let presage_thread = presage::store::Thread::try_from(message).map_err(|e| {
        ErrorKind::Parse
            .caused_by(e)
            .with_message("cannot parse thread from message")
            .add_field_context("field")
            .add_value_dbg_context(message)
    })?;
    Ok(thread(presage_thread))
}

pub fn thread(presage_thread: presage::store::Thread) -> ThreadId {
    match presage_thread {
        presage::store::Thread::Contact(service_id) => ThreadId::Contact(AccountId(service_id)),
        presage::store::Thread::Group(master_key) => ThreadId::Group(group_id(master_key)),
    }
}

// Conversion from the group master key to the ID is pretty slow and done lots of times.
// Cache the result.
// Such a global cache is not a good design, but having some struct hold it which will then need to be used to convert pretty much everything also does not seem too nice.
thread_local! {
    static GROUP_ID_CACHE: RefCell<HashMap<crate::GroupMasterKey, GroupId>> = RefCell::new(HashMap::new());
}

fn group_id(master_key: crate::GroupMasterKey) -> GroupId {
    GROUP_ID_CACHE.with(|cache| {
        *cache.borrow_mut().entry(master_key).or_insert_with(|| {
            let id = GroupSecretParams::derive_from_master_key(GroupMasterKey::new(master_key))
                .get_group_identifier();

            GroupId(id)
        })
    })
}

fn message_id(message: &Content) -> Result<MessageId> {
    let ts = match &message.body {
        libsignal_service::content::ContentBody::SynchronizeMessage(SyncMessage {
            sent: Some(m),
            ..
        }) => timestamp(m.timestamp.ok_or_else(|| {
            ErrorKind::Parse
                .error()
                .with_message("message does not contain a timestamp")
                .add_field_context("timestamp")
                .add_value_dbg_context(message)
        })?)?,
        _ => message.metadata.timestamp,
    };

    Ok(MessageId {
        sender: AccountId(message.metadata.sender),
        timestamp: ts,
        sub_id: MessageSubId::Primary,
    })
}

pub fn timestamp(timestamp: u64) -> Result<DateTime<Utc>> {
    chrono::Utc
        .timestamp_millis_opt(timestamp.try_into().map_err(|e| {
            ErrorKind::Parse
                .caused_by(e)
                .with_message("cannot convert timestamp to i64")
                .add_field_context("timestamp")
                .add_value_context(timestamp.to_string())
        })?)
        .single()
        .ok_or_else(|| {
            ErrorKind::Parse
                .error()
                .with_message("timestamp out of range")
                .add_field_context("timestamp")
                .add_value_context(timestamp.to_string())
        })
}

async fn reaction_message<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        data_provider,
    } = data;

    match &message.body.data_message() {
        Some(DataMessage {
            reaction: Some(reaction_message),
            ..
        }) => {
            let parsed_message_id = MessageId {
                sender: AccountId(
                    reaction_message
                        .parse_target_author_aci()
                        .ok_or_else(|| {
                            ErrorKind::Parse
                                .error()
                                .with_message("cannot parse reaction target author aci")
                                .add_field_context("reaction-target-author-aci")
                                .add_value_dbg_context(reaction_message)
                        })?
                        .into(),
                ),
                timestamp: timestamp(reaction_message.target_sent_timestamp.ok_or_else(|| {
                    ErrorKind::Parse
                        .error()
                        .with_message("reaction message does not have a timestamp")
                        .add_field_context("reaction-timestamp")
                        .add_value_dbg_context(reaction_message)
                })?)?,
                sub_id: MessageSubId::Primary,
            };
            let reaction = ReactionMessage {
                reaction_to_message: data_provider
                    .original_message_id(&parsed_message_id)
                    .await?,
                emoji: reaction_message.emoji.clone().ok_or_else(|| {
                    ErrorKind::Parse
                        .error()
                        .with_message("reaction message does not contain an emoji")
                        .add_field_context("reaction")
                        .add_value_dbg_context(reaction_message)
                })?,
                operation: if reaction_message.remove() {
                    crate::ReactionOperation::Remove
                } else {
                    crate::ReactionOperation::Add
                },
            };

            Ok(MessageConversionResult::single(Message {
                id: message_id.get_and_next(),
                thread: *thread,
                metadata: *metadata,
                content: ReceivedMessageContent::Amendment(AmendmentMessageContent::Reaction(
                    reaction,
                )),
            }))
        }
        _ => Ok(MessageConversionResult::NONE),
    }
}

async fn receipt_message<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        message_id,
        data_provider,
        ..
    } = data;

    match &message.body {
        libsignal_service::content::ContentBody::ReceiptMessage(receipt_message) => {
            let own_account_id = AccountId(message.metadata.destination);
            let kind = receipt_kind(receipt_message.r#type());
            let timestamps: Option<Vec<DateTime<Utc>>> = receipt_message
                .timestamp
                .iter()
                .map(|t| {
                    chrono::Utc
                        .timestamp_millis_opt((*t).try_into().ok()?)
                        .single()
                })
                .collect();
            let timestamps = timestamps.ok_or_else(|| {
                ErrorKind::Parse
                    .error()
                    .with_message(
                        "receipt message does not have valid timeststamps (out of range, not i64)",
                    )
                    .add_field_context("receipt-timestamp")
                    .add_value_dbg_context(&receipt_message.timestamp)
            })?;
            let threads = data_provider
                .thread_id_from_message_sender_timestamps(&own_account_id, &timestamps)
                .await?;
            assert!(threads.len() == timestamps.len());

            let mut timestamps_by_thread = HashMap::new();
            for (ts, thread) in timestamps.iter().zip(threads) {
                let Some(thread) = thread else {
                    log::warn!("Got a receipt message without an associated thread; postponing");
                    return Ok(MessageConversionResult::WaitingFor(MessageId {
                        sender: own_account_id,
                        timestamp: *ts,
                        sub_id: MessageSubId::Primary,
                    }));
                };
                let tss = timestamps_by_thread
                    .entry(thread)
                    .or_insert_with(std::vec::Vec::new);
                tss.push(ts);
            }

            let mut correct_message_id = MessageId {
                sender: message_id.sender,
                timestamp: message_id.timestamp,
                sub_id: MessageSubId::Primary,
            };

            Ok(timestamps_by_thread
                .into_iter()
                .map(|(thread, tss)| {
                    let receipt = ReceiptMessage {
                        message_ids: tss
                            .into_iter()
                            .map(|t| MessageId {
                                sender: own_account_id,
                                timestamp: *t,
                                sub_id: MessageSubId::Primary,
                            })
                            .collect(),
                        kind,
                    };

                    Message {
                        id: correct_message_id.get_and_next(),
                        thread,
                        metadata: *metadata,
                        content: ReceivedMessageContent::Amendment(
                            AmendmentMessageContent::Receipt(receipt),
                        ),
                    }
                })
                .collect())
        }
        _ => Ok(MessageConversionResult::NONE),
    }
}

fn receipt_kind(kind: Type) -> ReceiptKind {
    match kind {
        Type::Delivery => ReceiptKind::Delivery,
        Type::Read => ReceiptKind::Read,
        Type::Viewed => ReceiptKind::Viewed,
    }
}

fn typing_message<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        ..
    } = data;

    match &message.body {
        libsignal_service::content::ContentBody::TypingMessage(typing_message) => {
            // Note: typing_message also has a group_id field; that is used when computing the thread of the message.
            let typing = TypingMessage {
                action: typing_action(typing_message.action()),
                timestamp: chrono::Utc
                    .timestamp_millis_opt(
                        typing_message
                            .timestamp
                            .ok_or_else(|| {
                                ErrorKind::Parse
                                    .error()
                                    .with_message("typing message does not have a timestamp")
                                    .add_field_context("typing-timestamp")
                                    .add_value_dbg_context(typing_message)
                            })?
                            .try_into()
                            .map_err(|e| {
                                ErrorKind::Parse
                                    .caused_by(e)
                                    .with_message("typing message timestamp not i64")
                                    .add_field_context("typing-timestamp")
                                    .add_value_dbg_context(typing_message.timestamp)
                            })?,
                    )
                    .single()
                    .ok_or_else(|| {
                        ErrorKind::Parse
                            .error()
                            .with_message("typing message timestamp out of range")
                            .add_field_context("typing-timestamp")
                            .add_value_dbg_context(typing_message.timestamp)
                    })?,
            };
            Ok(MessageConversionResult::single(Message {
                id: message_id.get_and_next(),
                thread: *thread,
                metadata: *metadata,
                content: ReceivedMessageContent::Meta(MetaMessageContent::Typing(typing)),
            }))
        }
        _ => Ok(MessageConversionResult::NONE),
    }
}

fn typing_action(action: Action) -> TypingAction {
    match action {
        Action::Started => TypingAction::Started,
        Action::Stopped => TypingAction::Stopped,
    }
}

fn text_message<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        ..
    } = data;

    let Some(data_message) = message.body.data_message() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(text_message) = text_message_inner(data_message, thread)? else {
        return Ok(MessageConversionResult::NONE);
    };

    Ok(MessageConversionResult::single(Message {
        id: message_id.get_and_next(),
        thread: *thread,
        metadata: *metadata,
        content: ReceivedMessageContent::Main(MainMessageContent::Text(text_message)),
    }))
}

fn text_message_inner(
    data_message: &DataMessage,
    thread: &ThreadId,
) -> Result<Option<TextMessage>> {
    if data_message.body.is_none()
        && data_message.attachments.is_empty()
        && data_message.contact.is_empty()
    {
        return Ok(None);
    }

    let text_message = TextMessage {
        body: data_message.body.clone(),
        attachments: data_message
            .attachments
            .clone()
            .into_iter()
            .map(attachment)
            .collect::<Result<Vec<_>>>()?,
        quote: data_message
            .quote
            .clone()
            .map(|q| quote(q, thread))
            .transpose()?,
        body_ranges: body_ranges(&data_message.body, &data_message.body_ranges, thread)?,
        is_view_once: data_message.is_view_once(),
        // TODO
        previews: vec![],
        contacts: vec![],
    };

    Ok(Some(text_message))
}

// TODO: Tests
fn body_ranges(
    text: &Option<String>,
    ranges: &[presage::proto::BodyRange],
    thread: &ThreadId,
) -> Result<Vec<BodyRange>> {
    let Some(text) = text else {
        return Ok(vec![]);
    };
    if ranges.is_empty() {
        return Ok(vec![]);
    }

    let mut utf8_ranges: Vec<BodyRange> = ranges
        .iter()
        .map(|b| {
            Ok(BodyRange {
                start: 0,
                length: 0,
                associated_value: associated_value(
                    b.associated_value.as_ref().ok_or_else(|| {
                        ErrorKind::Parse
                            .error()
                            .with_message("body range does not have an associated value")
                            .add_field_context("body-range-associated-value")
                            .add_value_dbg_context(b)
                    })?,
                    thread,
                )?,
            })
        })
        .collect::<Result<_>>()?;

    // Map from endpoint position to an indicator whether the range starts or ends and the index of the body range.
    let mut utf16_endpoints: Vec<(u32, bool, usize)> = ranges
        .iter()
        .enumerate()
        .flat_map(|(i, r)| vec![(r.start(), true, i), (r.start() + r.length(), false, i)])
        .collect();

    // Should already be mostly sorted, but the below algorithm depends on it, so sort just to be sure.
    utf16_endpoints.sort_unstable();

    let mut utf16_current_position: usize = 0;
    let mut utf8_current_position: usize = 0;
    let utf16_string: Vec<u16> = text.encode_utf16().collect();

    for (utf16_endpoint_position, start, body_range_index) in utf16_endpoints {
        let utf16_part = &utf16_string[utf16_current_position
            ..(utf16_endpoint_position.try_into().map_err(|e| {
                ErrorKind::Parse
                    .caused_by(e)
                    .with_message("cannot convert body range utf-16 position to usize")
                    .add_field_context("body-range-endpoint-position")
                    .add_value_context(utf16_endpoint_position.to_string())
            })?)];
        let utf8_part = String::from_utf16(utf16_part).map_err(|e| {
            ErrorKind::Parse
                .caused_by(e)
                .with_message("could not convert body to utf-16")
                .add_field_context("body-range")
                .add_value_dbg_context(utf16_part)
        })?;

        utf16_current_position = utf16_endpoint_position.try_into().map_err(|e| {
            ErrorKind::Parse
                .caused_by(e)
                .with_message("could not convert body range utf-16 position to usize")
                .add_field_context("body-range-endpoint-position")
                .add_value_context(utf16_endpoint_position.to_string())
        })?;
        utf8_current_position += utf8_part.len();

        if start {
            utf8_ranges[body_range_index].start = utf8_current_position;
        } else {
            utf8_ranges[body_range_index].length =
                utf8_current_position - utf8_ranges[body_range_index].start;
        }
    }

    Ok(utf8_ranges)
}

fn associated_value(
    value: &presage::proto::body_range::AssociatedValue,
    thread: &ThreadId,
) -> Result<AssociatedValue> {
    Ok(match value {
        presage::proto::body_range::AssociatedValue::MentionAci(a) => {
            AssociatedValue::Mention(ContactId {
                account_id: AccountId(ServiceId::Aci(
                    Aci::parse_from_service_id_string(a).ok_or_else(|| {
                        ErrorKind::Parse
                            .error()
                            .with_message("could not parse associated value aci (string)")
                            .add_field_context("associated-value")
                            .add_value_dbg_context(a)
                    })?,
                )),
                in_group: thread.group(),
            })
        }
        presage::proto::body_range::AssociatedValue::MentionAciBinary(a) => {
            AssociatedValue::Mention(ContactId {
                account_id: AccountId(ServiceId::Aci(
                    Aci::parse_from_service_id_binary(a).ok_or_else(|| {
                        ErrorKind::Parse
                            .error()
                            .with_message("could not parse associatted value aci (binary)")
                            .add_field_context("associated-value")
                            .add_value_dbg_context(a)
                    })?,
                )),
                in_group: thread.group(),
            })
        }
        // Source: https://github.com/signalapp/Signal-Android/blob/main/lib/libsignal-service/src/main/protowire/SignalService.proto#L981.
        presage::proto::body_range::AssociatedValue::Style(0) => {
            AssociatedValue::Style(BodyStyle::None)
        }
        presage::proto::body_range::AssociatedValue::Style(1) => {
            AssociatedValue::Style(BodyStyle::Bold)
        }
        presage::proto::body_range::AssociatedValue::Style(2) => {
            AssociatedValue::Style(BodyStyle::Italic)
        }
        presage::proto::body_range::AssociatedValue::Style(3) => {
            AssociatedValue::Style(BodyStyle::Spoiler)
        }
        presage::proto::body_range::AssociatedValue::Style(4) => {
            AssociatedValue::Style(BodyStyle::Strikethrough)
        }
        presage::proto::body_range::AssociatedValue::Style(5) => {
            AssociatedValue::Style(BodyStyle::Monospace)
        }
        presage::proto::body_range::AssociatedValue::Style(s) => {
            return Err(ErrorKind::Parse
                .error()
                .with_message("associated value style out of range")
                .add_field_context("associated-style-value")
                .add_value_context(s.to_string()));
        }
    })
}

fn quote(quote: presage::proto::data_message::Quote, thread: &ThreadId) -> Result<Quote> {
    Ok(Quote {
        quoted_message: MessageId {
            sender: AccountId(
                quote
                    .parse_author_aci()
                    .ok_or_else(|| {
                        ErrorKind::Parse
                            .error()
                            .with_message("cannot parse author aci of quoted message")
                            .add_field_context("quote-author-aci")
                            .add_value_dbg_context(&quote)
                    })?
                    .into(),
            ),
            timestamp: chrono::Utc
                .timestamp_millis_opt(
                    quote
                        .id
                        .ok_or_else(|| {
                            ErrorKind::Parse
                                .error()
                                .with_message("quoted message does not have a timestamp")
                                .add_field_context("quote-timestamp")
                                .add_value_dbg_context(&quote)
                        })?
                        .try_into()
                        .map_err(|e| {
                            ErrorKind::Parse
                                .caused_by(e)
                                .with_message("quoted message timestamp cannot be converted to i64")
                                .add_field_context("quote-timestamp")
                                .add_value_dbg_context(quote.id)
                        })?,
                )
                .single()
                .ok_or_else(|| {
                    ErrorKind::Parse
                        .error()
                        .with_message("quoted message timestamp out of range")
                        .add_field_context("quote-timestamp")
                        .add_value_dbg_context(quote.id)
                })?,
            sub_id: MessageSubId::Primary,
        },
        body_ranges: body_ranges(&quote.text, &quote.body_ranges, thread)?,
        kind: quote_kind(quote.r#type()),
        body: quote.text,
        // TODO: Maybe special kind for quoted attachments, as also done by Signal?
        attachments: quote
            .attachments
            .into_iter()
            .flat_map(|a| a.thumbnail.map(attachment))
            .collect::<Result<Vec<_>>>()?,
    })
}

fn quote_kind(kind: presage::proto::data_message::quote::Type) -> QuoteKind {
    match kind {
        presage::proto::data_message::quote::Type::Normal => QuoteKind::Normal,
        presage::proto::data_message::quote::Type::GiftBadge => QuoteKind::GiftBadge,
        presage::proto::data_message::quote::Type::Poll => QuoteKind::Poll,
    }
}

async fn poll_create<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        ..
    } = data;

    let Some(data_message) = message.body.data_message() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(poll_create) = &data_message.poll_create else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(question) = &poll_create.question else {
        return Ok(MessageConversionResult::NONE);
    };

    Ok(MessageConversionResult::single(Message {
        id: message_id.get_and_next(),
        thread: *thread,
        metadata: *metadata,
        content: ReceivedMessageContent::Main(MainMessageContent::CreatePoll(
            crate::CreatePollMessage {
                question: question.clone(),
                options: poll_create.options.clone(),
                allow_multiple: poll_create.allow_multiple(),
            },
        )),
    }))
}

async fn story<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        ..
    } = data;

    match &message.body {
        ContentBody::StoryMessage(_story_message) => Ok(MessageConversionResult::single(Message {
            id: message_id.get_and_next(),
            thread: *thread,
            metadata: *metadata,
            content: ReceivedMessageContent::Main(MainMessageContent::Story(
                crate::StoryMessage {},
            )),
        })),
        _ => Ok(MessageConversionResult::NONE),
    }
}

async fn call<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        ..
    } = data;

    match &message.body {
        ContentBody::CallMessage(_call_message) => Ok(MessageConversionResult::single(Message {
            id: message_id.get_and_next(),
            thread: *thread,
            metadata: *metadata,
            content: ReceivedMessageContent::Main(MainMessageContent::Call(crate::CallMessage {})),
        })),
        _ => Ok(MessageConversionResult::NONE),
    }
}

async fn payment<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        ..
    } = data;

    let Some(data_message) = message.body.data_message() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(_payment) = &data_message.payment else {
        return Ok(MessageConversionResult::NONE);
    };

    Ok(MessageConversionResult::single(Message {
        id: message_id.get_and_next(),
        thread: *thread,
        metadata: *metadata,
        content: ReceivedMessageContent::Main(MainMessageContent::Payment(
            crate::PaymentMessage {},
        )),
    }))
}

async fn vote_poll<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        ..
    } = data;

    let Some(data_message) = message.body.data_message() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(poll_vote) = &data_message.poll_vote else {
        return Ok(MessageConversionResult::NONE);
    };

    let vote_for = MessageId {
        sender: AccountId(ServiceId::Aci(
            poll_vote.parse_target_author_aci().ok_or_else(|| {
                ErrorKind::Parse
                    .error()
                    .with_message("poll vote message does not have an author id")
            })?,
        )),
        timestamp: timestamp(poll_vote.target_sent_timestamp())?,
        sub_id: MessageSubId::Primary,
    };

    Ok(MessageConversionResult::single(Message {
        id: message_id.get_and_next(),
        thread: *thread,
        metadata: *metadata,
        content: ReceivedMessageContent::Amendment(AmendmentMessageContent::VotePoll(
            crate::VotePollMessage {
                vote_for,
                options: poll_vote.option_indexes.clone(),
            },
        )),
    }))
}

async fn terminate_poll<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        ..
    } = data;

    let Some(data_message) = message.body.data_message() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(terminate_poll) = &data_message.poll_terminate else {
        return Ok(MessageConversionResult::NONE);
    };

    let poll = MessageId {
        sender: message_id.sender,
        timestamp: timestamp(terminate_poll.target_sent_timestamp())?,
        sub_id: MessageSubId::Primary,
    };

    Ok(MessageConversionResult::single(Message {
        id: message_id.get_and_next(),
        thread: *thread,
        metadata: *metadata,
        content: ReceivedMessageContent::Amendment(AmendmentMessageContent::TerminatePoll(
            crate::TerminatePollMessage { poll },
        )),
    }))
}

async fn edit_message<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        data_provider,
    } = data;

    match &message.body {
        ContentBody::EditMessage(libsignal_service::content::EditMessage {
            target_sent_timestamp: Some(ts),
            data_message: Some(data_message),
        })
        | ContentBody::SynchronizeMessage(SyncMessage {
            sent:
                Some(Sent {
                    edit_message:
                        Some(libsignal_service::content::EditMessage {
                            target_sent_timestamp: Some(ts),
                            data_message: Some(data_message),
                        }),
                    ..
                }),
            ..
        }) => {
            let updated = text_message_inner(data_message, thread)?.ok_or_else(|| {
                ErrorKind::Parse
                    .error()
                    .with_message("updated edit message does not contain a text message")
                    .add_value_dbg_context(data_message)
                    .add_thread_context(*thread)
            })?;

            let edited_message = data_provider
                .original_message_id(&MessageId {
                    sender: message_id.sender,
                    timestamp: timestamp(*ts)?,
                    sub_id: MessageSubId::Primary,
                })
                .await?;

            let edit = EditMessage {
                edited_message,
                updated,
            };

            Ok(MessageConversionResult::single(Message {
                id: message_id.get_and_next(),
                thread: *thread,
                metadata: *metadata,
                content: ReceivedMessageContent::Amendment(AmendmentMessageContent::EditMessage(
                    edit,
                )),
            }))
        }
        _ => Ok(MessageConversionResult::NONE),
    }
}

async fn delete_message<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        ..
    } = data;

    let Some(data_message) = message.body.data_message() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(delete_message) = &data_message.delete else {
        return Ok(MessageConversionResult::NONE);
    };

    Ok(MessageConversionResult::single(Message {
        id: message_id.get_and_next(),
        thread: *thread,
        metadata: *metadata,
        content: ReceivedMessageContent::Amendment(AmendmentMessageContent::Deletion(
            crate::DeletionMessage {
                deleted_message: MessageId {
                    sender: message_id.sender,
                    timestamp: timestamp(delete_message.target_sent_timestamp())?,
                    sub_id: MessageSubId::Primary,
                },
            },
        )),
    }))
}

async fn delete_for_me_message<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        message_id,
        ..
    } = data;

    match &message.body {
        ContentBody::SynchronizeMessage(sync_message) => {
            let Some(delete) = &sync_message.delete_for_me else {
                return Ok(MessageConversionResult::NONE);
            };
            // TODO: Also handle conversation deletes, and attachment deletes.

            let mut result = vec![];
            for group in &delete.message_deletes {
                let thread = match group
                    .conversation
                    .as_ref()
                    .and_then(|c| c.identifier.as_ref())
                {
                    Some(presage::proto::conversation_identifier::Identifier::ThreadServiceId(
                        id,
                    )) => ServiceId::parse_from_service_id_string(id)
                        .map(|s| ThreadId::Contact(AccountId(s))),
                    Some(
                        presage::proto::conversation_identifier::Identifier::ThreadServiceIdBinary(
                            id,
                        ),
                    ) => ServiceId::parse_from_service_id_binary(id)
                        .map(|s| ThreadId::Contact(AccountId(s))),
                    Some(presage::proto::conversation_identifier::Identifier::ThreadGroupId(
                        id,
                    )) => id
                        .clone()
                        .try_into()
                        .ok()
                        .map(|i| ThreadId::Group(GroupId(i))),
                    Some(presage::proto::conversation_identifier::Identifier::ThreadE164(_)) => {
                        None
                    }
                    None => None,
                };
                let Some(deleted_message_thread) = thread else {
                    log::warn!("Cannot find thread of delete-for-self message; don't delete it");
                    continue;
                };
                for d in &group.messages {
                    let sender = AccountId(match &d.author {
                        Some(presage::proto::addressable_message::Author::AuthorServiceId(id)) => ServiceId::parse_from_service_id_string(id).ok_or_else(|| ErrorKind::Parse.error().with_message("delete-for-self message contains invalid string author service id").add_value_dbg_context(id))?,
                        Some(presage::proto::addressable_message::Author::AuthorServiceIdBinary(id)) => ServiceId::parse_from_service_id_binary(id).ok_or_else(|| ErrorKind::Parse.error().with_message("delete-for-self message contains invalid binary author service id").add_value_dbg_context(id))?,
                        _ => return Err(ErrorKind::Parse.error().with_message("delete-for-self message contains invalid sender specifier")),
                    });
                    let timestamp = timestamp(d.sent_timestamp())?;

                    result.push(Message {
                        id: message_id.get_and_next(),
                        thread: deleted_message_thread,
                        metadata: *metadata,
                        content: ReceivedMessageContent::Amendment(
                            AmendmentMessageContent::Deletion(crate::DeletionMessage {
                                deleted_message: MessageId {
                                    sender,
                                    timestamp,
                                    sub_id: MessageSubId::Primary,
                                },
                            }),
                        ),
                    })
                }
            }

            Ok(MessageConversionResult::Messages(result))
        }
        _ => Ok(MessageConversionResult::NONE),
    }
}

async fn admin_delete_message<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        ..
    } = data;

    let Some(data_message) = message.body.data_message() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(delete_message) = &data_message.admin_delete else {
        return Ok(MessageConversionResult::NONE);
    };

    Ok(MessageConversionResult::single(Message {
        id: message_id.get_and_next(),
        thread: *thread,
        metadata: *metadata,
        content: ReceivedMessageContent::Amendment(AmendmentMessageContent::Deletion(
            crate::DeletionMessage {
                deleted_message: MessageId {
                    sender: AccountId(
                        ServiceId::parse_from_service_id_binary(
                            delete_message.target_author_aci_binary(),
                        )
                        .ok_or_else(|| {
                            ErrorKind::Parse
                                .error()
                                .with_message("admin deletion message does not have a valid target")
                        })?,
                    ),
                    timestamp: timestamp(delete_message.target_sent_timestamp())?,
                    sub_id: MessageSubId::Primary,
                },
            },
        )),
    }))
}

async fn contact_changed<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        data_provider,
    } = data;

    let Some(data_message) = message.body.data_message() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(profile_key) = &data_message.profile_key else {
        return Ok(MessageConversionResult::NONE);
    };

    let profile_key = ProfileKey::create(profile_key.clone().try_into().map_err(|_| {
        ErrorKind::Parse
            .error()
            .with_message("profile key has invalid length")
            .add_value_dbg_context(profile_key)
    })?);
    let contact_id = ContactId {
        account_id: message_id.sender,
        in_group: thread.group(),
    };

    if data_provider
        .profile_updated(&contact_id, &profile_key)
        .await?
    {
        Ok(MessageConversionResult::single(Message {
            id: message_id.get_and_next(),
            thread: *thread,
            metadata: *metadata,
            content: ReceivedMessageContent::Meta(MetaMessageContent::ContactChange(
                crate::ContactChangeMessage {
                    contact: contact_id,
                    secrets: crate::ContactSecrets {
                        profile_key: Some(profile_key),
                    },
                },
            )),
        }))
    } else {
        Ok(MessageConversionResult::NONE)
    }
}

async fn contact_changed_expire_timer<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        data_provider,
    } = data;

    let Some(data_message) = message.body.data_message() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(account_id) = thread.contact() else {
        return Ok(MessageConversionResult::NONE);
    };

    let contact_id = ContactId {
        account_id,
        in_group: None,
    };

    let timer = timer(
        data_message
            .expire_timer
            .map(|v| libsignal_service::groups_v2::Timer { duration: v }),
    );

    if data_provider.expire_timer_updated(thread, timer).await? {
        let Some(profile_key) = &data_message.profile_key else {
            return Ok(MessageConversionResult::NONE);
        };

        let profile_key = ProfileKey::create(profile_key.clone().try_into().map_err(|_| {
            ErrorKind::Parse
                .error()
                .with_message("profile key has invalid length")
                .add_value_dbg_context(profile_key)
        })?);

        Ok(MessageConversionResult::single(Message {
            id: message_id.get_and_next(),
            thread: *thread,
            metadata: *metadata,
            content: ReceivedMessageContent::Meta(MetaMessageContent::ContactChange(
                crate::ContactChangeMessage {
                    contact: contact_id,
                    secrets: crate::ContactSecrets {
                        profile_key: Some(profile_key),
                    },
                },
            )),
        }))
    } else {
        Ok(MessageConversionResult::NONE)
    }
}

async fn group_changed<P: MessageConversionAdditionalDataProvider>(
    data: &mut MessageConversionData<P>,
) -> Result<MessageConversionResult> {
    let MessageConversionData {
        message,
        metadata,
        thread,
        message_id,
        data_provider,
    } = data;

    let Some(data_message) = message.body.data_message() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(group_id) = thread.group() else {
        return Ok(MessageConversionResult::NONE);
    };

    let Some(context) = &data_message.group_v2 else {
        return Ok(MessageConversionResult::NONE);
    };

    let secrets = GroupSecrets {
        master_key: context
            .master_key
            .as_ref()
            .ok_or_else(|| {
                ErrorKind::Parse
                    .error()
                    .with_message("group context does not have a master key")
                    .add_field_context("master-key")
                    .add_value_dbg_context(context)
            })?
            .clone()
            .try_into()
            .map_err(|_| {
                ErrorKind::Parse
                    .error()
                    .with_message("invalid master key length")
                    .add_field_context("master-key")
                    .add_value_dbg_context(&context.master_key)
            })?,
        revision: context.revision(),
    };

    if data_provider.group_updated(&group_id, &secrets).await? {
        Ok(MessageConversionResult::single(Message {
            id: message_id.get_and_next(),
            thread: *thread,
            metadata: *metadata,
            content: ReceivedMessageContent::Meta(MetaMessageContent::GroupChange(
                crate::GroupChangeMessage {
                    group: group_id,
                    secrets,
                },
            )),
        }))
    } else {
        Ok(MessageConversionResult::NONE)
    }
}

// Groups
pub fn group(master_key: GroupMasterKeyBytes, group: presage::model::groups::Group) -> Group {
    let id = group_id(master_key);
    Group {
        id,
        name: group.title,
        description: group.description,
        announcement_only: false, // TODO
        disappearing_messages_timer: timer(group.disappearing_messages_timer),
        members: group.members.into_iter().map(|m| member(m, id)).collect(),
        pending_members: vec![],    // TODO
        requesting_members: vec![], // TODO
        invite_link_password: group.invite_link_password,
        access_control: None, // TODO
        secrets: crate::GroupSecrets {
            master_key,
            revision: group.revision,
        },
    }
}

fn member(member: presage::model::groups::Member, group: GroupId) -> Member {
    Member {
        id: ContactId {
            account_id: AccountId(ServiceId::Aci(member.aci)),
            in_group: Some(group),
        },
        role: role(member.role),
        secrets: ContactSecrets {
            profile_key: Some(member.profile_key),
        },
        label: MemberLabel {
            label: member.label,
            emoji: member.label_emoji,
        },
    }
}

fn role(role: libsignal_service::groups_v2::Role) -> Role {
    match role {
        libsignal_service::groups_v2::Role::Unknown => Role::Unknown,
        libsignal_service::groups_v2::Role::Default => Role::Default,
        libsignal_service::groups_v2::Role::Administrator => Role::Administrator,
    }
}

pub fn timer(timer: Option<libsignal_service::prelude::Timer>) -> Timer {
    match timer {
        Some(libsignal_service::prelude::Timer { duration: 0 }) => Timer::Off,
        Some(libsignal_service::prelude::Timer { duration }) => {
            Timer::On(TimeDelta::seconds(duration as i64))
        }
        None => Timer::Off,
    }
}

pub fn contact(
    contact: presage::model::contacts::Contact,
    profile_key_overwrite: Option<ProfileKey>,
    profile: Option<Profile>,
    group: Option<GroupId>,
    member: Option<Member>,
) -> Result<Contact> {
    Ok(Contact {
        id: ContactId {
            account_id: AccountId(ServiceId::Aci(contact.uuid.into())),
            in_group: group,
        },
        phone_number: contact.phone_number,
        username: if contact.name.is_empty() {
            None
        } else {
            Some(contact.name)
        },
        email: None,
        is_blocked: false, // TODO
        given_name: profile
            .as_ref()
            .and_then(|p| p.name.as_ref())
            .map(|n| n.given_name.clone())
            .clone(),
        family_name: profile
            .as_ref()
            .and_then(|p| p.name.as_ref())
            .and_then(|n| n.family_name.clone()),
        about: profile.as_ref().and_then(|p| p.about.clone()),
        about_emoji: profile.as_ref().and_then(|p| p.about_emoji.clone()),
        disappearing_messages_timer: timer(Some(libsignal_service::prelude::Timer {
            duration: contact.expire_timer,
        })),
        secrets: crate::ContactSecrets {
            profile_key: profile_key_overwrite.or_else(|| {
                contact
                    .profile_key
                    .try_into()
                    .ok()
                    .map(|k| libsignal_service::prelude::ProfileKey { bytes: k })
            }),
        },
        group_member_data: member,
    })
}

pub fn registration_data(data: presage::manager::RegistrationData) -> RegistrationData {
    RegistrationData {
        account: AccountId(ServiceId::Aci(data.service_ids.aci())),
        device_id: data
            .device_id
            .and_then(|d| d.try_into().ok())
            .unwrap_or(*DEFAULT_DEVICE_ID),
        device_name: data.device_name,
        phone_number: data.phone_number,
    }
}

pub fn attachment_pointer(
    attachment: libsignal_service::proto::AttachmentPointer,
) -> Result<AttachmentPointer> {
    Ok(AttachmentPointer {
        id: attachment
            .attachment_identifier
            .as_ref()
            .ok_or_else(|| {
                ErrorKind::Parse
                    .error()
                    .with_message("attachment has no identifier")
                    .add_value_dbg_context(attachment.clone())
                    .add_field_context("attachment_identifier")
            })?
            .clone(),
        properties: crate::AttachmentProperties {
            content_type: attachment.content_type,
            size: attachment.size,
            file_name: attachment.file_name,
            width: attachment.width,
            height: attachment.height,
            caption: attachment.caption,
            blur_hash: attachment.blur_hash,
            uploaded: if let Some(t) = attachment.upload_timestamp {
                Some(timestamp(t)?)
            } else {
                None
            },
            thumbnail: attachment.thumbnail,
        },
        secrets: crate::AttachmentPointerSecrets {
            client_uuid: attachment.client_uuid,
            key: attachment.key,
            digest: attachment.digest,
            incremental_mac: attachment.incremental_mac,
            chunk_size: attachment.chunk_size,
            flags: attachment.flags,
            cdn_number: attachment.cdn_number,
        },
    })
}

pub fn attachment(
    attachment: libsignal_service::proto::AttachmentPointer,
) -> Result<AttachmentOrPointer> {
    Ok(AttachmentOrPointer::Pointer(attachment_pointer(
        attachment,
    )?))
}

pub fn device_info(info: presage::libsignal_service::websocket::account::DeviceInfo) -> DeviceInfo {
    DeviceInfo {
        id: info.id,
        name: info.name,
        created_at: info.created_at,
        last_seen: info.last_seen,
        secrets: crate::DeviceInfoSecrets {
            registration_id: info.registration_id,
        },
    }
}
