use std::sync::LazyLock;

use base::enums::UnitDBusLevel;
use gtk::{
    glib::{self, Binding, Quark},
    prelude::*,
};
use systemd::{
    data::get_custom_property_typed_raw,
    enums::UnitType,
    errors::SystemdErrors,
    socket_unit::SocketUnitInfo,
    time_handling::{self, MSEC_PER_SEC},
    timestamp_is_set,
};
use tracing::{error, warn};

use crate::{
    consts::*,
    widget::{
        preferences::data::PREFERENCES, unit_list::column::SysdColumn,
        unit_properties_selector::data_selection::UnitPropertySelection,
    },
};
use crate::{
    systemd::{
        data::UnitInfo,
        enums::{ActiveState, LoadState, Preset, UnitFileStatus},
    },
    widget::unit_list::UnitListPanel,
};

static BIND_INFO: LazyLock<Quark> = LazyLock::new(|| Quark::from_str("I"));
static BIND_CSS: LazyLock<Quark> = LazyLock::new(|| Quark::from_str("C"));
static BIND_CSS2: LazyLock<Quark> = LazyLock::new(|| Quark::from_str("C2"));

const CSS_CLASSES: &str = "css-classes";
const TEXT: &str = "text";
pub const PROPERTY_NAME: &str = "socket-listen-idx";

macro_rules! downcast_list_item {
    ($list_item_object:expr) => {{
        $list_item_object
            .downcast_ref::<gtk::ListItem>()
            .expect("item.downcast_ref::<gtk::ListItem>()")
    }};
}

fn factory_setup(_factory: &gtk::SignalListItemFactory, object: &glib::Object) {
    let list_item = downcast_list_item!(object);
    let inscription = gtk::Inscription::builder()
        //        .xalign(0.0)
        .wrap_mode(gtk::pango::WrapMode::None)
        .build();

    list_item.set_child(Some(&inscription));
}

macro_rules! factory_bind_pre {
    ($list_item_object:expr) => {{
        let list_item = downcast_list_item!($list_item_object);
        let inscription = list_item
            .child()
            .and_downcast::<gtk::Inscription>()
            .expect("item.downcast_ref::<gtk::Inscription>()");
        let unit_binding = list_item
            .item()
            .and_downcast::<UnitInfo>()
            .expect("item.downcast_ref::<gtk::UnitBinding>()");
        (inscription, unit_binding)
    }};
}

macro_rules! factory_bind {
    ($item_obj:expr, $func:ident) => {{
        let (inscription, unit) = factory_bind_pre!($item_obj);
        let text = $func(&unit);
        inscription.set_text(Some(&text));
        (inscription, unit)
    }};
}

macro_rules! factory_bind_enum {
    ($item_obj:expr, $func:ident) => {{
        let (inscription, unit) = factory_bind_pre!($item_obj);
        let text = unit.$func().as_str();
        inscription.set_text(Some(text));
        (inscription, unit)
    }};
}

const ACTIVE_STATE: &str = "active_state";
const CSS_GREY: &str = "grey";

macro_rules! display_inactive {
    ($widget:expr, $unit:expr) => {
        let state = $unit.active_state();
        if state.is_inactive() {
            $widget.set_css_classes(&[CSS_GREY]);
        } else {
            $widget.set_css_classes(&[]);
        }
    };
}

macro_rules! factory_connect_unbind {
    ($factory:expr, $($bind_id:expr), *) => {
        $factory.connect_unbind(|_factory, object| {
            let list_item = downcast_list_item!(object);
            let Some(child) = list_item.child() else {
                warn!("No child");
                return;
            };
            $(
                unbind(&child, $bind_id);
            )*
        });
    };
}

fn store_binding(object: &impl IsA<gtk::Widget>, key: Quark, binding: Binding) {
    unsafe {
        object.set_qdata(key, binding);
    }
}

fn unbind(child: &gtk::Widget, key: Quark) {
    let binding: Option<Binding> = unsafe { child.steal_qdata(key) };
    if let Some(binding) = binding {
        binding.unbind();
    }
}

fn inactive_display(widget: &impl IsA<gtk::Widget>, unit: &UnitInfo) {
    display_inactive!(widget, unit);

    let binding = unit
        .bind_property(ACTIVE_STATE, widget, CSS_CLASSES)
        .transform_to(|_, active_state: ActiveState| {
            let css_classes = if active_state.is_inactive() {
                [CSS_GREY].to_value()
            } else {
                [].to_value()
            };
            Some(css_classes)
        })
        .build();

    store_binding(widget, *BIND_CSS, binding);
}

pub fn fac_unit_name(display_color: bool) -> gtk::SignalListItemFactory {
    common_factory(display_color, UnitInfo::display_name)
}

pub fn fac_unit_name_full(display_color: bool) -> gtk::SignalListItemFactory {
    common_factory(display_color, UnitInfo::primary)
}

fn common_factory(
    display_color: bool,
    func: fn(&UnitInfo) -> String,
) -> gtk::SignalListItemFactory {
    let factory = gtk::SignalListItemFactory::new();

    factory.connect_setup(factory_setup);

    if display_color {
        factory.connect_bind(move |_factory, object| {
            let (inscription, unit) = factory_bind!(object, func);
            inactive_display(&inscription, &unit)
        });

        factory_connect_unbind!(factory, *BIND_CSS);
    } else {
        factory.connect_bind(move |_factory, object| {
            factory_bind!(object, func);
        });
    }
    factory
}

pub fn fac_unit_type(display_color: bool) -> gtk::SignalListItemFactory {
    let fac_unit_type = gtk::SignalListItemFactory::new();

    fac_unit_type.connect_setup(factory_setup);
    if display_color {
        fac_unit_type.connect_bind(move |_factory, object| {
            let (inscription, unit) = factory_bind_enum!(object, unit_type);
            inactive_display(&inscription, &unit)
        });
        factory_connect_unbind!(fac_unit_type, *BIND_CSS);
    } else {
        fac_unit_type.connect_bind(move |_factory, object| {
            factory_bind_enum!(object, unit_type);
        });
    }
    fac_unit_type
}

pub fn fac_bus(display_color: bool) -> gtk::SignalListItemFactory {
    let fac_bus = gtk::SignalListItemFactory::new();

    fac_bus.connect_setup(factory_setup);
    if display_color {
        fac_bus.connect_bind(move |_factory, object| {
            let (inscription, unit) = factory_bind_enum!(object, dbus_level);
            inactive_display(&inscription, &unit)
        });
        factory_connect_unbind!(fac_bus, *BIND_CSS);
    } else {
        fac_bus.connect_bind(move |_factory, object| {
            factory_bind_enum!(object, dbus_level);
        });
    }
    fac_bus
}

pub fn fac_active(display_color: bool) -> gtk::SignalListItemFactory {
    let fac_active = gtk::SignalListItemFactory::new();

    fac_active.connect_setup(|_factory, object| {
        let item = downcast_list_item!(object);
        let image = gtk::Image::new();
        item.set_child(Some(&image));
    });

    if display_color {
        fac_active.connect_bind(|_factory, object| {
            let (icon_image, unit) = active_icon(object);
            inactive_display(&icon_image, &unit)
        });

        factory_connect_unbind!(&fac_active, *BIND_INFO, *BIND_CSS);
    } else {
        fac_active.connect_bind(|_factory, object| {
            active_icon(object);
        });
        factory_connect_unbind!(&fac_active, *BIND_INFO);
    }
    fac_active
}

fn active_icon(object: &glib::Object) -> (gtk::Image, UnitInfo) {
    let list_item: &gtk::ListItem = downcast_list_item!(object);
    let icon_image = list_item.child().and_downcast::<gtk::Image>().unwrap();
    let unit = list_item.item().and_downcast::<UnitInfo>().unwrap();
    let state = unit.active_state();

    let icon_name = state.icon_name();
    icon_image.set_icon_name(icon_name);
    icon_image.set_tooltip_text(Some(state.as_str()));

    let binding = unit
        .bind_property("active_state", &icon_image, "icon-name")
        .transform_to(|_, state: ActiveState| state.icon_name())
        .build();

    store_binding(&icon_image, *BIND_INFO, binding);
    (icon_image, unit)
}

pub fn fac_sub_state(display_color: bool) -> gtk::SignalListItemFactory {
    common_factory(display_color, UnitInfo::sub_state)
}

pub fn fac_descrition(display_color: bool) -> gtk::SignalListItemFactory {
    let factory = gtk::SignalListItemFactory::new();

    factory.connect_setup(factory_setup);

    if display_color {
        factory.connect_bind(move |_factory, object| {
            let (inscription, unit) = factory_bind_pre!(object);
            let text = UnitInfo::description(&unit);
            inscription.set_text(text.as_deref());

            inactive_display(&inscription, &unit)
        });

        factory_connect_unbind!(factory, *BIND_CSS);
    } else {
        factory.connect_bind(move |_factory, object| {
            let (inscription, unit) = factory_bind_pre!(object);
            let text = UnitInfo::description(&unit);
            inscription.set_text(text.as_deref());
        });
    }
    factory
}

pub fn setup_factories(
    unit_list: &UnitListPanel,
    column_view_column_list: &Vec<gtk::ColumnViewColumn>,
    current_column_view_column_definition_list: &[UnitPropertySelection],
) {
    let display_color = unit_list.display_color();

    for column in column_view_column_list {
        let Some(id) = column.id() else {
            warn!("Column with no id!");
            continue;
        };

        let col_id = current_column_view_column_definition_list
            .iter()
            .find_map(|prop_selection| {
                if prop_selection.id().is_some_and(|s| id == s.as_str()) {
                    prop_selection.sysd_column()
                } else {
                    None
                }
            });

        if let Some(col_id) = col_id {
            let factory = get_factory_by_id(&col_id, display_color);
            column.set_factory(factory.as_ref());
        }
    }
}

pub fn get_factory_by_id(
    id: &SysdColumn,
    display_color: bool,
) -> Option<gtk::SignalListItemFactory> {
    match id {
        SysdColumn::Custom(_) => Some(get_custom_factory(id, display_color)),
        SysdColumn::Name => Some(fac_unit_name(display_color)),
        SysdColumn::FullName => Some(fac_unit_name_full(display_color)),
        SysdColumn::Type => Some(fac_unit_type(display_color)),
        SysdColumn::Bus => Some(fac_bus(display_color)),
        SysdColumn::State => Some(fac_enable_status(display_color)),
        SysdColumn::Preset => Some(fac_preset(display_color)),
        SysdColumn::Load => Some(fac_load_state(display_color)),
        SysdColumn::Active => Some(fac_active(display_color)),
        SysdColumn::SubState => Some(fac_sub_state(display_color)),
        SysdColumn::Description => Some(fac_descrition(display_color)),
        SysdColumn::TimerTimeNextElapseRT => Some(fac_time_next()),
        SysdColumn::TimerTimeLeftElapseMono => Some(fac_time_left()),
        SysdColumn::TimerTimePassed => Some(fac_time_passed()),
        SysdColumn::TimerTimeLast => Some(fac_time_last()),
        SysdColumn::SocketListenType => Some(fac_socket_listen_type()),
        SysdColumn::SocketListen => Some(fac_socket_listen()),
        SysdColumn::PathCondition => Some(fac_path_condition()),
        SysdColumn::Path => Some(fac_path_path()),
        SysdColumn::AutomountWhat => Some(fac_automount_what()),
        SysdColumn::AutomountMounted => Some(fac_automount_mounted()),
        SysdColumn::AutomountIdleTimeOut => Some(fac_automount_idle_timeout()),
        // _ => {
        //     warn!("What to do?. Id {id:?} not handle with factory");
        //     None
        // }
    }
}

const LOAD_STATE: &str = "load_state";
pub fn fac_load_state(display_color: bool) -> gtk::SignalListItemFactory {
    let fac_load_state = gtk::SignalListItemFactory::new();

    fac_load_state.connect_setup(factory_setup);

    if display_color {
        fac_load_state.connect_bind(move |_factory, object| {
            let (inscription, unit) = factory_bind_enum!(object, load_state);

            load_state_text_binding(&inscription, &unit);

            let binding = unit
                .bind_property(LOAD_STATE, &inscription, CSS_CLASSES)
                .transform_to(|_, load_state: LoadState| {
                    let css_classes = load_state_css_classes(load_state);
                    css_classes.map(|css| css.to_value())
                })
                .build();

            store_binding(&inscription, *BIND_CSS, binding);

            let binding = unit
                .bind_property(ACTIVE_STATE, &inscription, CSS_CLASSES)
                .transform_to(|_, active_state: ActiveState| {
                    let css_classes = if active_state.is_inactive() {
                        [CSS_GREY].to_value()
                    } else {
                        [].to_value()
                    };
                    Some(css_classes)
                })
                .build();

            store_binding(&inscription, *BIND_CSS2, binding);

            let load_state = unit.load_state();
            inscription.set_text(Some(load_state.as_str()));

            let css_classes = load_state_css_classes(load_state);
            if let Some(css) = css_classes {
                inscription.set_css_classes(&css);
            } else {
                display_inactive!(inscription, unit);
            }
        });
        factory_connect_unbind!(&fac_load_state, *BIND_INFO, *BIND_CSS, *BIND_CSS2);
    } else {
        fac_load_state.connect_bind(move |_factory, object| {
            let (inscription, unit) = factory_bind_enum!(object, load_state);
            load_state_text_binding(&inscription, &unit);
        });

        factory_connect_unbind!(&fac_load_state, *BIND_INFO);
    }

    fac_load_state
}

fn load_state_text_binding(inscription: &gtk::Inscription, unit: &UnitInfo) {
    let binding = unit
        .bind_property(LOAD_STATE, inscription, TEXT)
        .transform_to(|_, load_state: LoadState| Some(load_state.as_str()))
        .build();
    store_binding(inscription, *BIND_INFO, binding);
}

fn load_state_css_classes<'a>(load_state: LoadState) -> Option<[&'a str; 2]> {
    match load_state {
        LoadState::NotFound => Some(["yellow", "bold"]),
        LoadState::BadSetting | LoadState::Error | LoadState::Masked => Some(["red", "bold"]),
        _ => None,
    }
}

const ENABLE_STATUS: &str = "enable_status";
pub fn fac_enable_status(display_color: bool) -> gtk::SignalListItemFactory {
    let fac_enable_status = gtk::SignalListItemFactory::new();

    fac_enable_status.connect_setup(factory_setup);

    if display_color {
        fac_enable_status.connect_bind(move |_factory, object| {
            let (inscription, unit) = factory_bind_pre!(object);

            let status_code = unit.enable_status();
            inscription.set_text(Some(status_code.as_str()));
            inscription.set_tooltip_markup(status_code.tooltip_info().as_deref());

            let binding = unit
                .bind_property(ENABLE_STATUS, &inscription, TEXT)
                .transform_to(|_, enablement_status: UnitFileStatus| {
                    Some(enablement_status.as_str())
                })
                .build();

            store_binding(&inscription, *BIND_INFO, binding);

            let binding = unit
                .bind_property(ENABLE_STATUS, &inscription, CSS_CLASSES)
                .transform_to(|_, enablement_status: UnitFileStatus| {
                    let css_classes = enablement_css_classes(enablement_status);
                    css_classes.map(|css| css.to_value())
                })
                .build();

            store_binding(&inscription, *BIND_CSS, binding);

            let css_classes = enablement_css_classes(status_code);

            if let Some(css) = css_classes {
                inscription.set_css_classes(&css);
            } else {
                display_inactive!(inscription, unit);
            }
        });

        factory_connect_unbind!(&fac_enable_status, *BIND_INFO, *BIND_CSS);
    } else {
        fac_enable_status.connect_bind(move |_factory, object| {
            factory_bind_enum!(object, enable_status);
        });
    }
    fac_enable_status
}

fn enablement_css_classes<'a>(enablement_status: UnitFileStatus) -> Option<[&'a str; 2]> {
    match enablement_status {
        UnitFileStatus::Bad
        | UnitFileStatus::Disabled
        | UnitFileStatus::Masked
        | UnitFileStatus::MaskedRuntime => Some(["red", "bold"]),

        UnitFileStatus::Alias | UnitFileStatus::Enabled | UnitFileStatus::EnabledRuntime => {
            Some(["green", "bold"])
        }

        _ => None,
    }
}

const PRESET_NUM: &str = "preset";

pub fn fac_preset(display_color: bool) -> gtk::SignalListItemFactory {
    let fac_preset = gtk::SignalListItemFactory::new();

    fac_preset.connect_setup(factory_setup);

    if display_color {
        fac_preset.connect_bind(move |_factory, object| {
            let (inscription, unit) = factory_bind_enum!(object, preset);
            preset_text_binding(&inscription, &unit);

            let binding = unit
                .bind_property(PRESET_NUM, &inscription, CSS_CLASSES)
                .transform_to(|_s, preset_value: Preset| {
                    let css_classes = preset_css_classes(preset_value);
                    css_classes.map(|css| css.to_value())
                })
                .build();

            store_binding(&inscription, *BIND_CSS, binding);

            let preset_value = unit.preset();
            let css_classes = preset_css_classes(preset_value);

            if let Some(css) = css_classes {
                inscription.set_css_classes(&css);
            } else {
                display_inactive!(inscription, unit);
                inscription.set_css_classes(&[]);
            }
        });

        factory_connect_unbind!(&fac_preset, *BIND_INFO, *BIND_CSS);
    } else {
        fac_preset.connect_bind(move |_factory, object| {
            let (inscription, unit) = factory_bind_enum!(object, preset);
            preset_text_binding(&inscription, &unit);
        });
        factory_connect_unbind!(&fac_preset, *BIND_INFO);
    }
    fac_preset
}

fn preset_text_binding(inscription: &gtk::Inscription, unit: &UnitInfo) {
    let binding = unit
        .bind_property(PRESET_NUM, inscription, TEXT)
        .transform_to(|_s, preset: Preset| preset.as_str_op())
        .build();
    store_binding(inscription, *BIND_INFO, binding);
}

fn preset_css_classes(preset_value: Preset) -> Option<[&'static str; 2]> {
    match preset_value {
        Preset::Disabled => Some(["red", "bold"]),
        Preset::Enabled => Some(["green", "bold"]),
        Preset::Ignore => Some(["yellow", "bold"]),
        _ => None,
    }
}

type UnitFunc = fn(&UnitInfo, Quark) -> Option<String>;

type SocketFunc = fn(&SocketUnitInfo, Quark) -> Option<String>;

pub(super) fn get_custom_factory(
    sysd_col: &SysdColumn,
    display_color: bool,
) -> gtk::SignalListItemFactory {
    let factory = gtk::SignalListItemFactory::new();

    let key = sysd_col.generate_quark();
    factory.connect_setup(factory_setup);

    let Some(prop_type) = sysd_col.property_type() else {
        error!("NO PROP_TYPE SET for {:?}", sysd_col);
        return factory;
    };

    let (get_value, get_parent_value): (UnitFunc, SocketFunc) = match prop_type.as_str() {
        "b" => (
            UnitInfo::get_custom_property_to_string::<bool>,
            SocketUnitInfo::get_parent_qproperty_to_string::<bool>,
        ),
        "n" => (
            UnitInfo::get_custom_property_to_string::<i16>,
            SocketUnitInfo::get_parent_qproperty_to_string::<i16>,
        ),
        "q" => (
            UnitInfo::get_custom_property_to_string::<u16>,
            SocketUnitInfo::get_parent_qproperty_to_string::<u16>,
        ),
        "i" => (
            UnitInfo::get_custom_property_to_string::<i32>,
            SocketUnitInfo::get_parent_qproperty_to_string::<i32>,
        ),
        "u" => (
            UnitInfo::get_custom_property_to_string::<u32>,
            SocketUnitInfo::get_parent_qproperty_to_string::<u32>,
        ),
        "x" => (
            UnitInfo::get_custom_property_to_string::<i64>,
            SocketUnitInfo::get_parent_qproperty_to_string::<i64>,
        ),
        "t" => (
            UnitInfo::get_custom_property_to_string::<u64>,
            SocketUnitInfo::get_parent_qproperty_to_string::<u64>,
        ),
        "s" => (
            UnitInfo::get_custom_property_to_string::<String>,
            SocketUnitInfo::get_parent_qproperty_to_string::<String>,
        ),
        "v" => (
            UnitInfo::display_custom_property,
            SocketUnitInfo::display_custom_property,
        ),
        _ => (
            UnitInfo::get_custom_property_to_string::<String>,
            SocketUnitInfo::get_parent_qproperty_to_string::<String>,
        ),
    };

    factory.connect_bind(move |_factory, object| {
        let (inscription, unit) = factory_bind_pre!(object);
        if display_color {
            inactive_display(&inscription, &unit);
        }
        // let value = get_value(&unit, key);
        // println!("asdf key {:?} {:?}", key, value);
        let value = get_value(&unit, key).or_else(|| {
            if unit.has_property(PROPERTY_NAME) {
                unit.downcast_ref::<SocketUnitInfo>()
                    .and_then(|us| get_parent_value(us, key))
            } else {
                None
            }
        });

        inscription.set_text(value.as_deref());
    });

    factory
}

fn fac_time_next() -> gtk::SignalListItemFactory {
    let time_fac = gtk::SignalListItemFactory::new();

    time_fac.connect_setup(factory_setup);
    //WARN both columns needs to be present
    let next_elapse_realtime_key = SysdColumn::TimerTimeNextElapseRT.generate_quark();
    let next_elapse_monotonic_key = SysdColumn::TimerTimeLeftElapseMono.generate_quark();
    let timestamp_style = PREFERENCES.timestamp_style();
    time_fac.connect_bind(move |_factory, object| {
        let (inscription, unit) = factory_bind_pre!(object);
        inactive_display(&inscription, &unit);

        let next_elapse =
            calculate_next_elapse(next_elapse_realtime_key, next_elapse_monotonic_key, &unit);

        if next_elapse != u64::MAX {
            let time_since = time_handling::get_since_time(next_elapse, timestamp_style);
            inscription.set_text(Some(&time_since));
        } else {
            inscription.set_text(None);
        }
    });
    time_fac
}

fn fac_time_left() -> gtk::SignalListItemFactory {
    let time_fac = gtk::SignalListItemFactory::new();

    time_fac.connect_setup(factory_setup);
    let next_elapse_realtime_key = SysdColumn::TimerTimeNextElapseRT.generate_quark();
    let next_elapse_monotonic_key = SysdColumn::TimerTimeLeftElapseMono.generate_quark();
    time_fac.connect_bind(move |_factory, object| {
        let (inscription, unit) = factory_bind_pre!(object);
        inactive_display(&inscription, &unit);

        let next_elapse =
            calculate_next_elapse(next_elapse_realtime_key, next_elapse_monotonic_key, &unit);

        if next_elapse != u64::MAX {
            let time_relative = time_handling::format_timestamp_relative_full(next_elapse);
            inscription.set_text(Some(&time_relative));
        } else {
            inscription.set_text(None);
        }
    });

    time_fac
}

pub fn calculate_next_elapse<O>(
    next_elapse_realtime_key: Quark,
    next_elapse_monotonic_key: Quark,
    unit: &O,
) -> u64
where
    O: ObjectExt,
{
    let next_elapse_realtime =
        get_custom_property_typed_raw::<u64, O>(unit, next_elapse_realtime_key).unwrap_or(U64MAX);
    let next_elapse_monotonic =
        get_custom_property_typed_raw::<u64, O>(unit, next_elapse_monotonic_key).unwrap_or(U64MAX);

    time_handling::calc_next_elapse(next_elapse_realtime, next_elapse_monotonic)
}

fn fac_time_last() -> gtk::SignalListItemFactory {
    let time_fac = gtk::SignalListItemFactory::new();

    time_fac.connect_setup(factory_setup);

    let time_last_trigger_key = Quark::from_str(TIME_LAST_TRIGGER_USEC);
    let timestamp_style = PREFERENCES.timestamp_style();
    time_fac.connect_bind(move |_factory, object| {
        let (inscription, unit) = factory_bind_pre!(object);
        inactive_display(&inscription, &unit);

        let time_last_trigger =
            get_custom_property_typed_raw::<u64, UnitInfo>(&unit, time_last_trigger_key)
                .unwrap_or(U64MAX);

        if timestamp_is_set!(time_last_trigger) {
            let time_since = time_handling::get_since_time(time_last_trigger, timestamp_style);
            inscription.set_text(Some(&time_since));
        } else {
            inscription.set_text(None);
        }
    });
    time_fac
}

fn fac_time_passed() -> gtk::SignalListItemFactory {
    let time_fac = gtk::SignalListItemFactory::new();

    time_fac.connect_setup(factory_setup);
    //TODO Check if realtime should be better
    let time_last_trigger_key = Quark::from_str(TIME_LAST_TRIGGER_USEC);
    // let time_last_trigger_monotonic_key = Quark::from_str(TIME_LAST_TRIGGER_USEC_MONOTONIC);
    time_fac.connect_bind(move |_factory, object| {
        let (inscription, unit) = factory_bind_pre!(object);
        inactive_display(&inscription, &unit);

        let time_last_trigger =
            get_custom_property_typed_raw::<u64, UnitInfo>(&unit, time_last_trigger_key)
                .unwrap_or(U64MAX);

        if timestamp_is_set!(time_last_trigger) {
            let time_relative = time_handling::format_timestamp_relative_full(time_last_trigger);
            inscription.set_text(Some(&time_relative));
        } else {
            inscription.set_text(None);
        }
    });
    time_fac
}

#[macro_export]
macro_rules! extract_tuple_idx {
    ($unit: expr, $socket_listen: expr, $param : expr) => {
        match $param {
            0 => extract_listen!($unit, $socket_listen, 0),
            1 => extract_listen!($unit, $socket_listen, 1),
            _ => panic!("Not handled"),
        }
    };
}

#[macro_export]
macro_rules! extract_listen {
    ($unit: expr, $socket_listen: expr, $param : tt) => {{
        if $unit.has_property(PROPERTY_NAME) {
            if let Some(unit_socket) = $unit.downcast_ref::<SocketUnitInfo>() {
                let idx = unit_socket.socket_listen_idx();
                let listens =
                    unit_socket.get_parent_qproperty::<Vec<(String, String)>>($socket_listen);

                listens
                    .and_then(|v| v.get(idx as usize))
                    .map(|t| t.$param.as_str())
            } else {
                None
            }
        } else {
            let listens = $unit.get_custom_property::<Vec<(String, String)>>($socket_listen);
            listens.and_then(|v| v.first()).map(|t| t.$param.as_str())
        }
    }};
}

fn fac_socket_listen_type() -> gtk::SignalListItemFactory {
    let socket_fac = gtk::SignalListItemFactory::new();

    socket_fac.connect_setup(factory_setup);
    let socket_listen = SysdColumn::SocketListen.generate_quark();
    socket_fac.connect_bind(move |_, object| {
        let (inscription, unit) = factory_bind_pre!(object);
        let value = extract_listen!(unit, socket_listen, 0);
        inscription.set_text(value);
    });
    socket_fac
}

fn fac_socket_listen() -> gtk::SignalListItemFactory {
    let socket_fac = gtk::SignalListItemFactory::new();

    socket_fac.connect_setup(factory_setup);
    let socket_listen = SysdColumn::SocketListen.generate_quark();
    socket_fac.connect_bind(move |_, object| {
        let (inscription, unit) = factory_bind_pre!(object);

        let value = extract_listen!(unit, socket_listen, 1);
        inscription.set_text(value);
    });

    socket_fac
}

fn fac_path_condition() -> gtk::SignalListItemFactory {
    let socket_fac = gtk::SignalListItemFactory::new();

    socket_fac.connect_setup(factory_setup);
    let key = SysdColumn::PathCondition.generate_quark();
    socket_fac.connect_bind(move |_, object| {
        let (inscription, unit) = factory_bind_pre!(object);

        let value = extract_listen!(unit, key, 0);
        inscription.set_text(value);
    });

    socket_fac
}

fn fac_path_path() -> gtk::SignalListItemFactory {
    let socket_fac = gtk::SignalListItemFactory::new();

    socket_fac.connect_setup(factory_setup);
    let key = SysdColumn::Path.generate_quark();
    socket_fac.connect_bind(move |_, object| {
        let (inscription, unit) = factory_bind_pre!(object);

        let value = extract_listen!(unit, key, 1);
        inscription.set_text(value);
    });

    socket_fac
}

//TODO perf improve
fn fac_automount_mounted() -> gtk::SignalListItemFactory {
    let factory = gtk::SignalListItemFactory::new();
    factory.connect_setup(factory_setup);
    let where_key = SysdColumn::AutomountMounted.generate_quark();
    factory.connect_bind(move |_, object| {
        let (inscription, unit) = factory_bind_pre!(object);

        let time = unit.get_custom_property::<String>(where_key);

        let mounted =
            if let Some(unit_name) = time.and_then(|s| unit_name_from_path(s.as_str(), ".mount")) {
                fetch_property(unit.dbus_level(), &unit_name, UnitType::Unit, "ActiveState")
                    .inspect_err(|err| warn!("{err:?}"))
                    .map(|v| {
                        let state: ActiveState = v.into();
                        if state.is_inactive() { "no" } else { "yes" }
                    })
                    .ok()
            } else {
                None
            };

        inscription.set_text(mounted);
    });
    factory
}

//TODO perf improve
fn fac_automount_what() -> gtk::SignalListItemFactory {
    let factory = gtk::SignalListItemFactory::new();
    factory.connect_setup(factory_setup);
    let where_key = SysdColumn::AutomountWhat.generate_quark();
    factory.connect_bind(move |_, object| {
        let (inscription, unit) = factory_bind_pre!(object);

        let time = unit.get_custom_property::<String>(where_key);

        let what =
            if let Some(unit_name) = time.and_then(|s| unit_name_from_path(s.as_str(), ".mount")) {
                fetch_property(unit.dbus_level(), &unit_name, UnitType::Mount, "What")
                    .inspect_err(|err| warn!("{err:?}"))
                    .ok()
            } else {
                None
            };

        inscription.set_text(what.as_deref());
    });
    factory
}

fn fetch_property(
    level: UnitDBusLevel,
    name: &str,
    utype: UnitType,
    prop: &str,
) -> Result<String, SystemdErrors> {
    let value = systemd::fetch_unit_property_blocking(level, name, utype, prop)?;
    let v = value.downcast_ref::<String>()?;
    Ok(v)
}

fn fac_automount_idle_timeout() -> gtk::SignalListItemFactory {
    let factory = gtk::SignalListItemFactory::new();

    factory.connect_setup(factory_setup);
    let timeout_idle_key = SysdColumn::AutomountIdleTimeOut.generate_quark();
    factory.connect_bind(move |_, object| {
        let (inscription, unit) = factory_bind_pre!(object);

        let time = unit.get_custom_property::<u64>(timeout_idle_key);

        let time_str = if let Some(time) = time
            && *time != 0
        {
            let time_str = time_handling::format_timespan(*time, MSEC_PER_SEC);
            Some(time_str)
        } else {
            None
        };
        inscription.set_text(time_str.as_deref());
    });

    factory
}

fn unit_name_from_path(path: &str, suffix: &str) -> Option<String> {
    let mut out = String::with_capacity(path.len());
    for t in path.split('/') {
        match t {
            ".." => return None,
            "." => continue,
            _ => {}
        }

        if !out.is_empty() {
            out.push('-')
        }
        out.push_str(t);
    }

    if out.is_empty() {
        out.push('-');
    } else if let Some(c) = out.chars().last()
        && c == '-'
    {
        out.pop();
    }

    out.push_str(suffix);

    Some(out)
}

#[cfg(test)]
mod tests {

    #[test]
    fn test_unit_name_from_path() {
        test_unit_name_from_path_one("/waldo", ".mount", Some("waldo.mount"));
        test_unit_name_from_path_one("/waldo/quuix", ".mount", Some("waldo-quuix.mount"));
        test_unit_name_from_path_one("/waldo/quuix/", ".mount", Some("waldo-quuix.mount"));
        test_unit_name_from_path_one("", ".mount", Some("-.mount"));
        test_unit_name_from_path_one("/", ".mount", Some("-.mount"));
        test_unit_name_from_path_one("///", ".mount", Some("-.mount"));
        test_unit_name_from_path_one("/foo/../bar", ".mount", None);
        test_unit_name_from_path_one("/foo/./bar", ".mount", Some("foo-bar.mount"));
    }

    fn test_unit_name_from_path_one(path: &str, suffix: &str, expected: Option<&str>) {
        let s = super::unit_name_from_path(path, suffix);

        assert_eq!(s.as_deref(), expected);
    }
}
