use crate::app::App;
use crate::error::NewsFlashGtkError;
use crate::gobject_models::GPluginID;
use crate::i18n::i18n;
use eyre::{Result, eyre};
use glib::{Object, Properties, clone, prelude::*, subclass};
use gtk4::{Accessible, Buildable, CompositeTemplate, ConstraintTarget, Widget, subclass::prelude::*};
use libadwaita::{Bin, Toast, ToastOverlay, subclass::prelude::*};
use news_flash::error::NewsFlashError;
use news_flash::feed_api::FeedApiError;
use news_flash::models::{ApiSecret, LoginData, LoginGUI, OAuthData, PluginInfo};
use std::cell::{Cell, RefCell};

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::WebLogin)]
    #[template(file = "data/resources/ui_templates/login/web.blp")]
    pub struct WebLogin {
        #[template_child]
        pub toast_overlay: TemplateChild<ToastOverlay>,

        #[property(get, set, name = "plugin-id")]
        pub plugin_id: RefCell<GPluginID>,

        #[property(get, set, nullable, name = "custom-client-id")]
        pub custom_client_id: RefCell<Option<String>>,

        #[property(get, set, nullable, name = "custom-client-secret")]
        pub custom_client_secret: RefCell<Option<String>>,

        #[property(get, set, name = "redirect-url")]
        pub redirect_url: RefCell<String>,

        #[property(get, set, name = "paste-url")]
        pub paste_url: RefCell<String>,

        #[property(get, set, name = "show-paste-url", default_value = false)]
        pub show_paste_url: Cell<bool>,

        #[property(get, set)]
        pub description: RefCell<String>,

        #[property(get, set, name = "is-busy")]
        pub is_busy: Cell<bool>,

        #[property(get, set, name = "can-login")]
        pub can_login: Cell<bool>,
    }

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

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

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

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

            obj.connect_is_busy_notify(super::WebLogin::update_can_login);
            obj.connect_paste_url_notify(super::WebLogin::update_can_login);

            self.parent_constructed();
        }
    }

    impl WidgetImpl for WebLogin {}

    impl BinImpl for WebLogin {}

    #[gtk4::template_callbacks]
    impl WebLogin {
        #[template_callback]
        async fn on_login(&self) {
            let pasted_redirect_url = self.paste_url.borrow().clone();
            self.login(pasted_redirect_url).await
        }

        #[template_callback]
        fn busy_visible_child(&self, is_busy: bool) -> &'static str {
            if is_busy { "spinner" } else { "label" }
        }

        pub(super) async fn login(&self, redirect_url: String) {
            let obj = self.obj();
            obj.set_is_busy(true);

            let oauth_data = OAuthData {
                id: self.plugin_id.borrow().as_ref().clone(),
                url: redirect_url,
                custom_api_secret: self.custom_api_secret(),
            };
            let login_data = LoginData::OAuth(oauth_data);

            App::login(login_data).await;
            obj.set_is_busy(false);
        }

        pub(super) fn custom_api_secret(&self) -> Option<ApiSecret> {
            if let Some(client_id) = self.custom_client_id.borrow().as_deref()
                && let Some(client_secret) = self.custom_client_secret.borrow().as_deref()
            {
                Some(ApiSecret {
                    client_id: client_id.to_string(),
                    client_secret: client_secret.to_string(),
                })
            } else {
                None
            }
        }
    }
}

glib::wrapper! {
    pub struct WebLogin(ObjectSubclass<imp::WebLogin>)
        @extends Widget, Bin,
        @implements Accessible, Buildable, ConstraintTarget;
}

impl Default for WebLogin {
    fn default() -> Self {
        Object::new::<Self>()
    }
}

impl WebLogin {
    pub fn show_error(&self, error: NewsFlashError) {
        let imp = self.imp();

        let limit_reached = i18n("API Limit Reached");
        let login_failed = i18n("Failed to log in");
        let unknown_error = i18n("Unknown error");

        let error_text = if let NewsFlashError::API(FeedApiError::ApiLimit) = error {
            limit_reached
        } else if let NewsFlashError::NotLoggedIn = error {
            login_failed.clone()
        } else {
            unknown_error
        };

        let toast = Toast::new(&error_text);
        toast.set_button_label(Some(&i18n("details")));
        toast.set_action_name(Some("win.show-error-dialog"));
        imp.toast_overlay.add_toast(toast);

        App::default().set_newsflash_error(NewsFlashGtkError::NewsFlash {
            source: error,
            context: login_failed,
        });
    }

    pub fn set_service(&self, info: PluginInfo) -> Result<()> {
        let LoginGUI::OAuth(web_login_desc) = &info.login_gui else {
            return Err(eyre!("No login OAuth GUI description"));
        };

        let plugin_id: GPluginID = info.id.clone().into();
        self.set_plugin_id(plugin_id);

        let Some(url) = (web_login_desc.login_website)(self.imp().custom_api_secret().as_ref()) else {
            return Err(eyre!("No OAuth login URL"));
        };

        let Some(redirect_url) = web_login_desc.catch_redirect.clone() else {
            return Err(eyre!("No redirect URL"));
        };

        if !redirect_url.starts_with("newsflash://") {
            self.set_show_paste_url(true);
            self.set_description(i18n("After successful login, copy the redirect url and paste it below"));
        }

        self.set_redirect_url(redirect_url);

        App::open_url_in_default_browser(url.as_str());

        Ok(())
    }

    pub fn oauth_callback(&self, url: &str) {
        let url = url.to_string();
        glib::spawn_future_local(clone!(
            #[weak(rename_to = imp)]
            self.imp(),
            async move {
                imp.login(url).await;
            }
        ));
    }

    pub fn reset(&self) {
        self.set_show_paste_url(false);
        self.set_paste_url("");
        self.set_custom_client_id(None::<String>);
        self.set_custom_client_secret(None::<String>);
        self.set_redirect_url("");
        self.set_description("");
    }

    fn update_can_login(&self) {
        let can_login = !self.is_busy() && !self.paste_url().is_empty();
        self.set_can_login(can_login);
    }
}
