use crate::consts::ACTION_DAEMON_RELOAD_BUS;
use crate::format2;
use crate::{
    analyze::build_analyze_window,
    consts::ACTION_DAEMON_RELOAD,
    systemd,
    widget::{
        app_window::AppWindow,
        info_window,
        preferences::{
            PreferencesDialog,
            data::{DbusLevel, PREFERENCES},
        },
        signals_dialog::SignalsWindow,
    },
};
use adw::prelude::*;
use base::consts::APP_ID;
use base::enums::UnitDBusLevel;
use gettextrs::gettext;
use glib::VariantTy;
use gtk::{gio, glib, prelude::ActionMapExtManual};
use tracing::{error, info, warn};

pub const APP_TITLE: &str = "SysD Manager";

include!(concat!(env!("OUT_DIR"), "/release_notes.rs"));

pub fn on_startup(app: &adw::Application) {
    let about = gio::ActionEntry::builder("about")
        .activate(|application: &adw::Application, _, _| {
            let about = create_about();
            if let Some(win) = application.active_window() {
                about.present(Some(&win));
            } else {
                about.present(None::<&gtk::Widget>);
            }
        })
        .build();

    let analyze_blame = gio::ActionEntry::builder("analyze_blame")
        .activate(|application: &adw::Application, _b, _c| {
            let wins = application.windows();
            match build_analyze_window() {
                Ok(analyze_blame_window) => {
                    if let Some(first_window) = wins.first() {
                        analyze_blame_window.set_transient_for(Some(first_window));
                        analyze_blame_window.set_modal(true);
                    }

                    analyze_blame_window.present();
                }
                Err(sd_error) => {
                    let resp = sd_error.gui_description();

                    if let Some(resp) = resp {
                        let alert = adw::AlertDialog::builder()
                            .heading("Unavailable")
                            .body_use_markup(true)
                            .body(resp)
                            .close_response("close")
                            .build();

                        alert.add_response("close", "Close");

                        let firt_window = wins.first();
                        alert.present(firt_window);
                    }
                }
            };
        })
        .build();

    let signals = gio::ActionEntry::builder("signals")
        .activate(|application: &adw::Application, _, _| {
            let Some(window) = application.active_window() else {
                warn!("No window");
                return;
            };

            let Some(app_window) = window.downcast_ref::<AppWindow>() else {
                warn!("No app window");
                return;
            };

            let signals_window = if let Some(signals_window) = app_window.signals_window() {
                signals_window
            } else {
                let signals_window = SignalsWindow::new(app_window);
                app_window.set_signal_window(Some(&signals_window));
                //signals_window.set_transient_for(Some(&window));
                signals_window
            };

            signals_window.present();
        })
        .build();

    let systemd_info = gio::ActionEntry::builder("systemd_info")
        .activate(|application: &adw::Application, _, _| {
            let systemd_info_window = info_window::InfoWindow::new(None);
            systemd_info_window.fill_systemd_info();

            if let Some(first_window) = application.windows().first() {
                systemd_info_window.set_transient_for(Some(first_window));
                systemd_info_window.set_modal(true);
            }

            systemd_info_window.present();
        })
        .build();

    const ACTION_NAME_PREFERENCES: &str = "app.preferences";
    let preferences_action_entry: gio::ActionEntry<adw::Application> =
        gio::ActionEntry::builder(&ACTION_NAME_PREFERENCES[4..])
            .activate(|application: &adw::Application, _, _| {
                if let Some(win) = application.active_window() {
                    let app_window: Option<&AppWindow> = win.downcast_ref::<AppWindow>();

                    let pdialog = PreferencesDialog::new(app_window);
                    pdialog.present(Some(&win));
                    //pdialog.present(Some(&win));
                    //gtk::prelude::GtkWindowExt::present(&pdialog);
                } else {
                    let pdialog = PreferencesDialog::new(None);
                    pdialog.present(None::<&gtk::Widget>);
                }
            })
            .build();

    const ACTION_NAME_PROXY_MANAGEMENT: &str = "app.proxy-management";
    #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
    {
        let proxy_management_action_entry: gio::ActionEntry<adw::Application> =
            gio::ActionEntry::builder(&ACTION_NAME_PROXY_MANAGEMENT[4..])
                .activate(|application: &adw::Application, _, _| {
                    if let Some(win) = application.active_window() {
                        let app_window: Option<&AppWindow> = win.downcast_ref::<AppWindow>();

                        let pdialog = PreferencesDialog::new(app_window);
                        pdialog.set_visible_page_name("proxy");
                        pdialog.present(Some(&win));
                        //pdialog.present(Some(&win));
                        //gtk::prelude::GtkWindowExt::present(&pdialog);
                    } else {
                        let pdialog = PreferencesDialog::new(None);
                        pdialog.present(None::<&gtk::Widget>);
                    }
                })
                .build();

        app.add_action_entries([proxy_management_action_entry]);
    }
    app.set_accels_for_action(ACTION_NAME_PROXY_MANAGEMENT, &["<Ctrl>period"]);

    let daemon_reload_all_units: gio::ActionEntry<adw::Application> =
        gio::ActionEntry::builder(&ACTION_DAEMON_RELOAD[4..])
            .activate(|application: &adw::Application, simple_action, _variant| {
                let simple_action = simple_action.clone();
                let application = application.clone();

                let Some(app_win_op) = application
                    .active_window()
                    .and_downcast_ref::<AppWindow>()
                    .cloned()
                else {
                    error!("Not an AppWindow");
                    return;
                };

                let dbus_level = PREFERENCES.dbus_level();

                daemon_reload_with_dialog(simple_action, app_win_op, dbus_level);
            })
            .build();

    let daemon_reload_all_units_with_bus: gio::ActionEntry<adw::Application> =
        gio::ActionEntry::builder(ACTION_DAEMON_RELOAD_BUS)
            .activate(
                |application: &adw::Application, simple_action, variant: Option<&glib::Variant>| {
                    let Some(app_win_op) = application
                        .active_window()
                        .and_downcast_ref::<AppWindow>()
                        .cloned()
                    else {
                        error!("Not an AppWindow");
                        return;
                    };
                    let user_session = variant.and_then(|v| v.get::<bool>());
                    let dbus_level = match user_session {
                        Some(true) => DbusLevel::UserSession,
                        Some(false) => DbusLevel::System,
                        None => {
                            warn!("No user session specified, using default");
                            PREFERENCES.dbus_level()
                        }
                    };
                    let simple_action = simple_action.clone();
                    daemon_reload_with_dialog(simple_action, app_win_op, dbus_level);
                },
            )
            .parameter_type(Some(VariantTy::BOOLEAN))
            .build();

    app.set_accels_for_action(ACTION_NAME_PREFERENCES, &["<Ctrl>comma"]);

    app.add_action_entries([
        about,
        analyze_blame,
        systemd_info,
        preferences_action_entry,
        daemon_reload_all_units,
        signals,
        daemon_reload_all_units_with_bus,
    ]);
}

fn daemon_reload_with_dialog(
    simple_action: gio::SimpleAction,
    app_win_op: AppWindow,
    dbus_level: DbusLevel,
) {
    match dbus_level {
        DbusLevel::UserSession => {
            daemon_relaod(simple_action, app_win_op, UnitDBusLevel::UserSession)
        }
        DbusLevel::System => daemon_relaod(simple_action, app_win_op, UnitDBusLevel::System),
        DbusLevel::SystemAndSession => {
            const SYSTEM: &str = "system";
            const USER: &str = "user";
            const CLOSE: &str = "zclose";

            let body = "Need to determine the bus to use for daemon reload.\n\n\
                        Please select either <b>System</b> or <b>User\u{00A0}Session</b> bus.";

            let alert = adw::AlertDialog::builder()
                .heading("Select bus")
                .body_use_markup(true)
                .body(body)
                .close_response(CLOSE)
                .can_close(true)
                .build();

            alert.add_responses(&[
                (CLOSE, "_Cancel"),
                (USER, "_User Session"),
                (SYSTEM, "_System"),
            ]);

            alert.set_response_appearance(SYSTEM, adw::ResponseAppearance::Suggested);
            alert.set_response_appearance(USER, adw::ResponseAppearance::Suggested);

            {
                let win = app_win_op.clone();
                alert.connect_response(None, move |_dialog, response| {
                    info!("Response {response}");

                    match response {
                        SYSTEM => {
                            daemon_relaod(simple_action.clone(), win.clone(), UnitDBusLevel::System)
                        }
                        USER => daemon_relaod(
                            simple_action.clone(),
                            win.clone(),
                            UnitDBusLevel::UserSession,
                        ),
                        _ => {
                            warn!("Dialog response not handled: {response}");
                        }
                    }
                });
            }
            alert.present(Some(&app_win_op));
        }
    }
}

fn daemon_relaod(
    simple_action: gio::SimpleAction,
    app_win_op: AppWindow,
    dbus_level: UnitDBusLevel,
) {
    glib::spawn_future_local(async move {
        simple_action.set_enabled(false);

        let (sender, receiver) = tokio::sync::oneshot::channel();
        systemd::runtime().spawn(async move {
            let response = systemd::daemon_reload(dbus_level).await;
            if let Err(e) = sender.send(response) {
                error!("Channel closed unexpectedly: {e:?}");
            }
        });

        let Ok(response) = receiver
            .await
            .inspect_err(|err| error!("Tokio channel dropped {err:?}"))
        else {
            return;
        };

        let user_session = dbus_level.user_session();

        match response {
            Ok(_) => {
                info!("All units reloaded! User session {}", user_session);
                let instance_level = if user_session {
                    //instance level user
                    gettext("user")
                } else {
                    //instance level system
                    gettext("system")
                };

                let msg = format2!(
                    "Systemd manager configuration reloaded at <b>{}</b> level!",
                    instance_level
                );
                add_toast(&app_win_op, &msg);
            }
            Err(e) => {
                error!("Daemon Reload failed {e:?}");
                let msg = gettext("Daemon Reload failed!");
                add_toast(&app_win_op, &msg); //TODO make red
            }
        }
        simple_action.set_enabled(true);
    });
}

fn add_toast(app_window: &AppWindow, toast_msg: &str) {
    app_window.add_toast_message(toast_msg, true, None);
}

fn create_about() -> adw::AboutDialog {
    const VERSION: &str = env!("CARGO_PKG_VERSION");
    const CARGO_PKG_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
    const CARGO_PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");

    let authors: Vec<&str> = CARGO_PKG_AUTHORS.split(',').collect();

    let about = adw::AboutDialog::builder()
        .developers(authors.clone())
        .designers(authors)
        .name("About")
        .application_name(APP_TITLE)
        .application_icon(APP_ID)
        .version(VERSION)
        .license_type(gtk::License::Gpl30)
        .comments(CARGO_PKG_DESCRIPTION)
        .website("https://github.com/plrigaux/sysd-manager")
        .issue_url("https://github.com/plrigaux/sysd-manager/issues")
        .artists(["4nyNoob"])
        .translator_credits(
            "John Peter Sa <johnppetersa@gmail.com>
Pierre-Luc Rigaux
Priit Jõerüüt <hwlate@joeruut.com>",
        )
        .build();

    #[cfg(feature = "flatpak")]
    about.set_version(&format!("{} (Flatpak)", VERSION));

    #[cfg(feature = "appimage")]
    about.set_version(&format!("{} (AppImage)", VERSION));

    about.add_acknowledgement_section(
        //about dialogue
        Some(&gettext("Thank you for your support")),
        &["AsciiWolf", "Justin Searle", "Damglador"],
    );

    if let Some(rn_version) = RELEASE_NOTES_VERSION {
        about.set_release_notes_version(rn_version);
    }

    if let Some(release_notes) = RELEASE_NOTES {
        let mut release_notes = String::from(release_notes);
        release_notes.push_str("<p>_________________________</p><p>Full release notes:</p><p>https://github.com/plrigaux/sysd-manager/blob/main/CHANGELOG.md</p>");
        about.set_release_notes(&release_notes);
    }

    about
}
