//! Settings page — autostart toggle + agent-status section.

use crate::dbus::AgentClient;
use crate::window::ZbplWindow;
use adw::prelude::*;
use adw::subclass::prelude::*;
use gtk::{glib, CompositeTemplate};
use std::cell::{Cell, OnceCell};
use std::rc::Rc;

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate)]
    #[template(resource = "/com/printplatformpro/BrowserAgent/ui/settings_page.ui")]
    pub struct ZbplSettingsPage {
        #[template_child]
        pub autostart_row: TemplateChild<adw::SwitchRow>,
        #[template_child]
        pub version_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub http_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub http_status_icon: TemplateChild<gtk::Image>,
        #[template_child]
        pub https_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub https_status_icon: TemplateChild<gtk::Image>,
        #[template_child]
        pub agent_status_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub agent_status_icon: TemplateChild<gtk::Image>,
        #[template_child]
        pub restart_button: TemplateChild<gtk::Button>,

        pub agent: OnceCell<Rc<AgentClient>>,
        /// Suppresses the SwitchRow's notify::active handler while
        /// we're programmatically updating it from `refresh()`.
        /// Otherwise toggling would echo back to SetAutostart and
        /// flicker the row (or, worse, race with the previous call).
        pub updating: Cell<bool>,
    }

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

        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();
        }
        fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }

    impl ObjectImpl for ZbplSettingsPage {}
    impl WidgetImpl for ZbplSettingsPage {}
    impl PreferencesPageImpl for ZbplSettingsPage {}
}

glib::wrapper! {
    pub struct ZbplSettingsPage(ObjectSubclass<imp::ZbplSettingsPage>)
        @extends gtk::Widget, adw::PreferencesPage,
        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
}

impl ZbplSettingsPage {
    pub fn bind_agent(&self, agent: Rc<AgentClient>) {
        let imp = self.imp();
        imp.agent.set(agent).ok();

        // Connect the toggle handler exactly once. We deliberately
        // do NOT capture the agent inline — pull it back out via
        // imp.agent each fire so a hypothetical mid-life rebind
        // would still see the right one. In practice OnceCell
        // means one bind ever, so this is also future-proofing.
        let this = self.clone();
        imp.autostart_row.connect_active_notify(move |sw| {
            let imp = this.imp();
            if imp.updating.get() {
                // We just programmatically set this from refresh();
                // ignore the echo.
                return;
            }
            let Some(agent) = imp.agent.get() else { return };
            let want = sw.is_active();
            let toast_target = this
                .root()
                .and_then(|r| r.downcast::<ZbplWindow>().ok());
            match agent.set_autostart(want) {
                Ok(()) => {
                    if let Some(w) = toast_target.as_ref() {
                        let msg = if want {
                            "Auto-start enabled"
                        } else {
                            "Auto-start disabled"
                        };
                        w.toast(msg);
                    }
                }
                Err(e) => {
                    tracing::error!("set_autostart({want}): {e:?}");
                    if let Some(w) = toast_target.as_ref() {
                        w.toast(&format!("Could not change auto-start: {e}"));
                    }
                    // Bounce visually so the row reflects reality.
                    imp.updating.set(true);
                    sw.set_active(!want);
                    imp.updating.set(false);
                }
            }
        });

        // Wire the Restart button. The d-bus call returns immediately
        // but the agent's actual exit is on a ~200ms timer, so we
        // toast right away and the next refresh() will see the new
        // PID once systemd respawns.
        let this = self.clone();
        imp.restart_button.connect_clicked(move |btn| {
            let imp = this.imp();
            let Some(agent) = imp.agent.get() else { return };
            btn.set_sensitive(false);
            let toast_target = this.root().and_then(|r| r.downcast::<ZbplWindow>().ok());
            match agent.request_restart() {
                Ok(()) => {
                    if let Some(w) = toast_target.as_ref() {
                        w.toast("Restarting agent…");
                    }
                }
                Err(e) => {
                    tracing::error!("request_restart: {e:?}");
                    if let Some(w) = toast_target.as_ref() {
                        w.toast(&format!("Could not restart agent: {e}"));
                    }
                }
            }
            // Re-enable after 3s so the user can retry if the
            // systemd respawn didn't take.
            let btn_weak = btn.downgrade();
            glib::timeout_add_seconds_local_once(3, move || {
                if let Some(b) = btn_weak.upgrade() {
                    b.set_sensitive(true);
                }
            });
            // Refresh state shortly after — by then the new agent
            // process should be up and answering d-bus again.
            let this_weak = this.downgrade();
            glib::timeout_add_seconds_local_once(2, move || {
                if let Some(p) = this_weak.upgrade() {
                    p.refresh();
                }
            });
        });

        self.refresh();
    }

    pub fn refresh(&self) {
        let imp = self.imp();
        let Some(agent) = imp.agent.get() else { return };

        match agent.get_version() {
            Ok(v) => {
                imp.version_row.set_subtitle(&v);
                imp.agent_status_row.set_subtitle("Connected");
                imp.agent_status_icon.set_icon_name(Some("emblem-ok-symbolic"));
            }
            Err(e) => {
                tracing::warn!("agent unreachable: {e:?}");
                imp.version_row.set_subtitle("—");
                imp.agent_status_row.set_subtitle(
                    "Agent not running. Start: systemctl --user start pppro-browser-linux",
                );
                imp.agent_status_icon
                    .set_icon_name(Some("dialog-warning-symbolic"));
            }
        }

        // Per-listener bind state. Older agent versions (no
        // GetAgentHealth method) silently fall through to placeholder
        // text — the row still labels the canonical addr so the user
        // knows what's expected.
        match agent.get_agent_health() {
            Ok(h) => {
                let http_addr = if h.http_addr.is_empty() {
                    "127.0.0.1:9100".to_string()
                } else {
                    h.http_addr.clone()
                };
                if h.http_ok {
                    imp.http_row.set_subtitle(&format!("http://{}", http_addr));
                    imp.http_status_icon
                        .set_icon_name(Some("emblem-ok-symbolic"));
                } else {
                    let reason = if h.http_err.is_empty() {
                        "not bound".to_string()
                    } else {
                        h.http_err.clone()
                    };
                    imp.http_row
                        .set_subtitle(&format!("FAILED on {} — {}", http_addr, reason));
                    imp.http_status_icon
                        .set_icon_name(Some("dialog-error-symbolic"));
                }
                let https_addr = if h.https_addr.is_empty() {
                    "127.0.0.1:9101".to_string()
                } else {
                    h.https_addr.clone()
                };
                if h.https_ok {
                    imp.https_row.set_subtitle(&format!("https://{}", https_addr));
                    imp.https_status_icon
                        .set_icon_name(Some("emblem-ok-symbolic"));
                } else {
                    let reason = if h.https_err.is_empty() {
                        "not bound".to_string()
                    } else {
                        h.https_err.clone()
                    };
                    imp.https_row
                        .set_subtitle(&format!("FAILED on {} — {}", https_addr, reason));
                    imp.https_status_icon
                        .set_icon_name(Some("dialog-error-symbolic"));
                }
            }
            Err(_) => {
                imp.http_row.set_subtitle("http://127.0.0.1:9100");
                imp.http_status_icon
                    .set_icon_name(Some("content-loading-symbolic"));
                imp.https_row.set_subtitle("https://127.0.0.1:9101");
                imp.https_status_icon
                    .set_icon_name(Some("content-loading-symbolic"));
            }
        }

        if let Ok(state) = agent.get_autostart_state() {
            imp.updating.set(true);
            imp.autostart_row.set_sensitive(state.is_supported());
            imp.autostart_row.set_active(state.is_enabled());
            imp.updating.set(false);
        }
    }
}
