use crate::prelude::*;

use std::cell::OnceCell;
use std::hash::{DefaultHasher, Hash, Hasher};
#[cfg(not(feature = "screenshot"))]
use std::ops::RangeBounds;
use std::{collections::HashMap, ops::Bound, path::Path};

#[cfg(not(feature = "screenshot"))]
use chrono::{DateTime, Utc};
use fmanager::SignalClient;
use ftypes::AccountId;
use ftypes::ContactId;
use ftypes::DeviceId;
use ftypes::DeviceInfo;
use ftypes::MessageId;
use ftypes::TextMessage;
use ftypes::Thread;
use ftypes::ThreadId;
use ftypes::error::ErrorContext;
use ftypes::{AttachmentPointer, ErrorKind};
use futures::Stream;
use gio::Settings;
use gio::glib::{BoxedAnyObject, Bytes};
use gio::{Application, Notification};
use gtk::gdk::Texture;
use gtk::gdk_pixbuf::Pixbuf;
use oo7::Keyring;
use rand::distr::SampleString;
use url::Url;

use super::{Channel, Contact, Message};
use crate::{ApplicationError, tspawn};

const MESSAGES_INITIAL_LOAD: usize = 1;
const SCHEMA_ATTRIBUTE: &str = "xdg:schema";
const ATTRIBUTE_PASSWORD: (&str, &str) = ("type", "password");
const SECRET_LENGTH: usize = 64;

gtk::glib::wrapper! {
    /// The manager is the core of the logic of Flare.
    ///
    /// It is mostly a wrapper around [fmanager::Manager].
    /// It also has other functions, like caching the channels which are in use or sending notifications.
    pub struct Manager(ObjectSubclass<imp::Manager>);
}

type Store = fstore::DirectPresageWrapperStore;
type ManagerHandle = fmanager::ManagerHandle<Store>;

pub type SetupConfirmation = String;

#[derive(Debug)]
pub enum SetupResult {
    /// Setup must make a decision. Either register as primary device or link device.
    Pending(OnceCell<futures::channel::oneshot::Sender<fmanager::SetupDecision>>),

    /// The manager is pending a SMS confirmation code.
    Confirm(OnceCell<futures::channel::oneshot::Sender<SetupConfirmation>>),
    /// Display QR code to link.
    DisplayLinkQR(Url),
    /// Everything is finished
    Finished,
}

impl SignalClient<Store> for Manager {
    async fn request_setup_decision(&self) -> fmanager::SetupDecision {
        let (sender, receiver) = futures::channel::oneshot::channel();
        let cell = OnceCell::new();
        cell.set(sender).expect("Failed to set OnceCell");
        self.emit_by_name::<()>(
            "setup-result",
            &[&BoxedAnyObject::new(SetupResult::Pending(cell))],
        );
        receiver.await.expect("Failed to receive setup decision")
    }

    async fn request_qrcode_scan(&self, url: Url) {
        self.emit_by_name::<()>(
            "setup-result",
            &[&BoxedAnyObject::new(SetupResult::DisplayLinkQR(url))],
        );
    }

    async fn request_confirmation_code(&self) -> String {
        let (sender, receiver) = futures::channel::oneshot::channel();
        let cell = OnceCell::new();
        cell.set(sender).expect("Failed to set OnceCell");
        self.emit_by_name::<()>(
            "setup-result",
            &[&BoxedAnyObject::new(SetupResult::Confirm(cell))],
        );
        receiver.await.expect("Failed to receive confirmation code")
    }

    async fn setup_complete(&self, handle: ManagerHandle) {
        log::info!("Received setup-complete from the backend");
        self.imp().internal.borrow_mut().replace(handle);
        self.emit_by_name::<()>(
            "setup-result",
            &[&BoxedAnyObject::new(SetupResult::Finished)],
        );
        self.notify("is-primary");
    }

    async fn queue_empty(&self) {
        log::info!("Received queue-empty from the backend");
        let was_set_up = self.imp().finished_setup.replace(true);
        if !was_set_up {
            self.init_channels().await;

            // TODO: Send pending notifications.
            self.notify("finished-setup");
        }
    }

    async fn error(&self, error: ftypes::Error) {
        for context in error.context() {
            if let ErrorContext::Message(msg) = context {
                self.on_message_error(msg).await;
            }
        }

        if error.kind() != ErrorKind::NoInternet {
            self.emit_by_name::<()>(
                "error",
                &[&BoxedAnyObject::new(ApplicationError::from(error))],
            );
        }
    }

    fn on_message(&self, message: ftypes::ReceivedMessage) -> impl Future<Output = ()> {
        self.on_message_maybe_pending(message, false)
    }
}

/// Query the encryption password from the keyring, storing one if none exists.
async fn encryption_password() -> Result<String, ApplicationError> {
    let keyring = Keyring::new().await?;
    keyring.unlock().await?;
    let attributes = HashMap::from([
        (SCHEMA_ATTRIBUTE, crate::config::BASE_ID),
        ATTRIBUTE_PASSWORD,
    ]);

    log::trace!("Looking up password from libsecret");
    let items = keyring.search_items(&attributes).await?;
    let item = items.first();

    if let Some(item) = item {
        log::trace!("Password found");
        if item.is_locked().await? {
            log::trace!("Item is locked. Unlocking.");
            item.unlock().await?;
        }
        let secret_bytes = item.secret().await?;
        // Should normally not be lossy, but just in case
        let secret = String::from_utf8_lossy(&secret_bytes).into_owned();
        Ok(secret)
    } else {
        log::trace!("Password not found, creating password");
        let distribution = rand::distr::StandardUniform {};
        let secret = distribution.sample_string(&mut rand::rng(), SECRET_LENGTH);
        let secret_bytes = secret.as_bytes();
        log::trace!("Storing password");
        keyring
            .create_item(
                "Flare: Encryption password",
                &attributes,
                secret_bytes,
                true,
            )
            .await?;
        Ok(secret)
    }
}

/// Creating the configuration store at the specified path.
async fn config_store<P: AsRef<Path>>(p: &P) -> Result<Store, ApplicationError> {
    let path = p.as_ref();
    log::trace!("Initialize config store at {}", path.to_string_lossy());

    if path.exists() && !path.is_dir() {
        log::error!(
            "Store location already exists and is not a directory: {}",
            path.to_string_lossy()
        );
        return Err(ApplicationError::ConfigurationError(
            crate::ConfigurationError::DbPathNoFolder(path.to_owned()),
        ));
    }

    if !path.exists()
        && let Err(e) = std::fs::create_dir_all(path)
    {
        return Err(ApplicationError::ConfigurationError(
            crate::ConfigurationError::CannotCreateDbFolder(path.to_owned(), e),
        ));
    }

    let passphrase = tspawn!(async { encryption_password().await })
        .await
        .expect("Failed tokio join")?;
    let path = path.to_str().expect("Invalid sqlite store path").to_owned() + "db.sqlite";

    Ok(tspawn!(
        async move { fstore::DirectPresageWrapperStore::new(&path, Some(&passphrase),).await }
    )
    .await
    .expect("Failed tokio join")?)
}

impl Manager {
    pub fn new(application: Application) -> Manager {
        let s: Self = Object::new::<Self>();
        s.imp().application.borrow_mut().replace(application);
        s
    }

    /// Asynchronously initialize the manager. This will never return unless there is an error.
    ///
    /// This includes:
    /// - Setting up the configuration store.
    /// - Constructing the manager thread, reacting to any setup results or errors that happen.
    /// - Loading the stored channels.
    /// - Listening for messages or errors and propagating them to the correct channels.
    #[cfg(not(feature = "screenshot"))]
    pub async fn init<P: AsRef<Path>>(&self, p: &P) -> Result<(), ApplicationError> {
        let p = p.as_ref().to_owned();
        let (client_sender, client_receiver) = fmanager::client_channel::channel(self.clone());

        gspawn!(async {
            crate::TOKIO_RUNTIME.spawn_blocking(|| {
                crate::TOKIO_RUNTIME.block_on(async {
                    let local = tokio::task::LocalSet::new();
                    local
                        .run_until(async move {
                            log::trace!("Setting up the config store");
                            let config_store = config_store(&p)
                                .await
                                .expect("Failed to set up config store");

                            log::trace!("Setting up the manager");
                            let manager = fmanager::Manager::new(config_store, client_sender);
                            manager.receive().await
                        })
                        .await
                        .expect("Failed to spawn tokio local set");
                });
            });

            client_receiver.receive().await;
        });

        // Activate the tokio runtime.
        // This allows calling store-functions within the main thread.
        // This is extremely ugly, but sending things (especially the messages stream) between the threads is extremely annoying.
        // TODO: Can possibly still have a proper solution?
        let _guard = crate::TOKIO_RUNTIME.enter();
        std::future::pending::<()>().await;

        Ok(())
    }

    pub async fn shutdown(&self) {
        self.internal().shutdown_receive().await
    }

    async fn on_message_maybe_pending(&self, message: ftypes::ReceivedMessage, pending: bool) {
        log::trace!("Received message {:?}; pending: {}", message.id, pending);
        if let Some(msg) = message.as_rich_message() {
            log::trace!("Message is a rich message");
            let Some(message) = Message::from_ftypes(msg, self)
                .await
                .expect("Failed to construct message")
            else {
                return;
            };

            self.imp().last_message_datetime.replace(message.datetime());
            self.notify("last-message-datetime");

            message.set_pending(pending);

            let channel = message.channel();
            channel
                .new_message(message)
                .await
                .expect("Failed to add message to the channel");
        } else if let Some(msg) = message.as_amendment_message() {
            log::trace!("Message is an amendment message");
            // TODO: Error?
            let Ok(Some(channel)) = self.channel_from_id(msg.thread).await else {
                log::warn!("Got an amendment message from an unknown thread; ignoring");
                return;
            };

            for amended_message in msg.amends() {
                // TODO: Sender?
                let Some(amended) = channel
                    .timeline()
                    .get_by_timestamp(amended_message.timestamp)
                else {
                    log::trace!("Got an amendment for an unloaded message; ignoring");
                    continue;
                };

                amended.add_amendment(&msg);
            }
        } else {
            match &message.content {
                ftypes::ReceivedMessageContent::Meta(ftypes::MetaMessageContent::Typing(
                    typing,
                )) => {
                    let Ok(Some(channel)) = self.channel_from_id(message.thread).await else {
                        log::warn!("Got a typing message from an unknown thread; ignoring");
                        return;
                    };

                    let Ok(Some(sender)) = self.contact_from_id(message.sender()).await else {
                        log::warn!("Got a typing message from an unknown contact; ignoring");
                        return;
                    };

                    match typing.action {
                        ftypes::TypingAction::Started => {
                            channel.add_user_typing(crate::backend::channel::TypingNotification {
                                sender,
                                timestamp: typing.timestamp.timestamp_millis() as u64,
                            });
                        }
                        ftypes::TypingAction::Stopped => {
                            channel.remove_user_typing(sender);
                        }
                    }
                }
                ftypes::ReceivedMessageContent::Meta(ftypes::MetaMessageContent::GroupChange(
                    change,
                )) => {
                    if let Err(e) = self.update_channel(ThreadId::Group(change.group)).await {
                        log::error!(
                            "Failed to update group after change due to an error; ignoring: {e}"
                        );
                    }
                }
                ftypes::ReceivedMessageContent::Meta(
                    ftypes::MetaMessageContent::ContactChange(change),
                ) => {
                    if change.contact.in_group.is_none()
                        && let Err(e) = self
                            .update_channel(ThreadId::Contact(change.contact.account_id))
                            .await
                    {
                        log::error!(
                            "Failed to update contact after change due to an error; ignoring: {e}"
                        );
                    }

                    if let Err(e) = self.update_contact(change.contact).await {
                        log::error!(
                            "Failed to update contact after change due to an error; ignoring: {e}"
                        );
                    }
                }
                _ => {
                    log::warn!(
                        "Ignoring a message that cannot be handled by the frontend for now: {message:#?}"
                    );
                }
            }
        }
    }

    async fn on_message_error(&self, message: &ftypes::ReceivedMessage) {
        if let Some(message) = message.as_rich_message() {
            let Ok(Some(channel)) = self.channel_from_id(message.thread).await else {
                log::warn!("Could not find channel of erroring message; ignoring the error");
                return;
            };
            let Some(message) = channel.timeline().get_by_timestamp(message.id.timestamp) else {
                log::trace!("Cannot find message causing error in timeline; ignoring the error");
                return;
            };

            message.set_pending(false);
            message.set_error(true);
        } else if let Some(message) = message.as_amendment_message() {
            for amends in message.amends() {
                let Ok(Some(channel)) = self.channel_from_id(message.thread).await else {
                    log::warn!("Could not find channel of erroring message; ignoring the error");
                    return;
                };
                let Some(timeline_message) = channel.timeline().get_by_timestamp(amends.timestamp)
                else {
                    log::trace!(
                        "Cannot find message causing error in timeline; ignoring the error"
                    );
                    return;
                };
                timeline_message.remove_amendment(&message);
            }
        }
        // Ignore errors on meta messages, at least for now.
    }

    pub fn settings(&self) -> Settings {
        self.imp().settings.clone()
    }

    pub fn send_notification_for_channel(
        &self,
        id: Option<String>,
        channel: Channel,
        sender: Contact,
        body: String,
    ) {
        if sender.is_self() || body.is_empty() {
            // Skip notifications for messages sent from self or empty messages.
            return;
        }
        let (notification_title, notification_body) = if channel.is_group() {
            (channel.title(), format!("{}: {}", sender.title(), body))
        } else {
            (sender.title(), body)
        };

        let icon = Pixbuf::from_resource("/icon.svg").expect("Flare to have an application icon");

        let notification = Notification::new(&notification_title);
        notification.set_body(Some(&notification_body));
        notification.set_icon(&icon);
        notification.set_category(Some("im.received"));
        notification.set_default_action_and_target_value(
            "app.notification-clicked",
            Some(&self.channel_to_notification_variant(&channel).to_variant()),
        );

        self.send_notification(id, &notification);
    }

    pub fn send_notification(&self, id: Option<String>, notification: &gio::Notification) {
        if self.imp().settings.boolean("notifications")
            && let Some(application) = self.application()
        {
            if self.property("finished-setup") {
                log::trace!("Sending a notification");
                application.send_notification(id.as_deref(), notification);
            } else {
                log::trace!("Adding notification to the pending list");
                self.imp()
                    .pending_notifications
                    .borrow_mut()
                    .push((id, notification.clone()));
            }
        }
    }

    pub fn withdraw_notification(&self, id: &str) {
        if self.imp().settings.boolean("notifications")
            && let Some(application) = self.application()
        {
            if self.property("finished-setup") {
                log::trace!("Withdrawing notification");
                application.withdraw_notification(id);
            } else {
                log::trace!("Adding notification to the pending list");
                self.imp()
                    .pending_notifications
                    .borrow_mut()
                    .retain(|(s, _)| Some(id) != s.as_deref());
            }
        }
    }

    pub fn application(&self) -> Option<Application> {
        self.imp().application.borrow().clone()
    }

    pub async fn clear_registration(&self) -> Result<(), ApplicationError> {
        log::trace!("Clearing the manager");
        let internal = self.internal();
        tspawn!(async move { internal.clear_registration().await })
            .await
            .expect("Failed to spawn tokio")?;
        Ok(())
    }

    pub async fn clear_contents(&self) -> Result<(), ApplicationError> {
        log::trace!("Clearing the manager content");
        let internal = self.internal();
        tspawn!(async move { internal.clear_contents().await })
            .await
            .expect("Failed to spawn tokio")?;
        Ok(())
    }

    pub async fn clear_messages(&self) -> Result<(), ApplicationError> {
        log::trace!("Clearing messages from the manager");
        let internal = self.internal();
        tspawn!(async move { internal.clear_messages().await })
            .await
            .expect("Failed to spawn tokio")?;
        Ok(())
    }

    pub async fn clear_channel_messages(&self, thread: ThreadId) -> Result<(), ApplicationError> {
        log::trace!("Clearing channel messages the manager for thread {thread:?}",);
        let internal = self.internal();
        tspawn!(async move { internal.clear_thread_messages(thread).await })
            .await
            .expect("Failed to spawn tokio")?;
        Ok(())
    }

    pub async fn submit_recaptcha_challenge<S: AsRef<str>>(
        &self,
        token: S,
        captcha: S,
    ) -> Result<(), ApplicationError> {
        let token = token.as_ref().to_owned();
        let captcha = Url::try_from(captcha.as_ref()).map_err(|e| {
            ErrorKind::Protocol
                .caused_by(e)
                .with_message("captcha has invalid format; expected a `signalcaptcha://` link")
        })?;
        let internal = self.internal();
        tspawn!(async move {
            internal
                .submit_captcha(ftypes::CaptchaToken(token), captcha)
                .await
        })
        .await
        .expect("Failed to spawn tokio")?;
        Ok(())
    }

    #[cfg(not(feature = "screenshot"))]
    async fn message_inner(
        &self,
        id: MessageId,
    ) -> Result<Option<ftypes::RichMessage>, ftypes::Error> {
        self.internal().message(id).await
    }

    pub async fn message(&self, id: MessageId) -> Result<Option<Message>, ApplicationError> {
        crate::trace!("Querying message by id: {id:?}",);
        if let Some(message) = self.message_inner(id).await? {
            Ok(Message::from_ftypes(message, self).await?)
        } else {
            Ok(None)
        }
    }

    #[cfg(not(feature = "screenshot"))]
    async fn messages_inner(
        &self,
        thread: ThreadId,
        range: impl RangeBounds<DateTime<Utc>>,
    ) -> Result<impl Stream<Item = Result<ftypes::RichMessage, ftypes::Error>>, ftypes::Error> {
        Ok(async_stream::stream! {
            let internal = self.internal();
            for await m in internal.messages(thread, range).await? {
                yield m;
            }
        })
    }

    pub async fn messages<'a>(
        &'a self,
        thread: &ThreadId,
        from: Option<u64>,
    ) -> Result<impl Stream<Item = Result<Message, ftypes::Error>> + 'a, ApplicationError> {
        use chrono::{TimeZone, Utc};
        use futures::StreamExt;

        crate::trace!("Querying message by thread: {:?}, from {:?}", thread, from);
        let thread = *thread;
        Ok(async_stream::stream! {
            let result = self
                .messages_inner(
                    thread,
                    (
                        Bound::Unbounded,
                        from.map(|f| {
                            Bound::Excluded(
                                Utc.timestamp_millis_opt(f as i64)
                                    .single()
                                    .expect("Valid timestamp"),
                            )
                        })
                        .unwrap_or(Bound::Unbounded),
                    ),
                )
                .await?

                .then(move |m| async move {
                    match m {
                        Ok(m) => Message::from_ftypes(m, self).await,
                        Err(e) => Err(e),
                    }
                });

            for await m in result {
                let m = m.transpose();
                if let Some(m) = m {
                    yield m;
                }
            }
        })
    }

    fn internal(&self) -> ManagerHandle {
        self.internal_opt()
            .expect("Internal ManagerHandle to be set")
    }

    fn internal_opt(&self) -> Option<ManagerHandle> {
        self.imp().internal.borrow().as_ref().cloned()
    }

    pub fn available_channels(&self) -> Vec<Channel> {
        self.imp().channels.borrow().values().cloned().collect()
    }

    #[cfg(not(feature = "screenshot"))]
    async fn thread_avatar(&self, thread: &Thread) -> Result<Option<Texture>, ftypes::Error> {
        match self.internal().thread_avatar(thread).await {
            Ok(Some(bytes)) => {
                // TODO: Maybe don't ignore the error?
                let texture = Texture::from_bytes(&Bytes::from_owned(bytes)).ok();
                Ok(texture)
            }
            Ok(None) => Ok(None),
            Err(e) => Err(e),
        }
    }

    // TODO: Deduplicate with thread avatar
    #[cfg(not(feature = "screenshot"))]
    async fn contact_avatar(
        &self,
        contact: &ftypes::Contact,
    ) -> Result<Option<Texture>, ftypes::Error> {
        match self.internal().contact_avatar(contact).await {
            Ok(Some(bytes)) => {
                // TODO: Maybe don't ignore the error?
                let texture = Texture::from_bytes(&Bytes::from_owned(bytes)).ok();
                Ok(texture)
            }
            Ok(None) => Ok(None),
            Err(e) => Err(e),
        }
    }

    #[cfg(not(feature = "screenshot"))]
    async fn channels(&self) -> Result<Vec<ftypes::Thread>, ftypes::Error> {
        self.internal().threads().await
    }

    pub async fn init_channels(&self) -> bool {
        log::trace!("Trying to initialize channels");
        // TODO: Error handling.
        let threads = self.channels().await.expect("Failed to query threads");

        let channels: HashMap<_, _> = threads
            .into_iter()
            .map(|t| (t.id(), Channel::from_ftypes(t, self)))
            .collect();
        *self.imp().channels.borrow_mut() = channels.clone();

        let mut fut = vec![];

        for (_, c) in channels {
            self.emit_by_name::<()>("channel", &[&c]);
            let s = self.clone();
            fut.push(async move {
                c.load_last(MESSAGES_INITIAL_LOAD).await;
                log::info!("Query avatar for {:?}", c.thread());
                // TODO: Don't ignore errors?
                if let Ok(Some(avatar)) = s.thread_avatar(&c.inner()).await {
                    c.set_avatar(avatar);
                }
            });
        }

        futures::future::join_all(fut).await;

        true
    }

    pub async fn update_channel(&self, id: ThreadId) -> Result<(), ftypes::Error> {
        log::info!("Channel with id {:?} changed; reloading", id);
        let Some(channel) = self.channel_from_id(id).await? else {
            log::warn!("Cannot find the channel to update in flare; ignoring");
            return Ok(());
        };
        let Some(fchannel) = self.internal().thread(id).await? else {
            log::warn!("Cannot find the channel to update in flare-backend; ignoring");
            return Ok(());
        };

        channel.set_ftypes(fchannel);

        // TODO: Don't ignore errors?
        if let Ok(Some(avatar)) = self.thread_avatar(&channel.inner()).await {
            channel.set_avatar(avatar);
        }
        Ok(())
    }

    pub async fn update_contact(&self, id: ContactId) -> Result<(), ftypes::Error> {
        log::info!("Contact with id {:?} changed; reloading", id);
        let Some(contact) = self.contact_from_id(id).await? else {
            log::warn!("Cannot find the contact to update in flare; ignoring");
            return Ok(());
        };
        let Some(fcontact) = self.internal().contact(id).await? else {
            log::warn!("Cannot find the contact to update in flare-backend; ignoring");
            return Ok(());
        };

        contact.set_ftypes(fcontact);

        // TODO: Don't ignore errors?
        if let Ok(Some(avatar)) = self.contact_avatar(&contact.inner()).await {
            contact.set_avatar(avatar);
        }
        Ok(())
    }

    pub(super) async fn channel_from_id(
        &self,
        id: ThreadId,
    ) -> Result<Option<Channel>, ftypes::Error> {
        let channel = {
            let channels = self.imp().channels.borrow();
            channels.get(&id).cloned()
        };
        match channel {
            Some(c) => Ok(Some(c)),
            None => {
                let channel = self
                    .internal()
                    .thread(id)
                    .await?
                    .map(|t| Channel::from_ftypes(t, self));
                if let Some(channel) = &channel {
                    // Don't load last messages; if the channel was not available from initial loading, no previous messages exist yet.
                    log::info!("Query avatar for {:?}", channel.thread());
                    // TODO: Don't ignore errors?
                    if let Ok(Some(avatar)) = self.thread_avatar(&channel.inner()).await {
                        channel.set_avatar(avatar);
                    }
                    self.imp()
                        .channels
                        .borrow_mut()
                        .insert(channel.thread(), channel.clone());
                }
                Ok(channel)
            }
        }
    }

    #[cfg(not(feature = "screenshot"))]
    pub(super) async fn contact_from_id_inner(
        &self,
        id: ContactId,
    ) -> Result<Option<ftypes::Contact>, ftypes::Error> {
        self.internal().contact(id).await
    }

    pub(super) async fn contact_from_id(
        &self,
        id: ContactId,
    ) -> Result<Option<Contact>, ftypes::Error> {
        let contact = {
            let contacts = self.imp().contacts.borrow();
            contacts.get(&id).cloned()
        };
        match contact {
            Some(c) => Ok(Some(c)),
            None => {
                let contact = self
                    .contact_from_id_inner(id)
                    .await?
                    .map(|t| Contact::from_ftypes(t, self));
                if let Some(contact) = &contact {
                    log::info!("Query avatar for {:?}", contact.account_id());
                    // TODO: Don't ignore errors?
                    if let Ok(Some(avatar)) = self.contact_avatar(&contact.inner()).await {
                        contact.set_avatar(avatar);
                    }
                    self.imp()
                        .contacts
                        .borrow_mut()
                        .insert(contact.inner().id, contact.clone());
                }
                Ok(contact)
            }
        }
    }
}

impl Manager {
    pub async fn send_message(&self, thread: ThreadId, message: TextMessage) {
        log::trace!("`Manager::send_message` start");
        let internal = self.internal();
        let r = tspawn!(async move { internal.send_text_message(thread, message,).await })
            .await
            .expect("Failed to spawn tokio");
        log::trace!("`Manager::send_message` finished");
        self.on_message_maybe_pending(r, true).await;
    }

    pub async fn send_reaction(&self, thread: ThreadId, message: MessageId, reaction: String) {
        log::trace!("`Manager::send_reaction` start");
        let internal = self.internal();
        let r = tspawn!(async move { internal.send_reaction(thread, message, reaction).await })
            .await
            .expect("Failed to spawn tokio");
        log::trace!("`Manager::send_reaction` finished");
        self.on_message_maybe_pending(r, true).await;
    }

    pub async fn send_delete_for_everyone(&self, thread: ThreadId, message: MessageId) {
        log::trace!("`Manager::send_delete_for_everyone` start");
        let internal = self.internal();
        let r = tspawn!(async move { internal.send_delete_for_everyone(thread, message).await })
            .await
            .expect("Failed to spawn tokio");
        log::trace!("`Manager::send_delete_for_everyone` finished");
        self.on_message_maybe_pending(r, true).await;
    }

    pub(super) async fn send_session_reset(
        &self,
        recipient_addr: AccountId,
    ) -> Result<(), ApplicationError> {
        log::trace!("`Manager::send_session_reset` start");
        let internal = self.internal();
        tspawn!(async move { internal.send_session_reset(recipient_addr).await })
            .await
            .expect("Failed to spawn tokio");
        log::trace!("`Manager::send_session_reset` finished");
        Ok(())
    }

    pub(super) async fn get_attachment(
        &self,
        attachment_pointer: &AttachmentPointer,
    ) -> Result<Vec<u8>, ftypes::Error> {
        log::trace!("`Manager::get_attachment` start");
        let internal = self.internal();
        let attachment_pointer = attachment_pointer.clone();
        let r = tspawn!(async move { internal.attachment(attachment_pointer).await })
            .await
            .expect("Failed to spawn tokio");
        log::trace!("`Manager::get_attachment` finished");
        r
    }

    #[cfg(not(feature = "screenshot"))]
    pub(super) fn account_id(&self) -> AccountId {
        self.internal().own_account_id()
    }

    pub async fn request_contacts_sync(&self) -> Result<(), ApplicationError> {
        log::trace!("Requesting contacts sync",);
        let internal = self.internal();
        tspawn!(async move { internal.request_contacts_sync().await })
            .await
            .expect("Failed to spawn tokio");
        Ok(())
    }

    pub async fn link_secondary(&self, url: Url) -> Result<(), ftypes::Error> {
        log::trace!("`Manager::link_secondary` start");
        let internal = self.internal();
        let r = tspawn!(async move { internal.link_secondary(url).await })
            .await
            .expect("Failed to spawn tokio");
        log::trace!("`Manager::link_secondary` finished");
        r
    }

    pub async fn unlink_secondary(&self, id: DeviceId) -> Result<(), ftypes::Error> {
        log::trace!("`Manager::unlink_secondary` start");
        let internal = self.internal();
        let r = tspawn!(async move { internal.unlink_secondary(id).await })
            .await
            .expect("Failed to spawn tokio");
        log::trace!("`Manager::unlink_secondary` finished");
        r
    }

    #[cfg(not(feature = "screenshot"))]
    pub async fn devices(&self) -> Result<Vec<DeviceInfo>, ftypes::Error> {
        log::trace!("`Manager::devices` start");
        let internal = self.internal();
        let r = tspawn!(async move { internal.linked_devices().await })
            .await
            .expect("Failed to spawn tokio");
        log::trace!("`Manager::devices` finished");
        r
    }

    #[cfg(not(feature = "screenshot"))]
    pub fn is_primary(&self) -> bool {
        self.internal_opt().is_some_and(|h| h.is_primary_device())
    }

    pub fn channel_to_notification_variant(&self, channel: &Channel) -> u64 {
        let thread = channel.thread();
        let mut hasher = DefaultHasher::new();
        thread.hash(&mut hasher);
        hasher.finish()
    }

    pub fn notification_variant_to_channel(&self, variant: u64) -> Option<Channel> {
        self.imp()
            .channels
            .borrow()
            .iter()
            .filter(|(_thread, channel)| self.channel_to_notification_variant(channel) == variant)
            .map(|(_, channel)| channel.clone())
            .next()
    }
}

mod imp {
    use crate::{backend::Contact, prelude::*};
    use std::collections::HashMap;

    use ftypes::{ContactId, ThreadId};
    use gio::{Application, Notification, Settings, glib::DateTime};
    use glib::{BoxedAnyObject, ParamSpec, ParamSpecBoolean, Value};

    use crate::{
        backend::{Channel, Message},
        config::BASE_ID,
    };

    pub struct Manager {
        pub(super) internal: RefCell<Option<super::ManagerHandle>>,
        pub(super) channels: RefCell<HashMap<ThreadId, Channel>>,
        pub(super) contacts: RefCell<HashMap<ContactId, Contact>>,
        pub(super) finished_setup: Cell<bool>,
        pub(super) pending_notifications: RefCell<Vec<(Option<String>, Notification)>>,
        pub(super) last_message_datetime: RefCell<Option<DateTime>>,
        pub(super) settings: Settings,
        pub(super) application: RefCell<Option<Application>>,
    }

    impl Default for Manager {
        fn default() -> Self {
            Self {
                internal: Default::default(),
                channels: Default::default(),
                contacts: Default::default(),
                finished_setup: Default::default(),
                pending_notifications: Default::default(),
                last_message_datetime: Default::default(),
                settings: Settings::new(BASE_ID),
                application: Default::default(),
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for Manager {
        const NAME: &'static str = "FlManager";
        type Type = super::Manager;
    }

    impl ObjectImpl for Manager {
        fn properties() -> &'static [glib::ParamSpec] {
            static PROPERTIES: Lazy<Vec<ParamSpec>> = Lazy::new(|| {
                vec![
                    ParamSpecBoolean::builder("is-primary").read_only().build(),
                    ParamSpecBoolean::builder("finished-setup")
                        .read_only()
                        .build(),
                    ParamSpecBoolean::builder("last-message-datetime")
                        .read_only()
                        .build(),
                ]
            });
            PROPERTIES.as_ref()
        }

        fn property(&self, _id: usize, pspec: &ParamSpec) -> Value {
            match pspec.name() {
                "is-primary" => self.obj().is_primary().to_value(),
                "finished-setup" => self.finished_setup.get().to_value(),
                "last-message-datetime" => self.last_message_datetime.borrow().to_value(),
                _ => unimplemented!(),
            }
        }

        fn set_property(&self, _id: usize, _value: &Value, _pspec: &ParamSpec) {
            unimplemented!()
        }

        fn signals() -> &'static [Signal] {
            static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| -> Vec<Signal> {
                vec![
                    Signal::builder("message")
                        .param_types([Message::static_type()])
                        .build(),
                    Signal::builder("channel")
                        .param_types([Channel::static_type()])
                        .build(),
                    Signal::builder("setup-result")
                        .param_types([BoxedAnyObject::static_type()])
                        .build(),
                    Signal::builder("error")
                        .param_types([BoxedAnyObject::static_type()])
                        .build(),
                ]
            });
            SIGNALS.as_ref()
        }
    }
}
