use crate::app::App;
use crate::i18n::i18n;
use crate::util::JOIN_ERROR;
use crate::{add_dialog::g_parsed_url::GParsedUrl, infrastructure::TokioRuntime};
use glib::{Object, Properties, prelude::*, subclass::*};
use gtk4::{Accessible, Box, Buildable, CompositeTemplate, ConstraintTarget, Widget, subclass::prelude::*};
use news_flash::NewsFlash;
use news_flash::models::{FeedID, Url};
use once_cell::sync::Lazy;
use std::cell::{Cell, RefCell};
use std::sync::Arc;
use tokio::sync::Semaphore;

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate, Properties)]
    #[properties(wrapper_type = super::ParseFeedWidget)]
    #[template(file = "data/resources/ui_templates/add_dialog/parse_feed.blp")]
    pub struct ParseFeedWidget {
        #[property(get, set, name = "feed-url")]
        pub feed_url: RefCell<String>,

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

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

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

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

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

    #[glib::derived_properties]
    impl ObjectImpl for ParseFeedWidget {
        fn constructed(&self) {
            let obj = self.obj();
            obj.connect_feed_url_notify(super::ParseFeedWidget::update_can_parse);
            obj.connect_is_busy_notify(super::ParseFeedWidget::update_can_parse);

            self.parent_constructed();
        }

        fn signals() -> &'static [Signal] {
            static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
                vec![
                    Signal::builder("feed-parsed")
                        .param_types([GParsedUrl::static_type()])
                        .build(),
                    Signal::builder("error").param_types([String::static_type()]).build(),
                ]
            });
            SIGNALS.as_ref()
        }
    }

    impl WidgetImpl for ParseFeedWidget {}

    impl BoxImpl for ParseFeedWidget {}

    #[gtk4::template_callbacks]
    impl ParseFeedWidget {
        #[template_callback]
        async fn on_url_entry_activated(&self) {
            // hit enter in entry to parse url
            if self.can_parse.get() {
                self.on_parse_clicked().await;
            }
        }

        #[template_callback]
        async fn on_parse_clicked(&self) {
            let mut url_text = self.feed_url.borrow().clone();

            if !url_text.starts_with("http://") && !url_text.starts_with("https://") {
                url_text.insert_str(0, "https://");
            }

            self.obj().set_feed_url(url_text);
            self.parse_feed_url().await;
        }

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

        pub(super) async fn parse_feed_url(&self) {
            let obj = self.obj();

            let url_text = self.feed_url.borrow().clone();
            let Ok(url) = Url::parse(&url_text) else {
                tracing::error!(%url_text, "No valid url");
                obj.set_is_busy(false);
                obj.emit_by_name::<()>("error", &[&i18n("Not a valid URL")]);
                return;
            };

            // set 'next' button insensitive and show spinner
            obj.set_is_busy(true);

            let feed_id = FeedID::new(url.as_str());
            let url_clone = url.clone();

            let future = TokioRuntime::spawn(async move {
                let news_flash = App::news_flash();
                let semaphore = news_flash
                    .read()
                    .await
                    .as_ref()
                    .map(NewsFlash::get_semaphore)
                    .unwrap_or(Arc::new(Semaphore::new(1)));

                let client = App::client().await;
                news_flash::feed_parser::download_and_parse_feed(&url_clone, &feed_id, None, semaphore, &client).await
            });

            match future.await.expect(JOIN_ERROR) {
                Ok(parsed_url) => {
                    obj.emit_by_name::<()>("feed-parsed", &[&GParsedUrl::from(parsed_url)]);
                    obj.set_is_busy(false);
                }
                Err(error) => {
                    let url = url.as_str();
                    tracing::error!(%url, %error, "No feed found for url");
                    obj.set_is_busy(false);
                    obj.emit_by_name::<()>("error", &[&i18n("No Feed found")]);
                }
            }
        }
    }
}

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

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

impl ParseFeedWidget {
    pub async fn parse_url_str(&self, url: &str) {
        self.set_feed_url(url);
        self.imp().parse_feed_url().await;
    }

    fn update_can_parse(&self) {
        let is_idle = !self.is_busy();
        let have_feed_url = !self.feed_url().is_empty();

        self.set_can_parse(have_feed_url && is_idle);
    }
}
