use std::cell::OnceCell;

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

use crate::data::{KrCollection, KrCollectionKind, KrItem};
use crate::utils::error::DisplayError;
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_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]
        preferences_group: TemplateChild<adw::PreferencesGroup>,
        #[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(&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(
                            None,
                            Some(&gettext(
                                "You are about to change the password for the system's keyring. This password is usually the same as your login password to automatically unlock this keyring on login. If the password is changed to something different, the automatic unlocking of the keyring might stop 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(&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()
                    .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()
                    .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(
                        "All keys in the keyring will be permanently deleted",
                    )),
                );
                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(&obj).await;

                if ret == "delete" {
                    let res = collection.delete().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 self as this => move |m, _, _, _|{
                    if m.n_items() == 0 {
                        this.content_stack.set_visible_child(&*this.empty_status_page);
                    } else {
                        this.content_stack.set_visible_child(&*this.preferences_page);
                    }
                }));

            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_group, "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 self as this => move |_| {
                this.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 self as this => move |m, _, _, _| {
                if m.n_items() == 0 {
                    this.search_stack.set_visible_child(&*this.no_results_status_page);
                } else {
                    this.search_stack.set_visible_child(&*this.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 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;
}

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