use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{glib, glib::clone, CompositeTemplate};
use tracing::error;
use url::Url;

use super::AccountSettings;
use crate::{
    components::{AuthDialog, LoadingButtonRow},
    prelude::*,
    session::model::Session,
    toast,
    utils::oauth,
};

mod imp {
    use glib::subclass::InitializingObject;

    use super::*;

    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
    #[template(
        resource = "/org/gnome/Fractal/ui/session/view/account_settings/general_page/deactivate_account_subpage.ui"
    )]
    #[properties(wrapper_type = super::DeactivateAccountSubpage)]
    pub struct DeactivateAccountSubpage {
        #[template_child]
        confirmation: TemplateChild<adw::EntryRow>,
        #[template_child]
        loading_button: TemplateChild<LoadingButtonRow>,
        #[template_child]
        open_url_button: TemplateChild<adw::ButtonRow>,
        /// The current session.
        #[property(get, set = Self::set_session, construct_only)]
        session: glib::WeakRef<Session>,
        /// The ancestor [`AccountSettings`].
        #[property(get, set = Self::set_account_settings, construct_only)]
        account_settings: glib::WeakRef<AccountSettings>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for DeactivateAccountSubpage {
        const NAME: &'static str = "DeactivateAccountSubpage";
        type Type = super::DeactivateAccountSubpage;
        type ParentType = adw::NavigationPage;

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

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

    #[glib::derived_properties]
    impl ObjectImpl for DeactivateAccountSubpage {}

    impl WidgetImpl for DeactivateAccountSubpage {}
    impl NavigationPageImpl for DeactivateAccountSubpage {}

    #[gtk::template_callbacks]
    impl DeactivateAccountSubpage {
        /// Set the current session.
        fn set_session(&self, session: &Session) {
            self.session.set(Some(session));
            self.confirmation.set_title(session.user_id().as_str());
        }

        /// Set the ancestor [`AccountSettings`].
        fn set_account_settings(&self, account_settings: &AccountSettings) {
            self.account_settings.set(Some(account_settings));

            account_settings.connect_account_management_url_changed(clone!(
                #[weak(rename_to = imp)]
                self,
                move |_| {
                    imp.update_visible_button();
                }
            ));
            self.update_visible_button();
        }

        /// The account management URL of the authentication issuer, if any.
        fn account_management_url(&self) -> Option<Url> {
            self.account_settings
                .upgrade()
                .and_then(|s| s.account_management_url())
        }

        /// Update the visible button for the current state.
        fn update_visible_button(&self) {
            let should_open_url = self.account_management_url().is_some();
            self.loading_button.set_visible(!should_open_url);
            self.open_url_button.set_visible(should_open_url);
        }

        /// Update the state of the buttons.
        #[template_callback]
        fn update_buttons_state(&self) {
            let sensitive = self.can_deactivate_account();
            self.loading_button.set_sensitive(sensitive);
            self.open_url_button.set_sensitive(sensitive);
        }

        /// Whether the account can be deactivated with the current state.
        fn can_deactivate_account(&self) -> bool {
            self.confirmation.text() == self.confirmation.title()
        }

        /// Deactivate the account with the proper method.
        #[template_callback]
        async fn deactivate_account(&self) {
            if self.account_management_url().is_some() {
                self.open_deactivate_account_url().await;
            } else {
                self.deactivate_account_with_request().await;
            }
        }

        /// Deactivate the account of the current session by making a request to
        /// the homeserver.
        #[template_callback]
        async fn deactivate_account_with_request(&self) {
            let Some(session) = self.session.upgrade() else {
                return;
            };

            if !self.can_deactivate_account() {
                return;
            }

            self.loading_button.set_is_loading(true);
            self.confirmation.set_sensitive(false);

            let dialog = AuthDialog::new(&session);
            let obj = self.obj();

            let result = dialog
                .authenticate(&*obj, move |client, auth| async move {
                    client
                        .account()
                        .deactivate(None, auth, false)
                        .await
                        .map_err(Into::into)
                })
                .await;

            match result {
                Ok(_) => {
                    if let Some(session) = self.session.upgrade() {
                        if let Some(window) = obj.root().and_downcast_ref::<gtk::Window>() {
                            toast!(window, gettext("Account successfully deactivated"));
                        }
                        session.clean_up().await;
                    }
                    let _ = obj.activate_action("account-settings.close", None);
                }
                Err(error) => {
                    error!("Could not deactivate account: {error:?}");
                    toast!(obj, gettext("Could not deactivate account"));
                }
            }
            self.loading_button.set_is_loading(false);
            self.confirmation.set_sensitive(true);
        }

        // Open the account management URL to deactivate the account.
        #[template_callback]
        async fn open_deactivate_account_url(&self) {
            let Some(mut url) = self.account_management_url() else {
                error!("Could not find open account management URL");
                return;
            };

            oauth::AccountManagementAction::AccountDeactivate
                .add_to_account_management_url(&mut url);

            if let Err(error) = gtk::UriLauncher::new(url.as_ref())
                .launch_future(self.obj().root().and_downcast_ref::<gtk::Window>())
                .await
            {
                error!("Could not launch account management URL: {error}");
            }
        }
    }
}

glib::wrapper! {
    /// Account settings page about the user and the session.
    pub struct DeactivateAccountSubpage(ObjectSubclass<imp::DeactivateAccountSubpage>)
        @extends gtk::Widget, adw::NavigationPage, @implements gtk::Accessible;
}

impl DeactivateAccountSubpage {
    pub fn new(session: &Session, account_settings: &AccountSettings) -> Self {
        glib::Object::builder()
            .property("session", session)
            .property("account-settings", account_settings)
            .build()
    }
}
