use std::ops::RangeBounds;

use chrono::{DateTime, DurationRound, TimeDelta, Utc};
use ftypes::{
    AccountId, AttachmentPointer, CaptchaToken, Contact, ContactId, DeviceId, DeviceInfo, Group,
    GroupId, Message, MessageId, ReceivedMessage, ReceivedMessageContent, RegistrationData, Result,
    Store, TextMessage, Thread, ThreadId,
};
use futures::{SinkExt, Stream};
use url::Url;

use crate::Function;

macro_rules! forward_from_store {
    ($name:ident ($($kw:ident: $t:ty),*) -> $type:ty) => {
    pub fn $name(&self, $($kw: $t,)*) -> impl Future<Output = Result<$type>> {
        self.store.$name($($kw,)*)
    }
    }
}

macro_rules! send_and_receive {
    ($s:expr, Function::$name:tt ($($arg:expr),*)) => {{
        let (sender, receiver) = futures::channel::oneshot::channel();
        log::trace!("Sending request for {}", stringify!($name));
        $s.send(Function::$name($($arg,)* sender))
            .await;
        log::trace!("Awaiting response for {}", stringify!($name));
        receiver
            .await
            .expect("Failed to receive results from the manager")
    }}
}

#[derive(Clone)]
pub struct ManagerHandle<S: Store> {
    pub(crate) sender: futures::channel::mpsc::UnboundedSender<Function>,
    pub(crate) store: S,
    pub(crate) registration_data: RegistrationData,
}

impl<S: Store> ManagerHandle<S> {
    pub(crate) async fn new(
        sender: futures::channel::mpsc::UnboundedSender<Function>,
        store: S,
    ) -> Result<Self> {
        Ok(Self {
            sender,
            registration_data: store.registration_data().await?,
            store,
        })
    }

    async fn send(&self, function: Function) {
        self.sender
            .clone()
            .send(function)
            .await
            .expect("ManagerHandle was used after Manager exited");
    }

    forward_from_store!(group(id: GroupId) -> Option::<Group>);
    forward_from_store!(groups() -> Vec::<Group>);
    forward_from_store!(contact(id: ContactId) -> Option::<Contact>);
    forward_from_store!(contacts() -> Vec::<Contact>);
    forward_from_store!(thread(id: ThreadId) -> Option::<Thread>);
    forward_from_store!(threads() -> Vec::<Thread>);
    forward_from_store!(messages(id: ThreadId, range: impl RangeBounds<DateTime<Utc>>) -> impl Stream<Item = Result<ftypes::RichMessage>>);
    forward_from_store!(message(id: MessageId) -> Option<ftypes::RichMessage>);

    forward_from_store!(clear_registration() -> ());
    forward_from_store!(clear_contents() -> ());
    forward_from_store!(clear_messages() -> ());
    forward_from_store!(clear_thread_messages(thread: ThreadId) -> ());

    pub fn own_account_id(&self) -> AccountId {
        self.registration_data.account
    }

    pub fn own_device_id(&self) -> DeviceId {
        self.registration_data.device_id
    }

    pub fn is_primary_device(&self) -> bool {
        u32::from(self.own_device_id()) == 1
    }

    pub async fn send_message(
        &self,
        thread: ThreadId,
        content: ReceivedMessageContent,
    ) -> ReceivedMessage {
        let timestamp = chrono::Utc::now();
        // Need to round the timestamp to the closest millisecond, as otherwise the local message ID would be more precise than the one sent to the Signal servers.
        let timestamp = timestamp
            .duration_trunc(TimeDelta::milliseconds(1))
            .expect("Failed to round timestamp");

        let account_id = self.own_account_id();
        let device_id = self.own_device_id();

        let message_id = ftypes::MessageId {
            sender: account_id,
            timestamp,
            sub_id: ftypes::MessageSubId::Primary,
        };
        let metadata = ftypes::MessageMetadata {
            needs_receipt: false,
            unidentified_sender: false,
            was_plaintext: false,
            server_guid: None,
            sender_device_id: device_id,
        };
        let message = Message {
            id: message_id,
            thread,
            metadata,
            content,
        };

        self.send(Function::SendMessage(thread, message.clone()))
            .await;
        message
    }

    pub fn send_text_message(
        &self,
        thread: ThreadId,
        message: TextMessage,
    ) -> impl Future<Output = ReceivedMessage> {
        self.send_message(
            thread,
            ReceivedMessageContent::Main(ftypes::MainMessageContent::Text(message)),
        )
    }

    pub fn send_reaction<STR: AsRef<str>>(
        &self,
        thread: ThreadId,
        message: MessageId,
        reaction: STR,
    ) -> impl Future<Output = ReceivedMessage> {
        self.send_message(
            thread,
            ReceivedMessageContent::Amendment(ftypes::AmendmentMessageContent::Reaction(
                ftypes::ReactionMessage {
                    reaction_to_message: message,
                    emoji: reaction.as_ref().to_owned(),
                    operation: ftypes::ReactionOperation::Add,
                },
            )),
        )
    }

    pub async fn send_delete_for_everyone(
        &self,
        thread: ThreadId,
        message: MessageId,
    ) -> ReceivedMessage {
        if message.sender == self.own_account_id() {
            self.send_message(
                thread,
                ReceivedMessageContent::Amendment(ftypes::AmendmentMessageContent::Deletion(
                    ftypes::DeletionMessage {
                        deleted_message: message,
                    },
                )),
            )
            .await
        } else {
            panic!(
                "Was asked to send a deletion message for a message that was sent by another person; this should not happen"
            );
        }
    }

    pub fn send_remove_reaction(
        &self,
        thread: ThreadId,
        message: MessageId,
    ) -> impl Future<Output = ReceivedMessage> {
        self.send_message(
            thread,
            ReceivedMessageContent::Amendment(ftypes::AmendmentMessageContent::Reaction(
                ftypes::ReactionMessage {
                    reaction_to_message: message,
                    emoji: "".to_string(),
                    operation: ftypes::ReactionOperation::Remove,
                },
            )),
        )
    }

    pub fn send_session_reset(&self, account: AccountId) -> impl Future<Output = ()> {
        self.send(Function::SendSessionReset(account, Utc::now()))
    }

    pub async fn contact_avatar(&self, contact: &Contact) -> Result<Option<Vec<u8>>> {
        send_and_receive!(self, Function::ContactAvatar(contact.id, contact.secrets))
    }

    pub async fn group_avatar(&self, group: &Group) -> Result<Option<Vec<u8>>> {
        send_and_receive!(self, Function::GroupAvatar(group.id, group.secrets))
    }

    pub async fn thread_avatar(&self, thread: &Thread) -> Result<Option<Vec<u8>>> {
        match thread {
            Thread::Contact(contact) => self.contact_avatar(contact).await,
            Thread::Group(group) => self.group_avatar(group).await,
        }
    }

    pub async fn attachment(&self, attachment: AttachmentPointer) -> Result<Vec<u8>> {
        send_and_receive!(self, Function::Attachment(attachment))
    }

    pub fn request_contacts_sync(&self) -> impl Future<Output = ()> {
        self.send(Function::RequestContacts)
    }

    pub async fn linked_devices(&self) -> Result<Vec<DeviceInfo>> {
        send_and_receive!(self, Function::LinkedDevices())
    }

    pub async fn link_secondary(&self, url: Url) -> Result<()> {
        send_and_receive!(self, Function::LinkSecondary(url))
    }

    pub async fn unlink_secondary(&self, id: DeviceId) -> Result<()> {
        send_and_receive!(self, Function::UnlinkSecondary(id))
    }

    /// Submit a captcha filled out at <https://signalcaptchas.org/challenge/generate.html>.
    /// The result should be a `signalcaptcha://` link.
    pub async fn submit_captcha(&self, token: CaptchaToken, captcha: Url) -> Result<()> {
        send_and_receive!(self, Function::SubmitCaptcha(token, captcha))
    }

    /// Can be used e.g. when the network changes, which can be faster than waiting for the connection to time out.
    pub fn restart_receive(&self) -> impl Future<Output = ()> {
        self.send(Function::RestartReceive)
    }

    /// Shutdown receiving. Using any handle after calling this function is undefined behavior and may panic.
    pub async fn shutdown_receive(&self) {
        send_and_receive!(self, Function::ShutdownReceive())
    }
}
