use gio::ActionEntry;
use glib::{Variant, VariantType, subclass::prelude::*};
use libadwaita::prelude::*;
use news_flash::models::{CategoryID, FeedID, FeedMapping, TagID};

use crate::about_dialog::NewsFlashAbout;
use crate::add_dialog::{AddCategoryDialog, AddFeedDialog, AddTagDialog};
use crate::app::App;
use crate::discover::DiscoverDialog;
use crate::edit_category_dialog::EditCategoryDialog;
use crate::edit_feed_dialog::EditFeedDialog;
use crate::edit_tag_dialog::EditTagDialog;
use crate::error_dialog::ErrorDialog;
use crate::infrastructure::TokioRuntime;
use crate::main_window::MainWindow;
use crate::settings::SettingsDialog;
use crate::shortcuts_dialog::ShortcutsDialogBuilder;
use crate::util::JOIN_ERROR;

pub struct SpawnDialogActions;

impl SpawnDialogActions {
    pub fn setup() {
        // -------------------------
        // show discover dialog
        // -------------------------
        let discover = ActionEntry::builder("discover")
            .activate(|window, _action, _parameter| DiscoverDialog::default().present(Some(window)))
            .build();

        // -------------------------
        // show error dialog
        // -------------------------
        let show_error = ActionEntry::builder("show-error-dialog")
            .activate(|_window, _action, _parameter| Self::error_dialog())
            .build();

        // -------------------------
        // show shortcuts dialog
        // -------------------------
        let shortcuts = ActionEntry::builder("shortcut-window")
            .activate(|_window, _action, _parameter| Self::shortcut_dialog())
            .build();

        // -------------------------
        // about dialog
        // -------------------------
        let about = ActionEntry::builder("about-window")
            .activate(|window, _action, _parameter| NewsFlashAbout::show(window))
            .build();

        // -------------------------
        // show settings dialog
        // -------------------------
        let settings = ActionEntry::builder("settings")
            .activate(|window, _action, _parameter| SettingsDialog::default().present(Some(window)))
            .build();

        // -------------------------
        // add tag dialog
        // -------------------------
        let add_tag = ActionEntry::builder("add-tag-dialog")
            .activate(|window, _action, _parameter| AddTagDialog::default().present(Some(window)))
            .build();

        // -------------------------
        // add category dialog
        // -------------------------
        let add_category = ActionEntry::builder("add-category-dialog")
            .activate(|window, _action, _parameter| AddCategoryDialog::default().present(Some(window)))
            .build();

        // -------------------------
        // add feed dialog
        // -------------------------
        let add_feed = ActionEntry::builder("add-feed-dialog")
            .parameter_type(VariantType::new("s").ok().as_deref())
            .activate(|_window, _action, parameter| {
                let subscribe_url = parameter.and_then(Variant::str).filter(|url| !url.is_empty());
                Self::add_feed_dialog(subscribe_url);
            })
            .build();

        // -------------------------
        // edit category dialog
        // -------------------------
        let edit_category = ActionEntry::builder("edit-category-dialog")
            .parameter_type(Some(&String::static_variant_type()))
            .activate(|_window, _action, parameter| {
                let Some(category_id) = parameter.and_then(Variant::str).map(CategoryID::new) else {
                    return;
                };
                glib::spawn_future_local(Self::edit_category_dialog(category_id));
            })
            .build();

        // -------------------------
        // edit feed dialog
        // -------------------------
        let edit_feed = ActionEntry::builder("edit-feed-dialog")
            .parameter_type(VariantType::new("(ss)").ok().as_deref())
            .activate(|_window, _action, parameter| {
                let Some(parameter) = parameter else {
                    return;
                };
                let Some(feed_id) = parameter.child_value(0).str().map(FeedID::new) else {
                    return;
                };
                let Some(parent_id) = parameter.child_value(1).str().map(CategoryID::new) else {
                    return;
                };

                glib::spawn_future_local(Self::edit_feed_dialog(feed_id, parent_id));
            })
            .build();

        // -------------------------
        // edit tag dialog
        // -------------------------
        let edit_tag = ActionEntry::builder("edit-tag-dialog")
            .parameter_type(Some(&String::static_variant_type()))
            .activate(|_window, _action, parameter| {
                let Some(tag_id) = parameter.and_then(Variant::str).map(TagID::new) else {
                    return;
                };
                glib::spawn_future_local(Self::edit_tag_dialog(tag_id));
            })
            .build();

        MainWindow::instance().add_action_entries([
            discover,
            show_error,
            shortcuts,
            about,
            settings,
            add_tag,
            add_category,
            add_feed,
            edit_category,
            edit_feed,
            edit_tag,
        ]);
    }

    fn error_dialog() {
        let Some(error) = App::default().imp().news_flash_error.take() else {
            return;
        };

        let main_window = MainWindow::instance();
        let dialog = ErrorDialog::new(&error);
        dialog.present(Some(&main_window));
    }

    fn shortcut_dialog() {
        let main_window = MainWindow::instance();
        let settings = App::default().settings().keybindings();
        let dialog = ShortcutsDialogBuilder::build(&settings);
        dialog.present(Some(&main_window));
    }

    fn add_feed_dialog(url: Option<&str>) {
        let main_window = MainWindow::instance();
        let dialog = AddFeedDialog::default();
        dialog.present(Some(&main_window));
        if let Some(url) = url {
            let url = url.to_string();
            glib::spawn_future_local(async move {
                dialog.parse_url_str(&url).await;
            });
        }
    }

    async fn edit_category_dialog(category_id: CategoryID) {
        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()?;
            let categories = news_flash.get_categories().map(|(c, _m)| c).ok()?;
            categories.into_iter().find(|c| c.category_id == category_id)
        });

        if let Some(category) = future.await.expect(JOIN_ERROR) {
            EditCategoryDialog::new(category).present(Some(&MainWindow::instance()));
        } else {
            tracing::error!("Failed to find category for edit dialog");
        }
    }

    async fn edit_feed_dialog(feed_id: FeedID, parent_id: CategoryID) {
        let feed_mapping = FeedMapping {
            feed_id: feed_id.clone(),
            category_id: parent_id,
            sort_index: None,
        };

        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()?;
            let categories = news_flash.get_categories().map(|(c, _m)| c).ok()?;
            let feeds = news_flash.get_feeds().map(|(f, _m)| f).ok()?;
            let feed = feeds.into_iter().find(|f| f.feed_id == feed_id)?;
            Some((feed, categories))
        });

        if let Some((feed, categories)) = future.await.expect(JOIN_ERROR) {
            EditFeedDialog::new(feed, feed_mapping, categories).present(Some(&MainWindow::instance()));
        } else {
            tracing::error!("Failed to find feed for edit dialog");
        }
    }

    async fn edit_tag_dialog(tag_id: TagID) {
        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()?;
            let tags = news_flash.get_tags().map(|(tags, _taggings)| tags).ok()?;
            tags.into_iter().find(|t| t.tag_id == tag_id)
        });

        if let Some(tag) = future.await.expect(JOIN_ERROR) {
            EditTagDialog::new(tag).present(Some(&MainWindow::instance()));
        } else {
            tracing::error!("Failed to find tag for edit dialog");
        }
    }
}
