use std::sync::Arc;

use ashpd::{desktop::settings::Settings as SettingsProxy, zvariant};
use futures_util::StreamExt;
use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
use tracing::error;

use super::{ClockFormat, SystemSettings, SystemSettingsImpl};
use crate::{spawn, spawn_tokio};

const GNOME_DESKTOP_NAMESPACE: &str = "org.gnome.desktop.interface";
const CLOCK_FORMAT_KEY: &str = "clock-format";

mod imp {
    use super::*;

    #[derive(Debug, Default)]
    pub struct LinuxSystemSettings {}

    #[glib::object_subclass]
    impl ObjectSubclass for LinuxSystemSettings {
        const NAME: &'static str = "LinuxSystemSettings";
        type Type = super::LinuxSystemSettings;
        type ParentType = SystemSettings;
    }

    impl ObjectImpl for LinuxSystemSettings {
        fn constructed(&self) {
            self.parent_constructed();
            let obj = self.obj();

            spawn!(clone!(
                #[weak]
                obj,
                async move {
                    obj.init().await;
                }
            ));
        }
    }

    impl SystemSettingsImpl for LinuxSystemSettings {}
}

glib::wrapper! {
    /// API to access system settings on Linux.
    pub struct LinuxSystemSettings(ObjectSubclass<imp::LinuxSystemSettings>)
        @extends SystemSettings;
}

impl LinuxSystemSettings {
    pub fn new() -> Self {
        glib::Object::new()
    }

    /// Initialize the system settings.
    async fn init(&self) {
        let proxy = match spawn_tokio!(async move { SettingsProxy::new().await })
            .await
            .unwrap()
        {
            Ok(proxy) => proxy,
            Err(error) => {
                error!("Could not access settings portal: {error}");
                return;
            }
        };
        let proxy = Arc::new(proxy);

        let proxy_clone = proxy.clone();
        match spawn_tokio!(async move {
            proxy_clone
                .read::<ClockFormat>(GNOME_DESKTOP_NAMESPACE, CLOCK_FORMAT_KEY)
                .await
        })
        .await
        .unwrap()
        {
            Ok(clock_format) => self
                .upcast_ref::<SystemSettings>()
                .set_clock_format(clock_format),
            Err(error) => {
                error!("Could not access clock format system setting: {error}");
                return;
            }
        };

        let clock_format_changed_stream = match spawn_tokio!(async move {
            proxy
                .receive_setting_changed_with_args::<ClockFormat>(
                    GNOME_DESKTOP_NAMESPACE,
                    CLOCK_FORMAT_KEY,
                )
                .await
        })
        .await
        .unwrap()
        {
            Ok(stream) => stream,
            Err(error) => {
                error!("Could not listen to changes of the clock format system setting: {error}");
                return;
            }
        };

        let obj_weak = self.downgrade();
        clock_format_changed_stream.for_each(move |value| {
            let obj_weak = obj_weak.clone();
            async move {
                let clock_format = match value {
                    Ok(clock_format) => clock_format,
                    Err(error) => {
                        error!("Could not update clock format setting: {error}");
                        return;
                    }
                };

                if let Some(obj) = obj_weak.upgrade() {
                    obj.upcast_ref::<SystemSettings>().set_clock_format(clock_format);
                } else {
                    error!("Could not update clock format setting: could not upgrade weak reference");
                }
            }
        }).await;
    }
}

impl Default for LinuxSystemSettings {
    fn default() -> Self {
        Self::new()
    }
}

impl TryFrom<&zvariant::OwnedValue> for ClockFormat {
    type Error = zvariant::Error;

    fn try_from(value: &zvariant::OwnedValue) -> Result<Self, Self::Error> {
        let Ok(s) = <&str>::try_from(value) else {
            return Err(zvariant::Error::IncorrectType);
        };

        match s {
            "12h" => Ok(Self::TwelveHours),
            "24h" => Ok(Self::TwentyFourHours),
            _ => Err(zvariant::Error::Message(format!(
                "Invalid string `{s}`, expected `12h` or `24h`"
            ))),
        }
    }
}

impl TryFrom<zvariant::OwnedValue> for ClockFormat {
    type Error = zvariant::Error;

    fn try_from(value: zvariant::OwnedValue) -> Result<Self, Self::Error> {
        Self::try_from(&value)
    }
}
