use adw::{self, prelude::*};
use gettextrs::gettext;
use gtk::{glib, glib::clone, subclass::prelude::*, CompositeTemplate};
use tracing::error;

use super::Device;
use crate::{
    components::{AuthError, SpinnerButton},
    gettext_f, spawn,
    system_settings::ClockFormat,
    toast, Application,
};

mod imp {
    use std::cell::{Cell, RefCell};

    use glib::subclass::InitializingObject;

    use super::*;

    #[derive(Debug, Default, CompositeTemplate, glib::Properties)]
    #[template(
        resource = "/org/gnome/Fractal/ui/session/view/account_settings/devices_page/device_row.ui"
    )]
    #[properties(wrapper_type = super::DeviceRow)]
    pub struct DeviceRow {
        #[template_child]
        pub display_name: TemplateChild<gtk::Label>,
        #[template_child]
        pub verified_icon: TemplateChild<gtk::Image>,
        #[template_child]
        pub last_seen_ip: TemplateChild<gtk::Label>,
        #[template_child]
        pub last_seen_ts: TemplateChild<gtk::Label>,
        #[template_child]
        pub delete_logout_button: TemplateChild<SpinnerButton>,
        #[template_child]
        pub verify_button: TemplateChild<SpinnerButton>,
        /// The device displayed by this row.
        #[property(get, set = Self::set_device, construct_only)]
        pub device: RefCell<Option<Device>>,
        /// Whether this is the device of the current session.
        #[property(get, construct_only)]
        pub is_current_device: Cell<bool>,
        pub system_settings_handler: RefCell<Option<glib::SignalHandlerId>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for DeviceRow {
        const NAME: &'static str = "AccountSettingsDeviceRow";
        type Type = super::DeviceRow;
        type ParentType = gtk::ListBoxRow;

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

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

    #[glib::derived_properties]
    impl ObjectImpl for DeviceRow {
        fn constructed(&self) {
            self.parent_constructed();
            let obj = self.obj();

            match &obj.is_current_device() {
                false => self
                    .delete_logout_button
                    .set_label(gettext("Disconnect Session")),
                true => {
                    self.delete_logout_button.set_label(gettext("Log Out"));
                    self.delete_logout_button
                        .add_css_class("destructive-action");
                }
            }

            self.delete_logout_button
                .connect_clicked(clone!(@weak obj => move |_| {
                    match &obj.is_current_device() {
                        false=> obj.delete(),
                        true => obj.activate_action("account-settings.logout", None).unwrap()
                    }
                }));

            self.verify_button
                .connect_clicked(clone!(@weak obj => move |_| {
                    todo!("Not implemented");
                }));

            let system_settings = Application::default().system_settings();
            let system_settings_handler =
                system_settings.connect_clock_format_notify(clone!(@weak obj => move |_| {
                    obj.update_last_seen_ts();
                }));
            self.system_settings_handler
                .replace(Some(system_settings_handler));
        }

        fn dispose(&self) {
            if let Some(handler) = self.system_settings_handler.take() {
                Application::default().system_settings().disconnect(handler);
            }
        }
    }

    impl WidgetImpl for DeviceRow {}
    impl ListBoxRowImpl for DeviceRow {}

    impl DeviceRow {
        /// Set the device displayed by this row.
        fn set_device(&self, device: Device) {
            let obj = self.obj();

            self.display_name.set_label(&device.display_name());
            obj.set_tooltip_text(Some(device.device_id().as_str()));

            self.verified_icon.set_visible(device.verified());
            // TODO: Implement verification
            // imp.verify_button.set_visible(!device.is_verified());

            let last_seen_ip = device.last_seen_ip();
            if let Some(last_seen_ip) = &last_seen_ip {
                self.last_seen_ip.set_label(last_seen_ip);
            }
            self.last_seen_ip.set_visible(last_seen_ip.is_some());

            self.last_seen_ts
                .set_visible(device.last_seen_ts().is_some());

            self.device.replace(Some(device));
            obj.notify_device();

            obj.update_last_seen_ts();
        }
    }
}

glib::wrapper! {
    /// A row presenting a device.
    pub struct DeviceRow(ObjectSubclass<imp::DeviceRow>)
        @extends gtk::Widget, gtk::ListBoxRow, @implements gtk::Accessible;
}

impl DeviceRow {
    pub fn new(device: &Device, is_current_device: bool) -> Self {
        glib::Object::builder()
            .property("device", device)
            .property("is-current-device", is_current_device)
            .build()
    }

    fn delete(&self) {
        self.imp().delete_logout_button.set_loading(true);

        let Some(device) = self.device() else {
            return;
        };

        spawn!(clone!(@weak self as obj => async move {
            let window: Option<gtk::Window> = obj.root().and_downcast();
            match device.delete(window.as_ref()).await {
                Ok(_) => obj.set_visible(false),
                Err(AuthError::UserCancelled) => {},
                Err(error) => {
                    error!("Failed to disconnect device {}: {error:?}", device.device_id());
                    let device_name = device.display_name();
                    // Translators: Do NOT translate the content between '{' and '}', this is a variable name.
                    let error_message = gettext_f("Failed to disconnect device “{device_name}”", &[("device_name", &device_name)]);
                    toast!(obj, error_message);
                },
            }
            obj.imp().delete_logout_button.set_loading(false);
        }));
    }

    /// Update the last seen timestamp according to the current device and clock
    /// format setting.
    fn update_last_seen_ts(&self) {
        let Some(datetime) = self.device().and_then(|d| d.last_seen_ts()) else {
            return;
        };

        let clock_format = Application::default().system_settings().clock_format();
        let use_24 = clock_format == ClockFormat::TwentyFourHours;

        // This was ported from Nautilus and simplified for our use case.
        // See: https://gitlab.gnome.org/GNOME/nautilus/-/blob/1c5bd3614a35cfbb49de087bc10381cdef5a218f/src/nautilus-file.c#L5001
        let now = glib::DateTime::now_local().unwrap();
        let format;
        let days_ago = {
            let today_midnight =
                glib::DateTime::from_local(now.year(), now.month(), now.day_of_month(), 0, 0, 0f64)
                    .unwrap();

            let date = glib::DateTime::from_local(
                datetime.year(),
                datetime.month(),
                datetime.day_of_month(),
                0,
                0,
                0f64,
            )
            .unwrap();

            today_midnight.difference(&date).as_days()
        };

        // Show only the time if date is on today
        if days_ago == 0 {
            if use_24 {
                // Translators: Time in 24h format, i.e. "23:04".
                // Do not change the time format as it will follow the system settings.
                // See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
                format = gettext("Last seen at %H:%M");
            } else {
                // Translators: Time in 12h format, i.e. "11:04 PM".
                // Do not change the time format as it will follow the system settings.
                // See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
                format = gettext("Last seen at %I:%M %p");
            }
        }
        // Show the word "Yesterday" and time if date is on yesterday
        else if days_ago == 1 {
            if use_24 {
                // Translators: this a time in 24h format, i.e. "Last seen yesterday at 23:04".
                // Do not change the time format as it will follow the system settings.
                // See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
                // xgettext:no-c-format
                format = gettext("Last seen yesterday at %H:%M");
            } else {
                // Translators: this is a time in 12h format, i.e. "Last seen Yesterday at 11:04
                // PM".
                // Do not change the time format as it will follow the system settings.
                // See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
                // xgettext:no-c-format
                format = gettext("Last seen yesterday at %I:%M %p");
            }
        }
        // Show a week day and time if date is in the last week
        else if days_ago > 1 && days_ago < 7 {
            if use_24 {
                // Translators: this is the name of the week day followed by a time in 24h
                // format, i.e. "Last seen Monday at 23:04".
                // Do not change the time format as it will follow the system settings.
                //  See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
                // xgettext:no-c-format
                format = gettext("Last seen %A at %H:%M");
            } else {
                // Translators: this is the week day name followed by a time in 12h format, i.e.
                // "Last seen Monday at 11:04 PM".
                // Do not change the time format as it will follow the system settings.
                // See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
                // xgettext:no-c-format
                format = gettext("Last seen %A at %I:%M %p");
            }
        } else if datetime.year() == now.year() {
            if use_24 {
                // Translators: this is the month and day and the time in 24h format, i.e. "Last
                // seen February 3 at 23:04".
                // Do not change the time format as it will follow the system settings.
                // See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
                // xgettext:no-c-format
                format = gettext("Last seen %B %-e at %H:%M");
            } else {
                // Translators: this is the month and day and the time in 12h format, i.e. "Last
                // seen February 3 at 11:04 PM".
                // Do not change the time format as it will follow the system settings.
                // See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
                // xgettext:no-c-format
                format = gettext("Last seen %B %-e at %I:%M %p");
            }
        } else if use_24 {
            // Translators: this is the full date and the time in 24h format, i.e. "Last
            // seen February 3 2015 at 23:04".
            // Do not change the time format as it will follow the system settings.
            // See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
            // xgettext:no-c-format
            format = gettext("Last seen %B %-e %Y at %H:%M");
        } else {
            // Translators: this is the full date and the time in 12h format, i.e. "Last
            // seen February 3 2015 at 11:04 PM".
            // Do not change the time format as it will follow the system settings.
            // See `man strftime` or the documentation of g_date_time_format for the available specifiers: <https://docs.gtk.org/glib/method.DateTime.format.html>
            // xgettext:no-c-format
            format = gettext("Last seen %B %-e %Y at %I:%M %p");
        }

        let label = datetime.format(&format).unwrap();
        self.imp().last_seen_ts.set_label(&label);
    }
}
