use crate::systemd_gui::new_settings;
use gettextrs::pgettext;
use glib::{self, GString};
use gtk::{
    gio::Settings,
    pango::{self, FontDescription},
    prelude::SettingsExt,
};
use std::sync::{LazyLock, RwLock};
use strum::EnumIter;
use systemd::time_handling::TimestampStyle;
use tracing::{info, warn};

pub static PREFERENCES: LazyLock<Preferences> = LazyLock::new(|| {
    let settings = new_settings();
    Preferences::new_with_setting(settings)
});

const KEY_DBUS_LEVEL: &str = "pref-dbus-level";
pub const KEY_PREF_JOURNAL_COLORS: &str = "pref-journal-colors";
pub const KEY_PREF_JOURNAL_EVENTS_BATCH_SIZE: &str = "pref-journal-events-batch-size";
pub const KEY_PREF_JOURNAL_EVENT_MAX_SIZE: &str = "pref-journal-event-max-size";
pub const KEY_PREF_JOURNAL_DISPLAY_ORDER: &str = "pref-journal-display-order";
pub const KEY_PREF_JOURNAL_DISPLAY_FOLLOW: &str = "pref-journal-display-follow";
pub const KEY_PREF_UNIT_DESCRIPTION_WRAP: &str = "pref-unit-description-wrap";
pub const KEY_PREF_UNIT_FILE_STYLE_SCHEME: &str = "pref-unit-file-style-scheme";
pub const KEY_PREF_APP_FIRST_CONNECTION: &str = "pref-app-first-connection";
pub const KEY_PREF_TIMESTAMP_STYLE: &str = "pref-timestamp-style";
pub const KEY_PREF_STYLE_TEXT_FONT_FAMILY: &str = "pref-style-text-font-family";
pub const KEY_PREF_STYLE_TEXT_FONT_SIZE: &str = "pref-style-text-font-size";
pub const KEY_PREF_UNIT_LIST_DISPLAY_COLORS: &str = "pref-unit-list-display-colors";
pub const KEY_PREF_PREFERRED_COLOR_SCHEME: &str = "pref-preferred-color-scheme";
pub const KEY_PREF_ORIENTATION_MODE: &str = "pref-window-orientaion-mode";
pub const KEY_PREF_PROP_ORIENTATION_MODE: &str = "pref-window-properties-orientation-vertical";
pub const KEY_PREF_CASE_INSENSITIVE_DEFAULT: &str = "pref-case-insensitive-default";

#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, EnumIter, glib::Enum)]
#[enum_type(name = "DbusLevel")]
pub enum DbusLevel {
    #[default]
    #[enum_value(name = "session")]
    UserSession,

    #[enum_value(name = "system")]
    System,
    #[enum_value(name = "system_session")]
    SystemAndSession,
}

impl DbusLevel {
    pub fn as_str(&self) -> &str {
        match self {
            DbusLevel::UserSession => "session",
            DbusLevel::System => "system",
            DbusLevel::SystemAndSession => "system_session",
        }
    }

    pub fn label(&self) -> String {
        match self {
            //browser dbus option
            DbusLevel::UserSession => pgettext("dbus", "User Session Bus"),
            //browser dbus option
            DbusLevel::System => pgettext("dbus", "System Bus"),
            //browser dbus option
            DbusLevel::SystemAndSession => pgettext("dbus", "System & User Session Bus"),
        }
    }
}

impl From<GString> for DbusLevel {
    fn from(level: GString) -> Self {
        level.as_str().into()
    }
}

impl From<&str> for DbusLevel {
    fn from(level: &str) -> Self {
        match level.to_ascii_lowercase().as_str() {
            "system" => DbusLevel::System,
            "session" => DbusLevel::UserSession,
            _ => DbusLevel::SystemAndSession,
        }
    }
}

impl From<i32> for DbusLevel {
    fn from(level: i32) -> Self {
        match level {
            0 => DbusLevel::UserSession,
            1 => DbusLevel::System,
            _ => DbusLevel::SystemAndSession,
        }
    }
}

impl From<u32> for DbusLevel {
    fn from(level: u32) -> Self {
        DbusLevel::from(level as i32)
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, glib::Enum)]
#[enum_type(name = "EnableUnitFileMode")]
pub enum EnableUnitFileMode {
    #[default]
    Command = 0,
    DBus = 1,
}

impl EnableUnitFileMode {
    pub fn as_str(&self) -> &str {
        match self {
            EnableUnitFileMode::Command => "Subprocess call",
            EnableUnitFileMode::DBus => "D-bus call",
        }
    }
}

impl From<GString> for EnableUnitFileMode {
    fn from(level: GString) -> Self {
        level.as_str().into()
    }
}

impl From<&str> for EnableUnitFileMode {
    fn from(level: &str) -> Self {
        if "System".eq(level) {
            EnableUnitFileMode::Command
        } else {
            EnableUnitFileMode::DBus
        }
    }
}

impl From<u32> for EnableUnitFileMode {
    fn from(level: u32) -> Self {
        match level {
            1 => EnableUnitFileMode::DBus,
            _ => EnableUnitFileMode::Command,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, EnumIter)]
pub enum PreferredColorScheme {
    #[default]
    Default,
    PreferDark,
    PreferLight,
    ForceDark,
    ForceLight,
}

impl PreferredColorScheme {
    pub fn text(&self) -> String {
        match self {
            //preference color scheme option
            PreferredColorScheme::Default => pgettext("color scheme", "No Preference"),
            //preference color scheme option
            PreferredColorScheme::PreferDark => pgettext("color scheme", "Prefer Dark"),
            //preference color scheme option
            PreferredColorScheme::PreferLight => pgettext("color scheme", "Prefer Light"),
            //preference color scheme option
            PreferredColorScheme::ForceDark => pgettext("color scheme", "Force Dark"),
            //preference color scheme option
            PreferredColorScheme::ForceLight => pgettext("color scheme", "Force Light"),
        }
    }

    pub fn color_scheme(&self) -> adw::ColorScheme {
        match self {
            PreferredColorScheme::Default => adw::ColorScheme::Default,
            PreferredColorScheme::PreferLight => adw::ColorScheme::PreferLight,
            PreferredColorScheme::ForceDark => adw::ColorScheme::ForceDark,
            PreferredColorScheme::ForceLight => adw::ColorScheme::ForceLight,
            PreferredColorScheme::PreferDark => adw::ColorScheme::PreferDark,
        }
    }
}

impl From<PreferredColorScheme> for adw::ColorScheme {
    fn from(value: PreferredColorScheme) -> Self {
        value.color_scheme()
    }
}

impl From<i32> for PreferredColorScheme {
    fn from(value: i32) -> Self {
        match value {
            adw::ffi::ADW_COLOR_SCHEME_DEFAULT => PreferredColorScheme::Default,
            adw::ffi::ADW_COLOR_SCHEME_FORCE_LIGHT => PreferredColorScheme::ForceLight,
            adw::ffi::ADW_COLOR_SCHEME_PREFER_LIGHT => PreferredColorScheme::PreferLight,
            adw::ffi::ADW_COLOR_SCHEME_PREFER_DARK => PreferredColorScheme::PreferDark,
            adw::ffi::ADW_COLOR_SCHEME_FORCE_DARK => PreferredColorScheme::ForceDark,
            _ => PreferredColorScheme::Default,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, EnumIter)]
pub enum OrientationMode {
    #[default]
    Automatic,
    ForceHorizontal,
    ForceVertical,
}

impl OrientationMode {
    pub fn label(&self) -> String {
        match self {
            //preference application orientation option
            OrientationMode::Automatic => pgettext("pref orientation", "Automatic"),
            //preference application orientation option
            OrientationMode::ForceHorizontal => pgettext("pref orientation", "Side by Side"),
            //preference application orientation option
            OrientationMode::ForceVertical => pgettext("pref orientation", "Top Bottom"),
        }
    }

    pub fn icon_name(&self) -> Option<&str> {
        match self {
            OrientationMode::Automatic => None,
            OrientationMode::ForceHorizontal => Some("side-by-side"),
            OrientationMode::ForceVertical => Some("top-down"),
        }
    }

    pub fn key(&self) -> &str {
        match self {
            OrientationMode::Automatic => "auto",
            OrientationMode::ForceHorizontal => "side-by-side",
            OrientationMode::ForceVertical => "top-down",
        }
    }

    pub fn from_key(key: &str) -> Self {
        match key {
            "side-by-side" => OrientationMode::ForceHorizontal,
            "top-down" => OrientationMode::ForceVertical,
            _ => OrientationMode::Automatic,
        }
    }
}

impl From<u32> for OrientationMode {
    fn from(value: u32) -> Self {
        match value {
            0 => OrientationMode::Automatic,
            1 => OrientationMode::ForceHorizontal,
            2 => OrientationMode::ForceVertical,
            _ => OrientationMode::Automatic,
        }
    }
}

pub struct Preferences {
    dbus_level: RwLock<DbusLevel>,
    journal_colors: RwLock<bool>,
    journal_events_batch_size: RwLock<u32>,
    journal_event_max_size: RwLock<u32>,
    unit_file_style_scheme: RwLock<String>,
    app_first_connection: RwLock<bool>,
    timestamp_style: RwLock<TimestampStyle>,
    font_family: RwLock<String>,
    font_size: RwLock<u32>,
}

impl Preferences {
    pub fn new_with_setting(settings: Settings) -> Self {
        let level = settings.string(KEY_DBUS_LEVEL).into();
        let journal_colors = settings.boolean(KEY_PREF_JOURNAL_COLORS);
        let journal_events_batch_size = settings.uint(KEY_PREF_JOURNAL_EVENTS_BATCH_SIZE);
        let journal_event_max_size = settings.uint(KEY_PREF_JOURNAL_EVENT_MAX_SIZE);
        let app_first_connection = settings.boolean(KEY_PREF_APP_FIRST_CONNECTION);
        let timestamp_style = settings.string(KEY_PREF_TIMESTAMP_STYLE).into();
        let font_family = settings.string(KEY_PREF_STYLE_TEXT_FONT_FAMILY);
        let font_size = settings.uint(KEY_PREF_STYLE_TEXT_FONT_SIZE);
        let unit_file_style_scheme = settings.string(KEY_PREF_UNIT_FILE_STYLE_SCHEME);

        Preferences {
            dbus_level: RwLock::new(level),
            journal_colors: RwLock::new(journal_colors),
            journal_events_batch_size: RwLock::new(journal_events_batch_size),
            journal_event_max_size: RwLock::new(journal_event_max_size),
            app_first_connection: RwLock::new(app_first_connection),
            timestamp_style: RwLock::new(timestamp_style),
            font_family: RwLock::new(font_family.to_string()),
            font_size: RwLock::new(font_size),
            unit_file_style_scheme: RwLock::new(unit_file_style_scheme.to_string()),
        }
    }

    pub fn dbus_level(&self) -> DbusLevel {
        *self.dbus_level.read().unwrap()
    }

    pub fn journal_colors(&self) -> bool {
        *self.journal_colors.read().unwrap()
    }

    pub fn journal_max_events_batch_size(&self) -> u32 {
        *self.journal_events_batch_size.read().unwrap()
    }

    pub fn journal_event_max_size(&self) -> u32 {
        *self.journal_event_max_size.read().unwrap()
    }

    pub fn is_app_first_connection(&self) -> bool {
        *self.app_first_connection.read().unwrap()
    }

    pub fn timestamp_style(&self) -> TimestampStyle {
        *self.timestamp_style.read().unwrap()
    }

    pub fn font_family(&self) -> String {
        let read = self.font_family.read().unwrap();
        read.clone()
    }

    pub fn unit_file_style_scheme(&self) -> String {
        let read = self.unit_file_style_scheme.read().unwrap();
        read.clone()
    }

    pub fn font_size(&self) -> u32 {
        *self.font_size.read().unwrap()
    }

    pub fn set_dbus_level(&self, dbus_level: DbusLevel) {
        info!("set_dbus_level: {}", dbus_level.as_str());

        let mut self_dbus_level = self.dbus_level.write().expect("supposed to write");
        *self_dbus_level = dbus_level;
    }

    pub fn set_timestamp_style(&self, timestamp_style: TimestampStyle) {
        info!("set_timestamp_style: {timestamp_style}");

        let mut self_timestamp_style = self.timestamp_style.write().expect("supposed to write");
        *self_timestamp_style = timestamp_style;
    }

    pub fn set_and_save_dbus_level(&self, level: DbusLevel, settings: &Settings) {
        self.set_dbus_level(level);
        let level = self.dbus_level();
        match settings.set_string(KEY_DBUS_LEVEL, level.as_str()) {
            Ok(()) => info!(
                "Save setting '{KEY_DBUS_LEVEL}' with value {:?}",
                level.as_str()
            ),
            Err(e) => warn!("Save setting Error {e}"),
        }
    }

    pub fn set_journal_events_batch_size(&self, journal_events_batch_size_new: u32) {
        info!("set_journal_events: {journal_events_batch_size_new}");

        let mut journal_events_batch_size = self
            .journal_events_batch_size
            .write()
            .expect("supposed to write");
        *journal_events_batch_size = journal_events_batch_size_new;
    }

    pub fn set_journal_event_max_size(&self, journal_event_max_size_new: u32) {
        info!("journal_event_max_size: {journal_event_max_size_new}");

        let mut journal_event_max_size = self
            .journal_event_max_size
            .write()
            .expect("supposed to write");
        *journal_event_max_size = journal_event_max_size_new;
    }

    pub fn set_journal_colors(&self, display_color: bool) {
        info!("set_journal_colors: {display_color}");

        let mut journal_colors = self.journal_colors.write().expect("supposed to write");
        *journal_colors = display_color;
    }

    pub fn set_app_first_connection(&self, app_first_connection_new: bool) {
        info!("set_app_first_connection: {app_first_connection_new}");

        let mut app_first_connection = self
            .app_first_connection
            .write()
            .expect("supposed to write");
        *app_first_connection = app_first_connection_new;
    }

    fn set_font_family(&self, font_family: &str) {
        let mut font_family_rw = self.font_family.write().expect("supposed to write");
        *font_family_rw = font_family.to_string();
    }

    pub fn set_unit_file_style_scheme(&self, style_scheme: &str) {
        let mut unit_file_style_scheme_rw = self
            .unit_file_style_scheme
            .write()
            .expect("supposed to write");
        *unit_file_style_scheme_rw = style_scheme.to_string()
    }

    fn set_font_size(&self, font_size: u32) {
        let mut font_size_rw = self.font_size.write().expect("supposed to write");
        *font_size_rw = font_size;
    }

    pub fn set_font(&self, font_description: &FontDescription) {
        let family = font_description.family().map_or(GString::new(), |f| f);
        self.set_font_family(&family);

        let size = font_description.size() / pango::SCALE;
        self.set_font_size(size as u32);
    }

    pub fn set_font_default(&self) {
        self.set_font_family("");
        self.set_font_size(0);
    }
}

#[cfg(test)]
mod tests {

    use super::*;
    use crate::gtk::glib::translate::IntoGlib;

    #[test]
    fn test_dbus_level_any_number() {
        assert_eq!(
            <u32 as Into<DbusLevel>>::into(1000),
            DbusLevel::SystemAndSession
        )
    }

    #[test]
    fn test_dbus_level_int_mapping() {
        //assert_num_mapping(EnablementStatus::Unasigned);
        assert_num_mapping(DbusLevel::UserSession);
        assert_num_mapping(DbusLevel::System);
        assert_num_mapping(DbusLevel::SystemAndSession);
    }

    #[test]
    fn test_dbus_level_string_mapping() {
        //assert_num_mapping(EnablementStatus::Unasigned);
        assert_string_mapping(DbusLevel::UserSession, "Session");
        assert_string_mapping(DbusLevel::System, "System");
        assert_string_mapping(DbusLevel::SystemAndSession, "system_session");
    }

    fn assert_num_mapping(level: DbusLevel) {
        let val = level as u32;
        let convert: DbusLevel = val.into();
        assert_eq!(convert, level)
    }

    fn assert_string_mapping(level: DbusLevel, key: &str) {
        let convert: DbusLevel = key.into();
        assert_eq!(convert, level)
    }

    #[test]
    fn test_preferred_color_sheme() {
        let list = [
            PreferredColorScheme::Default,
            PreferredColorScheme::ForceLight,
            PreferredColorScheme::PreferDark,
            PreferredColorScheme::PreferLight,
            PreferredColorScheme::ForceDark,
        ];

        for p_color in list {
            let a_color: adw::ColorScheme = p_color.into();

            let i = a_color.into_glib();

            let b_color: PreferredColorScheme = i.into();

            assert_eq!(p_color, b_color);
        }
    }
}
