use crate::about_dialog::APP_NAME;
use crate::app::App;
use crate::article_list::ArticleList;
use crate::article_view::ArticleView;
use crate::config;
use crate::content_page::{ArticleListColumn, ContentPage};
use crate::global_actions::GlobalActions;
use crate::gobject_models::GSidebarSelection;
use crate::infrastructure::TokioRuntime;
use crate::login_screen::{CustomApiSecret, PasswordLogin, WebLogin};
use crate::reset_page::ResetPage;
use crate::saved_state::SavedState;
use crate::sidebar::{FeedList, SideBar};
use crate::themes::{NewsflashTheme, StyleManager};
use crate::util::{JOIN_ERROR, constants};
use crate::welcome_screen::WelcomePage;
use gio::{ActionGroup, ActionMap};
use glib::{self, ControlFlow, Variant, clone, subclass};
use gtk4::{
    self, Accessible, ApplicationWindow, Buildable, CompositeTemplate, ConstraintTarget, Native, Root, ShortcutManager,
    Widget, Window, prelude::*, subclass::prelude::*,
};
use libadwaita::{ApplicationWindow as AdwApplicationWindow, NavigationView, prelude::*, subclass::prelude::*};
use news_flash::models::{ApiSecret, ArticleID, LoginData, LoginGUI, PluginID, PluginInfo};
use news_flash::{NewsFlash, error::NewsFlashError};
use std::time::Duration;

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate)]
    #[template(file = "data/resources/ui_templates/main_window.blp")]
    pub struct MainWindow {
        #[template_child]
        pub navigation_view: TemplateChild<NavigationView>,
        #[template_child]
        pub welcome_page: TemplateChild<WelcomePage>,
        #[template_child]
        pub reset_page: TemplateChild<ResetPage>,
        #[template_child]
        pub content_page: TemplateChild<ContentPage>,
        #[template_child]
        pub password_login: TemplateChild<PasswordLogin>,
        #[template_child]
        pub web_login: TemplateChild<WebLogin>,
        #[template_child]
        pub api_secret_page: TemplateChild<CustomApiSecret>,
    }

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

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

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

    impl ObjectImpl for MainWindow {}

    impl WidgetImpl for MainWindow {}

    impl WindowImpl for MainWindow {}

    impl ApplicationWindowImpl for MainWindow {}

    impl AdwApplicationWindowImpl for MainWindow {}

    impl MainWindow {
        pub(super) async fn show_discover_for_local_rss(&self) {
            // show discover dialog after login with local rss
            let future = TokioRuntime::spawn(async move {
                let news_flash = App::news_flash();
                let news_flash_guad = news_flash.read().await;
                let news_flash = news_flash_guad.as_ref()?;
                news_flash.id().await
            });

            if future
                .await
                .expect(JOIN_ERROR)
                .map(|id| id.as_str() == "local_rss")
                .unwrap_or(false)
            {
                super::MainWindow::activate_action("discover", None);
            }
        }
    }
}

glib::wrapper! {
    pub struct MainWindow(ObjectSubclass<imp::MainWindow>)
        @extends Widget, Window, ApplicationWindow, AdwApplicationWindow,
        @implements Accessible, Buildable, ConstraintTarget, ActionMap, ActionGroup, Native, Root, ShortcutManager;
}

impl Default for MainWindow {
    fn default() -> Self {
        let window = glib::Object::new::<Self>();
        gtk4::prelude::GtkWindowExt::set_icon_name(&window, Some(config::APP_ID));
        gtk4::prelude::GtkWindowExt::set_title(&window, Some(APP_NAME));
        window
    }
}

impl MainWindow {
    pub fn instance() -> Self {
        App::default()
            .active_window()
            .expect("window not initialized")
            .downcast::<MainWindow>()
            .expect("Not of type MainWindow")
    }

    pub fn activate_action(name: &str, target: Option<&Variant>) {
        let win = Self::instance();

        if !win.is_action_enabled(name) {
            tracing::debug!(name, "action is not enabled");
        }

        ActionGroupExt::activate_action(&win, name, target)
    }

    pub fn init(&self) {
        let imp = self.imp();

        if let Ok(state) = SavedState::new_from_file() {
            let (width, height) = state.window_size;
            self.set_default_size(width, height);

            if state.maximized {
                self.maximize();
            }
        }

        if config::PROFILE == "Devel" {
            self.add_css_class("devel");
        }

        StyleManager::generate_preview_tile_css();

        let theme = App::default().settings().general().theme();
        StyleManager::apply_theme(&theme);

        libadwaita::StyleManager::default().connect_dark_notify(|_sm| {
            glib::timeout_add_local(Duration::from_millis(20), move || {
                StyleManager::generate_preview_tile_css();
                let theme = App::default().settings().general().theme();

                if theme == NewsflashTheme::Default {
                    StyleManager::apply_theme(&theme);
                }

                ControlFlow::Break
            });
        });

        self.connect_fullscreened_notify(|window| {
            let imp = window.imp();

            if imp.content_page.is_media_view_visible() {
                return;
            }

            if window.is_fullscreen() {
                imp.content_page.save_pre_fullscreen_state();

                glib::timeout_add_local(
                    Duration::from_millis(150),
                    glib::clone!(
                        #[weak]
                        imp,
                        #[upgrade_or]
                        ControlFlow::Break,
                        move || {
                            imp.content_page.show_only_article_view();
                            ControlFlow::Break
                        }
                    ),
                );
            } else {
                imp.content_page.restore_pre_fullscreen_state();
            }
        });

        GlobalActions::setup();

        // setup background permissions
        if App::default().settings().general().keep_running_in_background() {
            App::request_background_permission(App::default().settings().general().autostart());
        }

        // set visible page
        imp.navigation_view.replace_with_tags(&[constants::CONTENT_PAGE]);
        imp.content_page.load_branding();
    }

    pub fn show_welcome_page(&self) {
        let imp = self.imp();

        imp.navigation_view.replace_with_tags(&[constants::WELCOME_PAGE]);
    }

    pub fn show_login_page(&self, plugin_id: &PluginID, data: Option<LoginData>) {
        let imp = self.imp();

        imp.web_login.reset();
        imp.password_login.reset();
        imp.api_secret_page.reset();

        if let Some(info) = NewsFlash::list_backends().remove(plugin_id) {
            match &info.login_gui {
                LoginGUI::OAuth(oauth_info) => {
                    if oauth_info.custom_api_secret {
                        imp.api_secret_page
                            .set_service(info.id.clone(), oauth_info.custom_api_secret_url.clone());

                        if let Some(LoginData::OAuth(oauth_data)) = data {
                            if let Some(api_secret) = oauth_data.custom_api_secret {
                                imp.api_secret_page.fill(api_secret);
                            } else {
                                imp.api_secret_page.reset();
                            }
                        }

                        imp.navigation_view.push_by_tag(constants::API_SECRET_PAGE);
                    } else if let Ok(()) = imp.web_login.set_service(info) {
                        imp.navigation_view.push_by_tag(constants::WEB_PAGE);
                    }
                }
                LoginGUI::Direct(_) => {
                    if let Ok(()) = imp.password_login.set_service(info) {
                        if let Some(LoginData::Direct(data)) = data {
                            imp.password_login.fill(data);
                        } else {
                            imp.password_login.reset();
                        }
                        imp.navigation_view.push_by_tag(constants::PASSWORD_PAGE);
                    }
                }
                LoginGUI::None => {}
            }
        }
    }

    pub fn show_web_login_page(&self, info: PluginInfo, secret: Option<&ApiSecret>) {
        let imp = self.imp();

        _ = imp.web_login.set_service(info);
        imp.web_login.set_custom_client_id(secret.map(|s| s.client_id.clone()));
        imp.web_login
            .set_custom_client_secret(secret.map(|s| s.client_secret.clone()));
        imp.navigation_view.push_by_tag(constants::WEB_PAGE);
    }

    pub fn show_login_error(&self, error: NewsFlashError, data: &LoginData) {
        let imp = self.imp();

        match &data {
            LoginData::OAuth(_) => imp.web_login.show_error(error),
            LoginData::Direct(_) => imp.password_login.show_error(error),
            LoginData::None(_) => {}
        }
    }

    pub fn show_content_page(&self) {
        let imp = self.imp();

        imp.navigation_view.replace_with_tags(&[constants::CONTENT_PAGE]);
        imp.content_page.update_sidebar();
        imp.content_page.load_branding();

        glib::spawn_future_local(clone!(
            #[weak]
            imp,
            async move {
                imp.show_discover_for_local_rss().await;
            }
        ));
    }

    pub fn show_reset_page(&self) {
        let imp = self.imp();
        imp.reset_page.reset();
        imp.navigation_view.push_by_tag(constants::RESET_PAGE);
    }

    pub fn reset_account_failed(&self, error: NewsFlashError) {
        let imp = self.imp();
        imp.reset_page.error(error);
    }

    pub fn is_page_visible(&self, page_tag: &str) -> bool {
        self.imp()
            .navigation_view
            .visible_page()
            .and_then(|page| page.tag().map(|tag| tag.as_str() == page_tag))
            .unwrap_or(false)
    }

    pub fn oauth_callback(&self, url: &str) {
        if self.is_page_visible("web_login") {
            self.imp().web_login.oauth_callback(url)
        }
    }

    pub fn harvest_restore_relevant_state() {
        let article_list_column = ArticleListColumn::instance();
        let article_list = ArticleList::instance();
        let main_window = MainWindow::instance();
        let article_view = ArticleView::instance();
        let sidebar = SideBar::instance();
        let feedlist = FeedList::instance();
        let content_page = ContentPage::instance();

        let sidebar_visible = if content_page.is_default_layout() && content_page.is_outer_collapsed() {
            tracing::debug!("override sidebar visible on medium layout");
            false
        } else {
            article_list_column.show_sidebar()
        };

        let state = SavedState {
            sidebar_selection: sidebar.selection(),
            article_list_mode: article_list_column.mode(),
            search_term: article_list_column.search_term(),
            prefer_scraped_content: article_view.prefer_scraped_content(),
            selected_article: if article_list.selected_index() == gtk4::INVALID_LIST_POSITION {
                None
            } else {
                let index = article_list.selected_index();
                let article_id = article_list
                    .selected_model()
                    .map(|model| model.article_id().as_ref().as_str().to_string());
                article_id.map(|id| (index, id))
            },
            window_size: main_window.default_size(),
            maximized: main_window.is_maximized(),
            article_view_visible: content_page.is_article_view_visible(),
            sidebar_visible,
            subscriptions_expanded: sidebar.feedlist_expanded(),
            tags_expanded: sidebar.taglist_expanded(),
            expanded_categories: feedlist.get_expanded_categories(),
            volume: App::default().volume(),
            ..Default::default()
        };

        tracing::debug!(?state, "save state");

        if let Err(error) = state.write() {
            tracing::error!(%error, "Failed to serialize content page state");
        }
    }

    pub fn restore_state() {
        ContentPage::instance().update_sidebar();

        glib::idle_add_local(move || {
            let state = SavedState::new_from_file()
                .inspect_err(|error| {
                    tracing::error!(%error, "Failed to deserialize the content page state");
                })
                .unwrap_or_default();

            tracing::debug!(?state, "restore state");

            let app = App::default();
            let article_list_column = ArticleListColumn::instance();
            let article_view = ArticleView::instance();
            let sidebar = SideBar::instance();
            let feedlist = FeedList::instance();
            let content_page = ContentPage::instance();

            article_list_column.load_queue().set_enabled(false);

            sidebar.set_feedlist_expanded(state.subscriptions_expanded);
            sidebar.set_taglist_expanded(state.tags_expanded);

            feedlist.restore_expanded_categories(state.expanded_categories);

            article_list_column.set_search_term(state.search_term);

            if let Some((pos, id)) = state.selected_article {
                let article_id = ArticleID::new(&id);
                let page_size = i64::max((pos + 2) as i64, constants::ARTICLE_LIST_PAGE_SIZE);
                article_list_column.set_custom_page_size(Some(page_size));
                article_list_column.set_custom_article_to_load(Some(article_id.clone()));

                glib::timeout_add_local(Duration::from_millis(1200), move || {
                    ArticleList::instance().select_and_scroll_to(article_id.clone().into());
                    ArticleListColumn::instance().set_custom_page_size(None);
                    ArticleListColumn::instance().set_custom_article_to_load(None);
                    ControlFlow::Break
                });
            } else {
                ArticleList::instance().set_selected_index(gtk4::INVALID_LIST_POSITION);
            }

            let selection: GSidebarSelection = state.sidebar_selection.clone();
            sidebar.set_selection(selection);
            article_list_column.set_mode(state.article_list_mode);
            article_list_column.set_show_sidebar(state.sidebar_visible);

            article_view.set_prefer_scraped_content(state.prefer_scraped_content);
            content_page.show_article_view(state.article_view_visible);

            app.set_volume(state.volume);

            article_list_column.load_queue().set_enabled(true);
            article_list_column.new_list();
            ControlFlow::Break
        });
    }
}
