use adw::prelude::*;
use adw::subclass::prelude::*;
use gettextrs::gettext;
use glib::Properties;
use gtk::{glib, CompositeTemplate};
use std::cell::OnceCell;
use zxcvbn::feedback::{Suggestion, Warning};
use zxcvbn::zxcvbn;

use crate::data::KrCollection;
use crate::utils::error::DisplayError;

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::KrCreateItemDialog)]
    #[template(file = "create_item_dialog.ui")]
    pub struct KrCreateItemDialog {
        #[property(get, set, construct_only)]
        collection: OnceCell<KrCollection>,
        #[template_child]
        description_row: TemplateChild<adw::EntryRow>,
        #[template_child]
        password_row: TemplateChild<adw::PasswordEntryRow>,
        #[template_child]
        strength_indicator: TemplateChild<gtk::LevelBar>,
        #[template_child]
        password_hint_label: TemplateChild<gtk::Label>,
        #[template_child]
        stack: TemplateChild<gtk::Stack>,
        #[template_child]
        spinner: TemplateChild<gtk::Spinner>,
    }

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

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

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

    #[glib::derived_properties]
    impl ObjectImpl for KrCreateItemDialog {
        fn constructed(&self) {
            self.parent_constructed();
            self.check_password();
        }
    }

    impl WidgetImpl for KrCreateItemDialog {}

    impl AdwDialogImpl for KrCreateItemDialog {}

    #[gtk::template_callbacks]
    impl KrCreateItemDialog {
        #[template_callback]
        fn check_password(&self) {
            let password = self.password_row.text().to_string();
            let description = self.description_row.text().to_string();

            let (strength_level, hint) = Self::password_strength(&description, &password);

            self.password_hint_label.set_text(&hint);
            self.strength_indicator.set_value(strength_level as f64);
        }

        #[template_callback]
        async fn on_add_clicked(&self) {
            let password = self.password_row.text().to_string();
            let description = self.description_row.text().to_string();

            self.description_row.remove_css_class("error");
            if description.is_empty() {
                self.description_row.add_css_class("error");
                return;
            }

            self.password_row.remove_css_class("error");
            if password.is_empty() {
                self.password_row.add_css_class("error");
                return;
            }

            self.obj().set_can_close(false);
            self.stack.set_visible_child(&*self.spinner);

            self.obj()
                .collection()
                .add_password_item(&description, &password)
                .await
                .handle_error("Unable to add new item");

            self.obj().force_close();
        }

        fn password_strength(description: &str, password: &str) -> (i32, String) {
            let entropy = zxcvbn(password, &[description]);
            let strength_level = entropy.score() as i32;

            if let Some(feedback) = entropy.feedback() {
                if let Some(warning) = feedback.warning() {
                    let hint = match warning {
                        Warning::StraightRowsOfKeysAreEasyToGuess => {
                            gettext("Straight rows of keys are easy to guess.")
                        }
                        Warning::ShortKeyboardPatternsAreEasyToGuess => {
                            gettext("Short keyboard patterns are easy to guess.")
                        }
                        Warning::RepeatsLikeAaaAreEasyToGuess => {
                            gettext("Repeated keys are easy to guess.")
                        }
                        Warning::RepeatsLikeAbcAbcAreOnlySlightlyHarderToGuess => {
                            gettext("Repeated patterns are easy to guess.")
                        }
                        Warning::ThisIsATop10Password => {
                            gettext("This is one of the 10 most commonly used passwords.")
                        }
                        Warning::ThisIsATop100Password => {
                            gettext("This is one of the 100 most commonly used passwords.")
                        }
                        Warning::ThisIsACommonPassword => gettext("This is a common password."),
                        Warning::ThisIsSimilarToACommonlyUsedPassword => {
                            gettext("This is similar to a commonly used password.")
                        }
                        Warning::SequencesLikeAbcAreEasyToGuess => {
                            gettext("This contains a easy to guess sequence.")
                        }
                        Warning::RecentYearsAreEasyToGuess => {
                            gettext("Recent years are easy to guess.")
                        }
                        Warning::AWordByItselfIsEasyToGuess => {
                            gettext("A word by itself is easy to guess.")
                        }
                        Warning::DatesAreOftenEasyToGuess => {
                            gettext("Dates are often easy to guess.")
                        }
                        Warning::NamesAndSurnamesByThemselvesAreEasyToGuess => {
                            gettext("Names and surnames by themselves are easy to guess.")
                        }
                        Warning::CommonNamesAndSurnamesAreEasyToGuess => {
                            gettext("Common names and surnames are easy to guess.")
                        }
                    };
                    let hint = gettext!("Warning: {}", hint);
                    return (strength_level, hint);
                }

                if let Some(suggestion) = feedback.suggestions().first() {
                    let hint = match suggestion {
                        Suggestion::UseAFewWordsAvoidCommonPhrases => {
                            gettext("Use a few words and avoid common phrases.")
                        }
                        Suggestion::NoNeedForSymbolsDigitsOrUppercaseLetters => {
                            gettext("No need for symbols, digits or uppercase letters.")
                        }
                        Suggestion::AddAnotherWordOrTwo => gettext("Add another word or two."),
                        Suggestion::CapitalizationDoesntHelpVeryMuch => {
                            gettext("Capitalization doesn't help very much.")
                        }
                        Suggestion::AllUppercaseIsAlmostAsEasyToGuessAsAllLowercase => {
                            gettext("All uppercase is almost as easy to guess as all lowercase.")
                        }
                        Suggestion::ReversedWordsArentMuchHarderToGuess => {
                            gettext("Reversed words aren't much harder to guess.")
                        }
                        Suggestion::PredictableSubstitutionsDontHelpVeryMuch => {
                            gettext("Predictable substitutions don't help very much.")
                        }
                        Suggestion::UseALongerKeyboardPatternWithMoreTurns => {
                            gettext("Use a longer keyboard pattern with more turns.")
                        }
                        Suggestion::AvoidRepeatedWordsAndCharacters => {
                            gettext("Avoid repeated words and characters.")
                        }
                        Suggestion::AvoidSequences => gettext("Avoid using sequences."),
                        Suggestion::AvoidRecentYears => gettext("Avoid using recent years."),
                        Suggestion::AvoidYearsThatAreAssociatedWithYou => {
                            gettext("Avoid years that are associated with you.")
                        }
                        Suggestion::AvoidDatesAndYearsThatAreAssociatedWithYou => {
                            gettext("Avoid dates and years that are associated with you.")
                        }
                    };
                    let hint = gettext!("Suggestion: {}", hint);
                    return (strength_level, hint);
                }
            }

            (strength_level, gettext("This looks like a safe password!"))
        }
    }
}

glib::wrapper! {
    pub struct KrCreateItemDialog(ObjectSubclass<imp::KrCreateItemDialog>)
        @extends gtk::Widget, adw::Dialog;
}

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