use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{gdk, glib, glib::clone, CompositeTemplate};
use ruma::{events::room::power_levels::PowerLevelUserAction, OwnedEventId};

use crate::{
    components::{
        confirm_mute_room_member_dialog, confirm_room_member_destructive_action_dialog, Avatar,
        RoomMemberDestructiveAction, UserProfileDialog,
    },
    gettext_f, ngettext_f,
    prelude::*,
    session::{
        model::{Member, MemberRole, Membership, User},
        view::content::RoomHistory,
    },
    toast,
    utils::{add_activate_binding_action, BoundObject},
    Window,
};

mod imp {
    use std::cell::{Cell, RefCell};

    use glib::subclass::InitializingObject;

    use super::*;

    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
    #[template(
        resource = "/org/gnome/Fractal/ui/session/view/content/room_history/sender_avatar/mod.ui"
    )]
    #[properties(wrapper_type = super::SenderAvatar)]
    pub struct SenderAvatar {
        #[template_child]
        pub avatar: TemplateChild<Avatar>,
        #[template_child]
        pub user_id_btn: TemplateChild<gtk::Button>,
        /// Whether this avatar is active.
        ///
        /// This avatar is active when the popover is displayed.
        #[property(get)]
        pub active: Cell<bool>,
        pub permissions_handler: RefCell<Option<glib::SignalHandlerId>>,
        /// The displayed member.
        #[property(get, set = Self::set_sender, explicit_notify, nullable)]
        pub sender: BoundObject<Member>,
        /// The popover of this avatar.
        pub(super) popover: BoundObject<gtk::PopoverMenu>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for SenderAvatar {
        const NAME: &'static str = "ContentSenderAvatar";
        type Type = super::SenderAvatar;
        type ParentType = gtk::Widget;

        fn class_init(klass: &mut Self::Class) {
            Self::bind_template(klass);
            Self::Type::bind_template_callbacks(klass);

            klass.set_layout_manager_type::<gtk::BinLayout>();
            klass.set_css_name("sender-avatar");
            klass.set_accessible_role(gtk::AccessibleRole::ToggleButton);

            klass.install_action("sender-avatar.copy-user-id", None, |widget, _, _| {
                if let Some(popover) = widget.imp().popover.obj() {
                    popover.popdown();
                }

                let Some(sender) = widget.sender() else {
                    return;
                };

                widget.clipboard().set_text(sender.user_id().as_str());
                toast!(widget, gettext("Matrix user ID copied to clipboard"));
            });

            klass.install_action("sender-avatar.mention", None, |widget, _, _| {
                widget.mention();
            });

            klass.install_action_async(
                "sender-avatar.open-direct-chat",
                None,
                |widget, _, _| async move {
                    widget.open_direct_chat().await;
                },
            );

            klass.install_action("sender-avatar.permalink", None, |widget, _, _| {
                let Some(sender) = widget.sender() else {
                    return;
                };

                widget
                    .clipboard()
                    .set_text(&sender.matrix_to_uri().to_string());
                toast!(widget, gettext("Link copied to clipboard"));
            });

            klass.install_action_async("sender-avatar.invite", None, |widget, _, _| async move {
                widget.invite().await;
            });

            klass.install_action_async(
                "sender-avatar.revoke-invite",
                None,
                |widget, _, _| async move {
                    widget.kick().await;
                },
            );

            klass.install_action_async("sender-avatar.mute", None, |obj, _, _| async move {
                obj.toggle_muted().await;
            });

            klass.install_action_async("sender-avatar.unmute", None, |obj, _, _| async move {
                obj.toggle_muted().await;
            });

            klass.install_action_async("sender-avatar.kick", None, |widget, _, _| async move {
                widget.kick().await;
            });

            klass.install_action_async(
                "sender-avatar.deny-access",
                None,
                |widget, _, _| async move {
                    widget.kick().await;
                },
            );

            klass.install_action_async("sender-avatar.ban", None, |widget, _, _| async move {
                widget.ban().await;
            });

            klass.install_action_async("sender-avatar.unban", None, |widget, _, _| async move {
                widget.unban().await;
            });

            klass.install_action_async(
                "sender-avatar.remove-messages",
                None,
                |widget, _, _| async move {
                    widget.remove_messages().await;
                },
            );

            klass.install_action_async("sender-avatar.ignore", None, |widget, _, _| async move {
                widget.toggle_ignored().await;
            });

            klass.install_action_async(
                "sender-avatar.stop-ignoring",
                None,
                |widget, _, _| async move {
                    widget.toggle_ignored().await;
                },
            );

            klass.install_action("sender-avatar.view-details", None, |widget, _, _| {
                widget.view_details();
            });

            klass.install_action("sender-avatar.activate", None, |widget, _, _| {
                widget.show_popover(1, 0.0, 0.0);
            });

            add_activate_binding_action(klass, "sender-avatar.activate");
        }

        fn instance_init(obj: &InitializingObject<Self>) {
            obj.init_template();
        }
    }

    #[glib::derived_properties]
    impl ObjectImpl for SenderAvatar {
        fn constructed(&self) {
            self.parent_constructed();
            let obj = self.obj();

            obj.set_pressed_state(false);
        }

        fn dispose(&self) {
            if let Some(popover) = self.popover.obj() {
                popover.unparent();
                popover.remove_child(&*self.user_id_btn);
            }

            self.avatar.unparent();
        }
    }

    impl WidgetImpl for SenderAvatar {}

    impl AccessibleImpl for SenderAvatar {
        fn first_accessible_child(&self) -> Option<gtk::Accessible> {
            // Hide the children in the a11y tree.
            None
        }
    }

    impl SenderAvatar {
        /// Set the list of room members.
        fn set_sender(&self, sender: Option<Member>) {
            let prev_sender = self.sender.obj();

            if prev_sender == sender {
                return;
            }

            self.sender.disconnect_signals();

            if let Some(sender) = sender {
                let permissions_handler = sender.room().permissions().connect_changed(clone!(
                    #[weak(rename_to = imp)]
                    self,
                    move |_| {
                        imp.update_actions();
                    }
                ));
                self.permissions_handler.replace(Some(permissions_handler));

                let display_name_handler = sender.connect_display_name_notify(clone!(
                    #[weak(rename_to = imp)]
                    self,
                    move |_| {
                        imp.update_accessible_label();
                    }
                ));

                let membership_handler = sender.connect_membership_notify(clone!(
                    #[weak(rename_to = imp)]
                    self,
                    move |_| {
                        imp.update_actions();
                    }
                ));

                let power_level_handler = sender.connect_power_level_notify(clone!(
                    #[weak(rename_to = imp)]
                    self,
                    move |_| {
                        imp.update_actions();
                    }
                ));

                let is_ignored_handler = sender.connect_is_ignored_notify(clone!(
                    #[weak(rename_to = imp)]
                    self,
                    move |_| {
                        imp.update_actions();
                    }
                ));

                self.sender.set(
                    sender,
                    vec![
                        display_name_handler,
                        membership_handler,
                        power_level_handler,
                        is_ignored_handler,
                    ],
                );
                self.update_accessible_label();
                self.update_actions();
            }

            self.obj().notify_sender();
        }

        /// Update the accessible label for the current sender.
        fn update_accessible_label(&self) {
            let Some(sender) = self.sender.obj() else {
                return;
            };

            let label = gettext_f("{user}’s avatar", &[("user", &sender.display_name())]);
            self.obj()
                .update_property(&[gtk::accessible::Property::Label(&label)]);
        }

        /// Update the actions for the current state.
        fn update_actions(&self) {
            let Some(sender) = self.sender.obj() else {
                return;
            };
            let obj = self.obj();

            let permissions = sender.room().permissions();
            let membership = sender.membership();
            let sender_id = sender.user_id();
            let is_own_user = sender.is_own_user();
            let power_level = sender.power_level();
            let role = permissions.role(power_level);

            obj.action_set_enabled(
                "sender-avatar.mention",
                !is_own_user && membership == Membership::Join && permissions.can_send_message(),
            );

            obj.action_set_enabled("sender-avatar.open-direct-chat", !is_own_user);

            obj.action_set_enabled(
                "sender-avatar.invite",
                !is_own_user
                    && matches!(membership, Membership::Leave | Membership::Knock)
                    && permissions.can_do_to_user(sender_id, PowerLevelUserAction::Kick),
            );

            obj.action_set_enabled(
                "sender-avatar.revoke-invite",
                !is_own_user
                    && membership == Membership::Invite
                    && permissions.can_do_to_user(sender_id, PowerLevelUserAction::Kick),
            );

            obj.action_set_enabled(
                "sender-avatar.mute",
                !is_own_user
                    && role != MemberRole::Muted
                    && permissions.default_power_level() > permissions.mute_power_level()
                    && permissions
                        .can_do_to_user(sender_id, PowerLevelUserAction::ChangePowerLevel),
            );

            obj.action_set_enabled(
                "sender-avatar.unmute",
                !is_own_user
                    && role == MemberRole::Muted
                    && permissions.default_power_level() > permissions.mute_power_level()
                    && permissions
                        .can_do_to_user(sender_id, PowerLevelUserAction::ChangePowerLevel),
            );

            obj.action_set_enabled(
                "sender-avatar.kick",
                !is_own_user
                    && membership == Membership::Join
                    && permissions.can_do_to_user(sender_id, PowerLevelUserAction::Kick),
            );

            obj.action_set_enabled(
                "sender-avatar.deny-access",
                !is_own_user
                    && membership == Membership::Knock
                    && permissions.can_do_to_user(sender_id, PowerLevelUserAction::Kick),
            );

            obj.action_set_enabled(
                "sender-avatar.ban",
                !is_own_user
                    && membership != Membership::Ban
                    && permissions.can_do_to_user(sender_id, PowerLevelUserAction::Ban),
            );

            obj.action_set_enabled(
                "sender-avatar.unban",
                !is_own_user
                    && membership == Membership::Ban
                    && permissions.can_do_to_user(sender_id, PowerLevelUserAction::Unban),
            );

            obj.action_set_enabled(
                "sender-avatar.remove-messages",
                !is_own_user && permissions.can_redact_other(),
            );

            obj.action_set_enabled("sender-avatar.ignore", !is_own_user && !sender.is_ignored());

            obj.action_set_enabled(
                "sender-avatar.stop-ignoring",
                !is_own_user && sender.is_ignored(),
            );
        }

        pub(super) fn set_popover(&self, popover: Option<gtk::PopoverMenu>) {
            let old_popover = self.popover.obj();

            if old_popover == popover {
                return;
            }
            let obj = self.obj();

            // Reset the state.
            if let Some(popover) = old_popover {
                popover.unparent();
                popover.remove_child(&*self.user_id_btn);
            }
            self.popover.disconnect_signals();
            obj.set_active(false);

            if let Some(popover) = popover {
                // We need to remove the popover from the previous button, if any.
                if popover.parent().is_some() {
                    popover.unparent();
                }

                let parent_handler = popover.connect_parent_notify(clone!(
                    #[weak]
                    obj,
                    move |popover| {
                        if !popover.parent().is_some_and(|w| w == obj) {
                            let imp = obj.imp();

                            imp.popover.disconnect_signals();
                            popover.remove_child(&*imp.user_id_btn);
                        }
                    }
                ));
                let closed_handler = popover.connect_closed(clone!(
                    #[weak]
                    obj,
                    move |_| {
                        obj.set_active(false);
                    }
                ));

                popover.add_child(&*self.user_id_btn, "user-id");
                popover.set_parent(&*obj);

                self.popover
                    .set(popover, vec![parent_handler, closed_handler]);
            }
        }
    }
}

glib::wrapper! {
    /// An avatar with a popover menu for room members.
    pub struct SenderAvatar(ObjectSubclass<imp::SenderAvatar>)
        @extends gtk::Widget, @implements gtk::Accessible;
}

#[gtk::template_callbacks]
impl SenderAvatar {
    pub fn new() -> Self {
        glib::Object::new()
    }

    /// Set whether this active is active.
    fn set_active(&self, active: bool) {
        if self.active() == active {
            return;
        }

        self.imp().active.set(active);
        self.notify_active();
        self.set_pressed_state(active);
    }

    /// Set the CSS and a11 states.
    fn set_pressed_state(&self, pressed: bool) {
        if pressed {
            self.set_state_flags(gtk::StateFlags::CHECKED, false);
        } else {
            self.unset_state_flags(gtk::StateFlags::CHECKED);
        }

        let tristate = if pressed {
            gtk::AccessibleTristate::True
        } else {
            gtk::AccessibleTristate::False
        };
        self.update_state(&[gtk::accessible::State::Pressed(tristate)]);
    }

    /// Handle a click on the container.
    ///
    /// Shows a popover with the room member menu.
    #[template_callback]
    fn show_popover(&self, _n_press: i32, x: f64, y: f64) {
        let Some(room_history) = self
            .ancestor(RoomHistory::static_type())
            .and_downcast::<RoomHistory>()
        else {
            return;
        };

        self.set_active(true);

        let popover = room_history.sender_context_menu();
        self.imp().set_popover(Some(popover.clone()));

        popover.set_pointing_to(Some(&gdk::Rectangle::new(x as i32, y as i32, 0, 0)));
        popover.popup();
    }

    /// Add a mention of the sender to the message composer.
    fn mention(&self) {
        let Some(sender) = self.sender() else {
            return;
        };
        let Some(room_history) = self
            .ancestor(RoomHistory::static_type())
            .and_downcast::<RoomHistory>()
        else {
            return;
        };

        room_history.message_toolbar().mention_member(&sender);
    }

    /// View the sender details.
    fn view_details(&self) {
        let Some(sender) = self.sender() else {
            return;
        };

        let dialog = UserProfileDialog::new();
        dialog.set_room_member(sender);
        dialog.present(Some(self));
    }

    /// Open a direct chat with the current sender.
    ///
    /// If one doesn't exist already, it is created.
    async fn open_direct_chat(&self) {
        let Some(sender) = self.sender().and_upcast::<User>() else {
            return;
        };

        let room = if let Some(room) = sender.direct_chat() {
            room
        } else {
            toast!(self, &gettext("Creating a new Direct Chat…"));

            match sender.get_or_create_direct_chat().await {
                Ok(room) => room,
                Err(_) => {
                    toast!(self, &gettext("Could not create a new Direct Chat"));
                    return;
                }
            }
        };

        let Some(main_window) = self.root().and_downcast::<Window>() else {
            return;
        };

        main_window.show_room(sender.session().session_id(), room.room_id());
    }

    /// Invite the sender to the room.
    async fn invite(&self) {
        let Some(sender) = self.sender() else {
            return;
        };

        toast!(self, gettext("Inviting user…"));

        let room = sender.room();
        let user_id = sender.user_id().clone();
        if room.invite(&[user_id]).await.is_err() {
            toast!(self, gettext("Could not invite user"));
        }
    }

    /// Kick the user from the room.
    async fn kick(&self) {
        let Some(sender) = self.sender() else {
            return;
        };

        let Some(response) = confirm_room_member_destructive_action_dialog(
            &sender,
            RoomMemberDestructiveAction::Kick,
            self,
        )
        .await
        else {
            return;
        };

        let membership = sender.membership();

        let label = match membership {
            Membership::Invite => gettext("Revoking invite…"),
            Membership::Knock => gettext("Denying access…"),
            _ => gettext("Kicking user…"),
        };
        toast!(self, label);

        let room = sender.room();
        let user_id = sender.user_id().clone();
        if room.kick(&[(user_id, response.reason)]).await.is_err() {
            let error = match membership {
                Membership::Invite => gettext("Could not revoke invite of user"),
                Membership::Knock => gettext("Could not deny access to user"),
                _ => gettext("Could not kick user"),
            };
            toast!(self, error);
        }
    }

    /// (Un)mute the user in the room.
    async fn toggle_muted(&self) {
        let Some(sender) = self.sender() else {
            return;
        };

        let old_power_level = sender.power_level();
        let permissions = sender.room().permissions();

        // Warn if user is muted but was not before.
        let mute_power_level = permissions.mute_power_level();
        let mute = old_power_level > mute_power_level;
        if mute && !confirm_mute_room_member_dialog(&sender, self).await {
            return;
        }

        let user_id = sender.user_id().clone();

        let new_power_level = if mute {
            toast!(self, gettext("Muting member…"));
            mute_power_level
        } else {
            toast!(self, gettext("Unmuting member…"));
            permissions.default_power_level()
        };

        match permissions
            .set_user_power_level(user_id, new_power_level)
            .await
        {
            Ok(_) => {
                if mute {
                    toast!(self, gettext("Member muted"));
                } else {
                    toast!(self, gettext("Member unmuted"));
                }
            }
            Err(_) => {
                if mute {
                    toast!(self, gettext("Could not mute member"));
                } else {
                    toast!(self, gettext("Could not unmute member"));
                }
            }
        }
    }

    /// Ban the room member.
    async fn ban(&self) {
        let Some(sender) = self.sender() else {
            return;
        };

        let permissions = sender.room().permissions();
        let redactable_events = if permissions.can_redact_other() {
            sender.redactable_events()
        } else {
            vec![]
        };

        let Some(response) = confirm_room_member_destructive_action_dialog(
            &sender,
            RoomMemberDestructiveAction::Ban(redactable_events.len()),
            self,
        )
        .await
        else {
            return;
        };

        toast!(self, gettext("Banning user…"));

        let room = sender.room();
        let user_id = sender.user_id().clone();
        if room
            .ban(&[(user_id, response.reason.clone())])
            .await
            .is_err()
        {
            toast!(self, gettext("Could not ban user"));
        }

        if response.remove_events {
            self.remove_known_messages_inner(&sender, redactable_events, response.reason)
                .await;
        }
    }

    /// Unban the room member.
    async fn unban(&self) {
        let Some(sender) = self.sender() else {
            return;
        };

        toast!(self, gettext("Unbanning user…"));

        let room = sender.room();
        let user_id = sender.user_id().clone();
        if room.unban(&[(user_id, None)]).await.is_err() {
            toast!(self, gettext("Could not unban user"));
        }
    }

    /// Remove the known events of the room member.
    async fn remove_messages(&self) {
        let Some(sender) = self.sender() else {
            return;
        };

        let redactable_events = sender.redactable_events();

        let Some(response) = confirm_room_member_destructive_action_dialog(
            &sender,
            RoomMemberDestructiveAction::RemoveMessages(redactable_events.len()),
            self,
        )
        .await
        else {
            return;
        };

        self.remove_known_messages_inner(&sender, redactable_events, response.reason)
            .await;
    }

    async fn remove_known_messages_inner(
        &self,
        sender: &Member,
        events: Vec<OwnedEventId>,
        reason: Option<String>,
    ) {
        let n = u32::try_from(events.len()).unwrap_or(u32::MAX);
        toast!(
            self,
            ngettext_f(
                // Translators: Do NOT translate the content between '{' and '}',
                // this is a variable name.
                "Removing 1 message sent by the user…",
                "Removing {n} messages sent by the user…",
                n,
                &[("n", &n.to_string())]
            )
        );

        let room = sender.room();

        if let Err(failed_events) = room.redact(&events, reason).await {
            let n = u32::try_from(failed_events.len()).unwrap_or(u32::MAX);
            toast!(
                self,
                ngettext_f(
                    // Translators: Do NOT translate the content between '{' and '}',
                    // this is a variable name.
                    "Could not remove 1 message sent by the user",
                    "Could not remove {n} messages sent by the user",
                    n,
                    &[("n", &n.to_string())]
                )
            );
        }
    }

    /// Toggle whether the user is ignored or not.
    async fn toggle_ignored(&self) {
        let Some(sender) = self.sender().and_upcast::<User>() else {
            return;
        };
        let is_ignored = sender.is_ignored();

        let label = if is_ignored {
            gettext("Stop ignoring user…")
        } else {
            gettext("Ignoring user…")
        };
        toast!(self, label);

        if is_ignored {
            if sender.stop_ignoring().await.is_err() {
                toast!(self, gettext("Could not stop ignoring user"));
            }
        } else if sender.ignore().await.is_err() {
            toast!(self, gettext("Could not ignore user"));
        }
    }
}
