use std::fmt;

use gettextrs::gettext;
use gtk::{
    glib,
    glib::{clone, closure_local},
    prelude::*,
    subclass::prelude::*,
};
use matrix_sdk::{
    deserialized_responses::SyncOrStrippedState, event_handler::EventHandlerDropGuard,
};
use ruma::{
    events::{
        room::power_levels::{
            PowerLevelAction, PowerLevelUserAction, RoomPowerLevels, RoomPowerLevelsEventContent,
        },
        MessageLikeEventType, StateEventType, SyncStateEvent,
    },
    Int, OwnedUserId, UserId,
};
use tracing::error;

use super::{Member, Membership, Room};
use crate::{prelude::*, spawn, spawn_tokio};

/// Power level of a user.
///
/// Is usually in the range (0..=100), but can be any JS integer.
pub type PowerLevel = i64;

/// The maximum power level that can be set, according to the Matrix
/// specification.
///
/// This is the same value as `MAX_SAFE_INT` from the `js_int` crate.
pub const POWER_LEVEL_MAX: PowerLevel = 0x001F_FFFF_FFFF_FFFF;
/// The minimum power level that can be set, according to the Matrix
/// specification.
///
/// This is the same value as `MIN_SAFE_INT` from the `js_int` crate.
pub const POWER_LEVEL_MIN: PowerLevel = -POWER_LEVEL_MAX;
/// The minimum power level to have the role of Administrator, according to the
/// Matrix specification.
pub const POWER_LEVEL_ADMIN: PowerLevel = 100;
/// The minimum power level to have the role of Moderator, according to the
/// Matrix specification.
pub const POWER_LEVEL_MOD: PowerLevel = 50;

/// Role of a room member, like admin or moderator.
#[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
#[enum_type(name = "MemberRole")]
pub enum MemberRole {
    /// A room member with the default power level.
    #[default]
    Default,
    /// A room member with a non-default power level, but lower than and a
    /// moderator.
    Custom,
    /// A moderator.
    Moderator,
    /// An administrator.
    Administrator,
    /// A room member that cannot send messages.
    Muted,
}

impl fmt::Display for MemberRole {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            // Translators: As in 'Default power level', meaning permissions.
            Self::Default => write!(f, "{}", gettext("Default")),
            // Translators: As in, 'Custom power level', meaning permissions.
            Self::Custom => write!(f, "{}", gettext("Custom")),
            Self::Moderator => write!(f, "{}", gettext("Moderator")),
            Self::Administrator => write!(f, "{}", gettext("Admin")),
            // Translators: As in 'Muted room member', a member that cannot send messages.
            Self::Muted => write!(f, "{}", gettext("Muted")),
        }
    }
}

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

    use glib::subclass::Signal;
    use once_cell::sync::Lazy;
    use ruma::events::room::power_levels::NotificationPowerLevelType;

    use super::*;

    #[derive(Debug, glib::Properties)]
    #[properties(wrapper_type = super::Permissions)]
    pub struct Permissions {
        /// The room where these permissions apply.
        #[property(get)]
        pub room: glib::WeakRef<Room>,
        /// The source of the power levels information.
        pub power_levels: RefCell<RoomPowerLevels>,
        power_levels_drop_guard: OnceCell<EventHandlerDropGuard>,
        /// Whether our own member is joined.
        pub is_joined: Cell<bool>,
        /// The power level of our own member.
        #[property(get)]
        pub own_power_level: Cell<PowerLevel>,
        /// The default power level for members.
        #[property(get)]
        pub default_power_level: Cell<PowerLevel>,
        /// The power level to mute members.
        #[property(get)]
        pub mute_power_level: Cell<PowerLevel>,
        /// Whether our own member can change the room's avatar.
        #[property(get)]
        pub can_change_avatar: Cell<bool>,
        /// Whether our own member can change the room's name.
        #[property(get)]
        pub can_change_name: Cell<bool>,
        /// Whether our own member can change the room's topic.
        #[property(get)]
        pub can_change_topic: Cell<bool>,
        /// Whether our own member can invite another user.
        #[property(get)]
        pub can_invite: Cell<bool>,
        /// Whether our own member can send a message.
        #[property(get)]
        pub can_send_message: Cell<bool>,
        /// Whether our own member can send a reaction.
        #[property(get)]
        pub can_send_reaction: Cell<bool>,
        /// Whether our own member can redact their own event.
        #[property(get)]
        pub can_redact_own: Cell<bool>,
        /// Whether our own member can redact the event of another user.
        #[property(get)]
        pub can_redact_other: Cell<bool>,
        /// Whether our own member can notify the whole room.
        #[property(get)]
        pub can_notify_room: Cell<bool>,
    }

    impl Default for Permissions {
        fn default() -> Self {
            Self {
                room: Default::default(),
                power_levels: RefCell::new(RoomPowerLevelsEventContent::default().into()),
                power_levels_drop_guard: Default::default(),
                is_joined: Default::default(),
                own_power_level: Default::default(),
                default_power_level: Default::default(),
                mute_power_level: Default::default(),
                can_change_avatar: Default::default(),
                can_change_name: Default::default(),
                can_change_topic: Default::default(),
                can_invite: Default::default(),
                can_send_message: Default::default(),
                can_send_reaction: Default::default(),
                can_redact_own: Default::default(),
                can_redact_other: Default::default(),
                can_notify_room: Default::default(),
            }
        }
    }

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

    #[glib::derived_properties]
    impl ObjectImpl for Permissions {
        fn signals() -> &'static [Signal] {
            static SIGNALS: Lazy<Vec<Signal>> =
                Lazy::new(|| vec![Signal::builder("changed").build()]);
            SIGNALS.as_ref()
        }
    }

    impl Permissions {
        /// Initialize the room.
        pub(super) fn init_own_member(&self, own_member: Member) {
            own_member.connect_membership_notify(clone!(
                #[weak(rename_to = imp)]
                self,
                move |_| {
                    imp.update_is_joined();
                }
            ));

            self.update_is_joined();
        }

        /// The room member for our own user.
        pub(super) fn own_member(&self) -> Option<Member> {
            self.room.upgrade().map(|r| r.own_member())
        }

        /// Initialize the power levels from the store.
        pub(super) async fn init_power_levels(&self) {
            let Some(room) = self.room.upgrade() else {
                return;
            };

            let matrix_room = room.matrix_room();

            let matrix_room_clone = matrix_room.clone();
            let handle = spawn_tokio!(async move {
                let state_event = match matrix_room_clone
                    .get_state_event_static::<RoomPowerLevelsEventContent>()
                    .await
                {
                    Ok(state_event) => state_event,
                    Err(error) => {
                        error!("Initial load of room power levels failed: {error}");
                        return None;
                    }
                };

                state_event
                    .and_then(|r| r.deserialize().ok())
                    .and_then(|ev| match ev {
                        SyncOrStrippedState::Sync(e) => Some(e),
                        // The power levels are usually not in the stripped state.
                        _ => None,
                    })
            });

            if let Some(event) = handle.await.unwrap() {
                self.update_power_levels(&event);
            }

            let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
            let handle = matrix_room.add_event_handler(
                move |event: SyncStateEvent<RoomPowerLevelsEventContent>| {
                    let obj_weak = obj_weak.clone();
                    async move {
                        let ctx = glib::MainContext::default();
                        ctx.spawn(async move {
                            spawn!(async move {
                                if let Some(obj) = obj_weak.upgrade() {
                                    obj.imp().update_power_levels(&event);
                                }
                            });
                        });
                    }
                },
            );

            let drop_guard = matrix_room.client().event_handler_drop_guard(handle);
            self.power_levels_drop_guard.set(drop_guard).unwrap();
        }

        /// Update whether our own member is joined
        fn update_is_joined(&self) {
            let Some(own_member) = self.own_member() else {
                return;
            };

            let is_joined = own_member.membership() == Membership::Join;

            if self.is_joined.get() == is_joined {
                return;
            }

            self.is_joined.set(is_joined);
            self.permissions_changed();
        }

        /// Update the power levels with the given event.
        fn update_power_levels(&self, event: &SyncStateEvent<RoomPowerLevelsEventContent>) {
            let power_levels = event.power_levels();
            self.power_levels.replace(power_levels.clone());
            self.permissions_changed();

            if let Some(room) = self.room.upgrade() {
                if let Some(members) = room.members() {
                    members.update_power_levels(&power_levels);
                } else {
                    let own_member = room.own_member();
                    let own_user_id = own_member.user_id();
                    own_member.set_power_level(power_levels.for_user(own_user_id).into());
                }
            }
        }

        /// Trigger updates when the permissions changed.
        fn permissions_changed(&self) {
            self.update_own_power_level();
            self.update_default_power_level();
            self.update_mute_power_level();
            self.update_can_change_avatar();
            self.update_can_change_name();
            self.update_can_change_topic();
            self.update_can_invite();
            self.update_can_send_message();
            self.update_can_send_reaction();
            self.update_can_redact_own();
            self.update_can_redact_other();
            self.update_can_notify_room();
            self.obj().emit_by_name::<()>("changed", &[]);
        }

        /// Update the power level of our own member.
        fn update_own_power_level(&self) {
            let Some(room) = self.room.upgrade() else {
                return;
            };
            let own_member = room.own_member();

            let power_level = self
                .power_levels
                .borrow()
                .for_user(own_member.user_id())
                .into();

            if self.own_power_level.get() == power_level {
                return;
            }

            self.own_power_level.set(power_level);
            self.obj().notify_own_power_level();
        }

        /// Update the default power level for members.
        fn update_default_power_level(&self) {
            let power_level = self.power_levels.borrow().users_default.into();

            if self.default_power_level.get() == power_level {
                return;
            }

            self.default_power_level.set(power_level);
            self.obj().notify_default_power_level();
        }

        /// Update the power level to mute members.
        fn update_mute_power_level(&self) {
            // To mute user they must not have enough power to send messages.
            let power_levels = self.power_levels.borrow();
            let message_power_level = power_levels
                .events
                .get(&MessageLikeEventType::RoomMessage.into())
                .copied()
                .unwrap_or(power_levels.events_default);
            let power_level = (-1).min(message_power_level.into());

            if self.mute_power_level.get() == power_level {
                return;
            }

            self.mute_power_level.set(power_level);
            self.obj().notify_mute_power_level();
        }

        /// Whether our own member is allowed to do the given action.
        pub(super) fn is_allowed_to(&self, room_action: PowerLevelAction) -> bool {
            if !self.is_joined.get() {
                // We cannot do anything if the member is not joined.
                return false;
            }

            let Some(own_member) = self.own_member() else {
                return false;
            };

            self.power_levels
                .borrow()
                .user_can_do(own_member.user_id(), room_action)
        }

        /// Update whether our own member can change the room's avatar.
        fn update_can_change_avatar(&self) {
            let can_change_avatar =
                self.is_allowed_to(PowerLevelAction::SendState(StateEventType::RoomAvatar));

            if self.can_change_avatar.get() == can_change_avatar {
                return;
            };

            self.can_change_avatar.set(can_change_avatar);
            self.obj().notify_can_change_avatar();
        }

        /// Update whether our own member can change the room's name.
        fn update_can_change_name(&self) {
            let can_change_name =
                self.is_allowed_to(PowerLevelAction::SendState(StateEventType::RoomName));

            if self.can_change_name.get() == can_change_name {
                return;
            };

            self.can_change_name.set(can_change_name);
            self.obj().notify_can_change_name();
        }

        /// Update whether our own member can change the room's topic.
        fn update_can_change_topic(&self) {
            let can_change_topic =
                self.is_allowed_to(PowerLevelAction::SendState(StateEventType::RoomTopic));

            if self.can_change_topic.get() == can_change_topic {
                return;
            };

            self.can_change_topic.set(can_change_topic);
            self.obj().notify_can_change_topic();
        }

        /// Update whether our own member can invite another user in the room.
        fn update_can_invite(&self) {
            let can_invite = self.is_allowed_to(PowerLevelAction::Invite);

            if self.can_invite.get() == can_invite {
                return;
            };

            self.can_invite.set(can_invite);
            self.obj().notify_can_invite();
        }

        /// Update whether our own member can send a message in the room.
        fn update_can_send_message(&self) {
            let can_send_message = self.is_allowed_to(PowerLevelAction::SendMessage(
                MessageLikeEventType::RoomMessage,
            ));

            if self.can_send_message.get() == can_send_message {
                return;
            };

            self.can_send_message.set(can_send_message);
            self.obj().notify_can_send_message();
        }

        /// Update whether our own member can send a reaction.
        fn update_can_send_reaction(&self) {
            let can_send_reaction = self.is_allowed_to(PowerLevelAction::SendMessage(
                MessageLikeEventType::Reaction,
            ));

            if self.can_send_reaction.get() == can_send_reaction {
                return;
            };

            self.can_send_reaction.set(can_send_reaction);
            self.obj().notify_can_send_reaction();
        }

        /// Update whether our own member can redact their own event.
        fn update_can_redact_own(&self) {
            let can_redact_own = self.is_allowed_to(PowerLevelAction::RedactOwn);

            if self.can_redact_own.get() == can_redact_own {
                return;
            };

            self.can_redact_own.set(can_redact_own);
            self.obj().notify_can_redact_own();
        }

        /// Update whether our own member can redact the event of another user.
        fn update_can_redact_other(&self) {
            let can_redact_other = self.is_allowed_to(PowerLevelAction::RedactOther);

            if self.can_redact_other.get() == can_redact_other {
                return;
            };

            self.can_redact_other.set(can_redact_other);
            self.obj().notify_can_redact_other();
        }

        /// Update whether our own member can notify the whole room.
        fn update_can_notify_room(&self) {
            let can_notify_room = self.is_allowed_to(PowerLevelAction::TriggerNotification(
                NotificationPowerLevelType::Room,
            ));

            if self.can_notify_room.get() == can_notify_room {
                return;
            };

            self.can_notify_room.set(can_notify_room);
            self.obj().notify_can_notify_room();
        }
    }
}

glib::wrapper! {
    /// The permissions of our own user in a room.
    pub struct Permissions(ObjectSubclass<imp::Permissions>);
}

impl Permissions {
    pub fn new() -> Self {
        glib::Object::new()
    }

    /// Set our own member.
    pub(super) async fn init(&self, room: &Room) {
        let imp = self.imp();

        imp.room.set(Some(room));
        imp.init_own_member(room.own_member());
        imp.init_power_levels().await;
    }

    /// The source of the power levels information.
    pub fn power_levels(&self) -> RoomPowerLevels {
        self.imp().power_levels.borrow().clone()
    }

    /// The current [`MemberRole`] for the given power level.
    pub fn role(&self, power_level: PowerLevel) -> MemberRole {
        if power_level >= POWER_LEVEL_ADMIN {
            MemberRole::Administrator
        } else if power_level >= POWER_LEVEL_MOD {
            MemberRole::Moderator
        } else if power_level == self.default_power_level() {
            MemberRole::Default
        } else if power_level < self.default_power_level() && power_level <= self.mute_power_level()
        {
            // Only set role as muted for members below default, to avoid visual noise in
            // rooms where muted is the default.
            MemberRole::Muted
        } else {
            MemberRole::Custom
        }
    }

    /// Whether our own member is allowed to do the given action.
    pub fn is_allowed_to(&self, room_action: PowerLevelAction) -> bool {
        self.imp().is_allowed_to(room_action)
    }

    /// Whether our own user can do the given action on the user with the given
    /// ID.
    pub fn can_do_to_user(&self, user_id: &UserId, action: PowerLevelUserAction) -> bool {
        let imp = self.imp();

        if !imp.is_joined.get() {
            // We cannot do anything if the member is not joined.
            return false;
        }

        let Some(own_member) = imp.own_member() else {
            return false;
        };
        let own_user_id = own_member.user_id();

        let power_levels = imp.power_levels.borrow();

        if own_user_id == user_id {
            // The only action we can do for our own user is change the power level.
            return action == PowerLevelUserAction::ChangePowerLevel
                && power_levels.user_can_send_state(own_user_id, StateEventType::RoomPowerLevels);
        }

        power_levels.user_can_do_to_user(own_user_id, user_id, action)
    }

    /// Set the power level of the room member with the given user ID.
    pub async fn set_user_power_level(
        &self,
        user_id: OwnedUserId,
        power_level: PowerLevel,
    ) -> Result<(), ()> {
        let Some(room) = self.room() else {
            return Err(());
        };

        let matrix_room = room.matrix_room().clone();
        let handle = spawn_tokio!(async move {
            let power_level = Int::new_saturating(power_level);
            matrix_room
                .update_power_levels(vec![(&user_id, power_level)])
                .await
        });

        match handle.await.unwrap() {
            Ok(_) => Ok(()),
            Err(error) => {
                error!("Could not set user power level: {error}");
                Err(())
            }
        }
    }

    /// Set the power levels.
    pub async fn set_power_levels(&self, power_levels: RoomPowerLevels) -> Result<(), ()> {
        let Some(room) = self.room() else {
            return Err(());
        };

        let matrix_room = room.matrix_room().clone();
        let handle = spawn_tokio!(async move {
            let event = RoomPowerLevelsEventContent::from(power_levels);
            matrix_room.send_state_event(event).await
        });

        match handle.await.unwrap() {
            Ok(_) => Ok(()),
            Err(error) => {
                error!("Failed to set power levels: {error}");
                Err(())
            }
        }
    }

    /// Whether the user with the given ID is allowed to do the given action.
    pub fn user_is_allowed_to(&self, user_id: &UserId, room_action: PowerLevelAction) -> bool {
        self.imp()
            .power_levels
            .borrow()
            .user_can_do(user_id, room_action)
    }

    /// Connect to the signal emitted when the permissions changed.
    pub fn connect_changed<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
        self.connect_closure(
            "changed",
            true,
            closure_local!(move |obj: Self| {
                f(&obj);
            }),
        )
    }
}

impl Default for Permissions {
    fn default() -> Self {
        Self::new()
    }
}
