use std::cell::OnceCell;

use adw::prelude::*;
use adw::subclass::prelude::*;
use gettextrs::gettext;
use glib::{Properties, clone};
use gtk::{CompositeTemplate, glib};

use crate::data::{KrCollection, KrCollectionKind, KrItem};
use crate::utils::error::DisplayError;
use crate::utils::window_identifier::window_identifier;
use crate::widgets::{KrCreateItemDialog, KrItemRow};
use crate::{app, data};

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::KrCollectionItemsPage)]
    #[template(file = "collection_items_page.ui")]
    pub struct KrCollectionItemsPage {
        #[property(get, set, construct_only)]
        collection: OnceCell<KrCollection>,
        #[template_child]
        search_button: TemplateChild<gtk::Button>,
        #[template_child]
        search_bar: TemplateChild<gtk::SearchBar>,
        #[template_child]
        search_entry: TemplateChild<gtk::SearchEntry>,
        #[template_child]
        unlock_banner: TemplateChild<adw::Banner>,
        #[template_child]
        search_stack: TemplateChild<gtk::Stack>,
        #[template_child]
        content_stack: TemplateChild<gtk::Stack>,
        #[template_child]
        no_results_status_page: TemplateChild<adw::StatusPage>,
        #[template_child]
        empty_status_page: TemplateChild<adw::StatusPage>,
        #[template_child]
        preferences_page: TemplateChild<adw::PreferencesPage>,
        #[template_child]
        item_list: TemplateChild<gtk::ListBox>,
        model: gtk::SortListModel,
    }

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

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

            klass.install_action("collection.search", None, |obj, _, _| {
                obj.imp().search_bar.set_search_mode(true);
            });
            klass.add_binding_action(
                gtk::gdk::Key::f,
                gtk::gdk::ModifierType::CONTROL_MASK,
                "collection.search",
            );

            klass.install_action_async("collection.create-item", None, |obj, _, _| async move {
                let dialog = KrCreateItemDialog::new(&obj.collection());
                dialog.present(Some(&obj));
            });
            klass.add_binding_action(
                gtk::gdk::Key::n,
                gtk::gdk::ModifierType::CONTROL_MASK,
                "collection.create-item",
            );

            klass.install_action_async(
                "collection.change-password",
                None,
                |obj, _, _| async move {
                    let collection = obj.collection();

                    if collection.kind() == KrCollectionKind::Login {
                        let dialog = adw::AlertDialog::new(
                            Some(&gettext("Change System Keyring Password")),
                            Some(&gettext(
                                "The system keyring normally uses the login password to automatically unlock the keyring on log in. Changing the keyring password to something different may stop this working.",
                            )),
                        );
                        dialog.add_response("cancel", &gettext("Cancel"));
                        dialog.set_default_response(Some("cancel"));
                        dialog.add_response("change", &gettext("Change"));
                        dialog.set_response_appearance("change", adw::ResponseAppearance::Destructive);
                        let ret = dialog.choose_future(Some(&obj)).await;
                        if ret != "change" {
                            return;
                        }
                    }

                    collection
                        .change_password()
                        .await
                        .handle_error("Unable to change keyring password");
                },
            );
            klass.add_binding_action(
                gtk::gdk::Key::p,
                gtk::gdk::ModifierType::CONTROL_MASK,
                "collection.change-password",
            );

            klass.install_action_async("collection.lock", None, |obj, _, _| async move {
                let collection = obj.collection();
                collection
                    .lock(window_identifier(&obj).await)
                    .await
                    .handle_error("Unable to lock collection");
            });
            klass.add_binding_action(
                gtk::gdk::Key::l,
                gtk::gdk::ModifierType::CONTROL_MASK,
                "collection.lock",
            );

            klass.install_action_async("collection.unlock", None, |obj, _, _| async move {
                let collection = obj.collection();
                collection
                    .unlock(window_identifier(&obj).await)
                    .await
                    .handle_error("Unable to unlock keyring");
            });
            klass.add_binding_action(
                gtk::gdk::Key::u,
                gtk::gdk::ModifierType::CONTROL_MASK,
                "collection.unlock",
            );

            klass.install_action_async("collection.delete", None, |obj, _, _| async move {
                let collection = obj.collection();
                let dialog = adw::AlertDialog::new(
                    Some(&gettext("Delete Keyring?")),
                    Some(&gettext!(
                        "Deleting the “{}” keyring will permanently delete all keys stored in it.",
                        collection.title()
                    )),
                );
                dialog.add_response("cancel", &gettext("Cancel"));
                dialog.set_default_response(Some("cancel"));
                dialog.add_response("delete", &gettext("Delete"));
                dialog.set_response_appearance("delete", adw::ResponseAppearance::Destructive);
                let ret = dialog.choose_future(Some(&obj)).await;

                if ret == "delete" {
                    let res = collection.delete(window_identifier(&obj).await).await;
                    res.handle_error("Unable to delete collection");
                    if res.is_ok() {
                        app().window().show_overview_page();
                    }
                }
            });
            klass.add_binding_action(
                gtk::gdk::Key::Delete,
                gtk::gdk::ModifierType::CONTROL_MASK,
                "collection.delete",
            );
        }

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

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

            self.search_bar.connect_entry(&*self.search_entry);

            let sorter = gtk::StringSorter::new(Some(&gtk::PropertyExpression::new(
                data::KrItem::static_type(),
                gtk::Expression::NONE,
                "label",
            )));
            self.model.set_sorter(Some(&sorter));

            self.model.connect_items_changed(clone!(
                #[weak(rename_to = imp)]
                self,
                move |m, _, _, _| {
                    let mut search_sensitive = true;

                    if m.n_items() == 0 {
                        if !imp.search_bar.is_search_mode() {
                            search_sensitive = false;
                        }
                        imp.content_stack.set_visible_child(&*imp.empty_status_page);
                    } else {
                        imp.content_stack.set_visible_child(&*imp.preferences_page);
                    }

                    imp.search_button.set_sensitive(search_sensitive);
                }
            ));

            self.item_list.bind_model(Some(&self.model), |x| {
                let collection_item: data::KrItem = x.clone().downcast().unwrap();
                KrItemRow::new(collection_item).upcast()
            });

            collection
                .bind_property("title", &*obj, "title")
                .sync_create()
                .build();

            collection
                .bind_property("kind", &*self.preferences_page, "description")
                .transform_to(|_, kind| {
                    Some(match kind {
                        KrCollectionKind::Login => gettext("This keyring is bound to your user and gets automatically unlocked at login"),
                        KrCollectionKind::Session => gettext("All keys in this temporary keyring are removed again on logout"),
                        _ => "".to_string()
                    })
                })
                .sync_create()
                .build();

            collection
                .bind_property("is-locked", &*self.unlock_banner, "revealed")
                .sync_create()
                .build();

            collection
                .bind_property("is-locked", &*self.item_list, "sensitive")
                .invert_boolean()
                .sync_create()
                .build();

            collection.connect_is_locked_notify(clone!(
                #[weak(rename_to = imp)]
                self,
                move |_| {
                    imp.update_actions();
                }
            ));

            // Search
            let search_filter = gtk::StringFilter::new(Some(&gtk::PropertyExpression::new(
                KrItem::static_type(),
                None::<&gtk::Expression>,
                "title",
            )));
            self.search_entry
                .bind_property("text", &search_filter, "search")
                .build();
            let filter_model =
                gtk::FilterListModel::new(Some(collection.regular_items()), Some(search_filter));

            filter_model.connect_items_changed(clone!(
                #[weak(rename_to = imp)]
                self,
                move |m, _, _, _| {
                    if m.n_items() == 0 && imp.search_bar.is_search_mode() {
                        imp.search_stack
                            .set_visible_child(&*imp.no_results_status_page);
                    } else {
                        imp.search_stack.set_visible_child(&*imp.content_stack);
                    }
                }
            ));

            self.model.set_model(Some(&filter_model));
            obj.action_set_enabled("collection.delete", !collection.kind().is_system());
            obj.action_set_enabled(
                "collection.change-password",
                collection.kind() != KrCollectionKind::Session,
            );
            self.update_actions();
        }
    }

    impl WidgetImpl for KrCollectionItemsPage {}

    impl NavigationPageImpl for KrCollectionItemsPage {}
    impl AccessibleImpl for KrCollectionItemsPage {}
    impl BuildableImpl for KrCollectionItemsPage {}

    impl KrCollectionItemsPage {
        fn update_actions(&self) {
            let obj = self.obj();
            let collection = obj.collection();

            let is_locked = collection.is_locked();
            obj.action_set_enabled(
                "collection.lock",
                !is_locked && !collection.kind().is_system(),
            );
            obj.action_set_enabled("collection.unlock", is_locked);
            obj.action_set_enabled("collection.create-item", !is_locked);
        }
    }
}

glib::wrapper! {
    pub struct KrCollectionItemsPage(ObjectSubclass<imp::KrCollectionItemsPage>)
        @extends gtk::Widget, adw::NavigationPage,
        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;

}

impl KrCollectionItemsPage {
    pub fn new(collection: &KrCollection) -> Self {
        glib::Object::builder()
            .property("collection", collection)
            .build()
    }
}
