use std::collections::{BTreeMap, HashMap};
use std::fmt::{Debug, Write};

use crate::consts::{TIME_NEXT_ELAPSE_USEC_MONOTONIC, TIME_NEXT_ELAPSE_USEC_REALTIME, U64MAX};
use crate::systemd::{
    self,
    data::{UnitInfo, UnitProcess},
    enums::{LoadState, Preset},
};
use crate::utils::writer::{
    HyperLinkType, SPECIAL_GLYPH_TREE_BRANCH, SPECIAL_GLYPH_TREE_RIGHT, SPECIAL_GLYPH_TREE_SPACE,
    SPECIAL_GLYPH_TREE_VERTICAL, UnitInfoWriter,
};
use crate::widget::{
    preferences::data::PREFERENCES, unit_info::construct_info::systemd::timestamp_is_set,
};
use base::enums::UnitDBusLevel;
use systemd::time_handling::calc_next_elapse;
use systemd::{
    enums::ActiveState,
    swrite,
    time_handling::{self, TimestampStyle},
};
use tracing::{debug, warn};
use zvariant::{DynamicType, OwnedValue, Str, Value};

pub(crate) fn fill_all_info(
    unit: &UnitInfo,
    unit_writer: &mut UnitInfoWriter,
) -> HashMap<String, OwnedValue> {
    //let mut unit_info_tokens = Vec::new();
    fill_name_description(unit_writer, unit);

    let map = systemd::fetch_system_unit_info_native_map(unit).unwrap_or_else(|e| {
        warn!("Fails to retrieve Unit info for {:?} {e:?}", unit.primary());
        HashMap::new()
    });

    let timestamp_style = PREFERENCES.timestamp_style();

    let level = unit.dbus_level();
    fill_description(unit_writer, &map, unit);
    fill_follows(unit_writer, &map);
    fill_load_state(unit_writer, &map, unit);
    fill_transient(unit_writer, &map);
    fill_dropin(unit_writer, &map);
    fill_active_state(unit_writer, &map, unit, timestamp_style);
    fill_invocation(unit_writer, &map);
    fill_triggered_by(unit_writer, &map, level);
    fill_device(unit_writer, &map);
    fill_trigger(unit_writer, &map, unit, timestamp_style);
    fill_triggers(unit_writer, &map, level);
    fill_where(unit_writer, &map);
    fill_what(unit_writer, &map);
    fill_docs(unit_writer, &map);
    fill_main_pid(unit_writer, &map, unit);
    fill_status(unit_writer, &map);
    fill_error(unit_writer, &map);
    fill_ip(unit_writer, &map);
    fill_io(unit_writer, &map);
    fill_tasks(unit_writer, &map);
    fill_fd_store(unit_writer, &map);
    fill_memory(unit_writer, &map);
    fill_listen(unit_writer, &map);
    fill_accept(unit_writer, &map);
    fill_cpu(unit_writer, &map);
    fill_control_group(unit_writer, &map, unit);

    map
}

fn fill_name_description(unit_writer: &mut UnitInfoWriter, unit: &UnitInfo) {
    fill_row(unit_writer, "Name:", &unit.primary())
}

const KEY_WIDTH: usize = 15;

macro_rules! get_value {
    ($map:expr, $key:expr) => {
        get_value!($map, $key, ())
    };

    ($map:expr, $key:expr, $dft:expr) => {{
        let Some(value) = $map.get($key) else {
            debug!("Key doesn't exists: {:?}", $key);
            return $dft;
        };
        value
    }};
}

macro_rules! clean_message {
    ($result:expr) => {
        clean_message!($result, "", ())
    };

    ($result:expr,  $log_prefix:expr) => {
        clean_message!($result, $log_prefix, ())
    };

    ($result:expr, $log_prefix:expr, $default_return:expr) => {{
        match $result {
            Ok(ok) => ok,
            Err(e) => {
                tracing::warn!("{} {:?}", $log_prefix, e);
                return $default_return;
            }
        }
    }};
}

fn write_key(unit_writer: &mut UnitInfoWriter, key_label: &str) {
    let s = format!("{key_label:>KEY_WIDTH$} ");
    unit_writer.insert(&s);
}

fn fill_row(unit_writer: &mut UnitInfoWriter, key_label: &str, value: &str) {
    let s = format!("{key_label:>KEY_WIDTH$} {value}\n");
    unit_writer.insert(&s);
}

fn fill_dropin(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let value = get_value!(map, "DropInPaths");

    let drop_in_paths = get_array_str(value);

    if drop_in_paths.is_empty() {
        return;
    }

    let mut map = BTreeMap::new();

    for file_name in drop_in_paths {
        let (first, last) = match file_name.rsplit_once('/') {
            Some((first, last)) => (first, last),
            None => (file_name, ""),
        };

        let suffixes = map.entry(first).or_insert(Vec::new());
        suffixes.push((last, file_name));
    }

    let mut is_first1 = true;
    for (prefix, suffixes) in map {
        let key_label = if is_first1 {
            is_first1 = false;
            "Drop in:"
        } else {
            ""
        };

        let s = format!("{key_label:>KEY_WIDTH$} {prefix}\n");
        unit_writer.insert(&s);

        let mut is_first = true;
        for (d, link) in suffixes.iter() {
            if is_first {
                unit_writer.insert(&format!("{:KEY_WIDTH$} {}", "", SPECIAL_GLYPH_TREE_RIGHT));
                is_first = false;
            } else {
                unit_writer.insert(", ");
            }

            unit_writer.hyperlink(d, link, HyperLinkType::File);
        }
        unit_writer.newline();
    }
}

fn fill_active_state(
    unit_writer: &mut UnitInfoWriter,
    map: &HashMap<String, OwnedValue>,
    unit: &UnitInfo,
    timestamp_style: TimestampStyle,
) {
    let value = get_value!(map, "ActiveState");

    let state: &str = value_to(value);

    let state: ActiveState = state.into();
    unit.set_active_state(state);
    write_key(unit_writer, "Active:");

    let mut state_text = String::from(state.as_str());
    if let Some(substate) = get_substate(map, unit) {
        state_text.push_str(" (");
        state_text.push_str(substate);
        state_text.push(')');
    }

    if state.is_active() {
        unit_writer.insert_active(&state_text);
    } else {
        unit_writer.insert(&state_text);
    };

    if let Some(since) = add_since(map, state.as_str(), timestamp_style) {
        unit_writer.insert(&format!(" since {}; {}\n", since.0, since.1));
        fill_duration(unit_writer, map, unit);
    } else {
        unit_writer.newline();
    }
}

fn fill_duration(
    unit_writer: &mut UnitInfoWriter,
    map: &HashMap<String, OwnedValue>,
    unit: &UnitInfo,
) {
    if !systemd::enums::UnitType::Target.eq(&unit.unit_type()) {
        return;
    }

    let active_enter_timestamp = value_to_def(map.get("ActiveEnterTimestamp"), U64MAX);
    let active_exit_timestamp = value_to_def(map.get("ActiveExitTimestamp"), U64MAX);

    if timestamp_is_set!(active_enter_timestamp)
        && timestamp_is_set!(active_exit_timestamp)
        && active_exit_timestamp >= active_enter_timestamp
    {
        let duration = active_exit_timestamp - active_enter_timestamp;
        let timespan = time_handling::format_timespan(duration, time_handling::MSEC_PER_SEC);
        fill_row(unit_writer, "Duration:", &timespan);
    }
}

fn get_substate<'a>(map: &'a HashMap<String, OwnedValue>, unit: &UnitInfo) -> Option<&'a str> {
    let value = get_value!(map, "SubState", None);
    let value_str = value_to(value);
    unit.set_sub_state(value_str);
    Some(value_str)
}

fn add_since(
    map: &HashMap<String, OwnedValue>,
    state: &str,
    timestamp_style: TimestampStyle,
) -> Option<(String, String)> {
    let key = match state {
        "active" | "reloading" | "refreshing" => "ActiveEnterTimestamp",
        "inactive" | "failed" => "InactiveEnterTimestamp",
        "activating" => "InactiveExitTimestamp",
        _ => "ActiveExitTimestamp",
    };

    let duration = value_some(map.get(key));

    if duration != 0 {
        let since = time_handling::get_since_and_passed_time(duration, timestamp_style);
        Some(since)
    } else {
        None
    }
}

fn fill_description(
    unit_writer: &mut UnitInfoWriter,
    map: &HashMap<String, OwnedValue>,
    unit: &UnitInfo,
) {
    let value = get_value!(map, "Description");
    let description = value_to(value);
    fill_row(unit_writer, "Description:", description);

    unit.set_description(description);
}

fn fill_load_state(
    unit_writer: &mut UnitInfoWriter,
    map: &HashMap<String, OwnedValue>,
    unit: &UnitInfo,
) {
    let load_state: LoadState = map.get("LoadState").into();

    unit.set_load_state(load_state);

    write_key(unit_writer, "Loaded:");
    unit_writer.insert(load_state.as_str());

    let three_param = [
        map.get("FragmentPath"),
        map.get("UnitFileState"),
        map.get("UnitFilePreset"),
    ];

    let mut all_none = true;
    for p in three_param {
        let Some(value) = p else {
            continue;
        };

        if let Value::Str(inner_str) = value as &Value
            && !inner_str.is_empty()
        {
            all_none = false;
            break;
        }
    }

    if !all_none {
        unit_writer.insert(" (");

        let [file_path, unit_file_state_op, unit_file_preset_op] = three_param;

        let mut pad_left = false;

        if let Some(file_path) = file_path {
            let file_path_str = value_to(file_path);
            unit.set_file_path(Some(file_path_str)); //for lazy load
            unit_writer.hyperlink(file_path_str, file_path_str, HyperLinkType::File);
            pad_left = true;
        }

        if let Some(unit_file_state) = unit_file_state_op {
            if pad_left {
                unit_writer.insert("; ");
            }
            let unit_file_state = value_to(unit_file_state);
            write_enabled_state(unit_writer, unit_file_state);

            pad_left = true;
        }

        if let Some(unit_file_preset) = unit_file_preset_op {
            if pad_left {
                unit_writer.insert("; ");
            }
            unit_writer.insert("preset: ");
            let unit_file_preset: &str = value_to(unit_file_preset);
            let preset: Preset = unit_file_preset.into();
            unit.set_preset(preset);
            write_enabled_state(unit_writer, unit_file_preset);
        }

        unit_writer.insert(")");
    }

    unit_writer.newline();
}

fn write_enabled_state(unit_writer: &mut UnitInfoWriter, state: &str) {
    match state {
        "enabled" => unit_writer.insert_active(state),
        "disabled" => unit_writer.insert_disable(state),
        _ => unit_writer.insert(state),
    };
}

fn fill_follows(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let value = get_value!(map, "Following");
    let value: &str = value_to(value);

    if value.is_empty() {
        return;
    }

    let s = format!("unit currently follows state of {value}");
    fill_row(unit_writer, "Follows:", &s);
}

fn fill_transient(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let value = get_value!(map, "Transient");

    let transient = clean_message!(bool::try_from(value), "Wrong zvalue conversion");

    if transient {
        let value = if transient { "yes" } else { "no" };
        fill_row(unit_writer, "Transient:", value);
    }
}

fn fill_status(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let value = get_value!(map, "StatusText");
    let value: &str = value_to(value);

    if !value.is_empty() {
        let s = format!("{:>KEY_WIDTH$} ", "Status:");
        unit_writer.insert(&s);
        unit_writer.insert_status(value);
        unit_writer.newline();
    }
}

fn fill_error(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let status_errno: i32 = value_some(map.get("StatusErrno"));
    let status_bus_error: &str = value_some(map.get("StatusBusError"));
    let status_varlink_error: &str = value_some(map.get("StatusVarlinkError"));

    if status_errno == 0 && status_bus_error.is_empty() && status_varlink_error.is_empty() {
        return;
    }

    write_key(unit_writer, "Error:");

    let mut prefix = "";

    if status_errno > 0 {
        let mut text = format!("{prefix} {status_errno}");

        if let Some(strerror) = strerror(status_errno) {
            swrite!(text, " ({strerror})");
        }

        unit_writer.insert(&text);
        prefix = "; ";
    }

    if !status_bus_error.is_empty() {
        let text = format!("{prefix} D-Bus: {status_bus_error}");
        unit_writer.insert(&text);
        prefix = "; ";
    }

    if !status_varlink_error.is_empty() {
        let text = format!("{prefix} Varlink: {status_varlink_error}");
        unit_writer.insert(&text);
    }

    unit_writer.newline();
}

fn strerror(err_no: i32) -> Option<String> {
    const ERRNO_BUF_LEN: usize = 1024;
    //let mut str_error = String::with_capacity(1024);
    let mut str_error: Vec<u8> = vec![0; ERRNO_BUF_LEN];
    //let mut str_error = [0; ERRNO_BUF_LEN];
    unsafe {
        let str_error_raw_ptr = str_error.as_mut_ptr() as *mut libc::c_char;
        libc::strerror_r(err_no, str_error_raw_ptr, ERRNO_BUF_LEN);

        let nul_range_end = str_error
            .iter()
            .position(|&c| c == b'\0')
            .unwrap_or(ERRNO_BUF_LEN);

        str_error.truncate(nul_range_end);
        String::from_utf8(str_error).ok()
    }
}

fn fill_device(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    fill_what_string(unit_writer, map, "SysFSPath", "Device:")
}

fn fill_where(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    fill_what_string(unit_writer, map, "Where", "Where:")
}

fn fill_what(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    fill_what_string(unit_writer, map, "What", "What:")
}

fn fill_what_string(
    unit_writer: &mut UnitInfoWriter,
    map: &HashMap<String, OwnedValue>,
    key: &str,
    key_label: &str,
) {
    let value: &str = value_some(map.get(key));
    if !value.is_empty() {
        fill_row(unit_writer, key_label, value);
    }
}

fn fill_docs(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let value = get_value!(map, "Documentation");

    let Value::Array(array) = value as &Value else {
        return;
    };

    for (idx, value) in array.iter().enumerate() {
        let key = match idx {
            0 if array.len() == 1 => "Doc:",
            0 => "Docs:",
            _ => "",
        };

        write_key(unit_writer, key);
        let doc = value_to(value);
        insert_doc(unit_writer, doc);
        unit_writer.newline();
    }
}

fn insert_doc(unit_writer: &mut UnitInfoWriter, doc: &str) {
    if doc.starts_with("man:") {
        unit_writer.hyperlink(doc, doc, HyperLinkType::Man);
    } else if doc.starts_with("http") {
        unit_writer.hyperlink(doc, doc, HyperLinkType::Http);
    } else {
        unit_writer.insert(doc);
    }
}

fn get_array_str<'a>(value: &'a Value<'a>) -> Vec<&'a str> {
    match value as &Value {
        Value::Array(a) => {
            let mut vec = Vec::with_capacity(a.len());
            for mi in a.iter() {
                vec.push(value_to(mi));
            }
            vec
        }
        _ => {
            warn!("Wrong zvalue conversion: {:?}", value.signature());
            Vec::new()
        }
    }
}

fn fill_fd_store(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let n_fd_store: u32 = value_some(map.get("NFileDescriptorStore"));
    let fd_store_max: u32 = value_some(map.get("FileDescriptorStoreMax"));

    if n_fd_store == 0 && fd_store_max == 0 {
        return;
    }

    write_key(unit_writer, "FD Store:");

    unit_writer.insert(&n_fd_store.to_string());
    unit_writer.insert_grey(&format!(" (limit: {fd_store_max})\n"));
}

fn fill_memory(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let memory_current = value_to_def(map.get("MemoryCurrent"), U64MAX);

    if memory_current == U64MAX {
        return;
    }

    write_key(unit_writer, "Memory:");

    let value_str = human_bytes(memory_current);

    unit_writer.insert(&value_str);

    type MemoryManaging = (&'static str, u64, &'static str, fn(u64) -> bool);

    let mut memories: [MemoryManaging; 17] = [
        ("MemoryMin", U64MAX, "min: ", is_zero),
        ("MemoryLow", U64MAX, "low: ", is_zero),
        ("StartupMemoryLow", U64MAX, "low (startup): ", is_zero),
        ("MemoryHigh", U64MAX, "high: ", is_limit_max), //3
        (
            "StartupMemoryHigh",
            U64MAX,
            "high (startup): ",
            is_limit_max,
        ),
        ("MemoryMax", U64MAX, "max: ", is_limit_max), //5
        ("StartupMemoryMax", U64MAX, "max (startup): ", is_limit_max),
        ("MemorySwapMax", U64MAX, "swap max: ", is_limit_max),
        (
            "StartupMemorySwapMax",
            U64MAX,
            "swap max (startup): ",
            is_limit_max,
        ),
        ("MemoryZSwapMax", U64MAX, "zswap max: ", is_limit_max),
        (
            "StartupMemoryZSwapMax",
            U64MAX,
            "zswap max (startup): ",
            is_limit_max,
        ),
        ("MemoryLimit", U64MAX, "limit: ", is_limit_max),
        ("MemoryAvailable", U64MAX, "available: ", is_limit_max),
        ("MemoryPeak", U64MAX, "peak: ", is_limit_max),
        ("MemorySwapCurrent", U64MAX, "swap: ", is_limit_max_or_zero),
        (
            "MemorySwapPeak",
            U64MAX,
            "swap peak: ",
            is_limit_max_or_zero,
        ),
        (
            "MemoryZSwapCurrent",
            U64MAX,
            "zswap: ",
            is_limit_max_or_zero,
        ),
    ];

    for (key, value, _, _) in memories.iter_mut() {
        if let Some(bus_value) = map.get(key as &str)
            && let Value::U64(converted) = bus_value as &Value
        {
            *value = *converted;
        }
    }

    let mut is_first = true;
    let mut out = String::with_capacity(100);
    for (key, value, label, not_valid) in memories {
        if not_valid(value)
            || (
                key == "MemoryAvailable"
            && is_limit_max(memories[3].1)//memory_high
            && is_limit_max(memories[5].1)
                //memory_max
            )
        {
            continue;
        }

        if is_first {
            out.push_str(" (");
            is_first = false;
        } else {
            out.push_str(", ");
        }

        out.push_str(label);
        let mem_human = human_bytes(value);
        out.push_str(&mem_human);
    }

    if !is_first {
        out.push(')');
        unit_writer.insert(&out);
    }

    unit_writer.newline();
}

fn is_limit_max_or_zero(value: u64) -> bool {
    value == 0 || value == U64MAX
}

fn is_zero(value: u64) -> bool {
    value == 0
}

fn is_limit_max(value: u64) -> bool {
    value == U64MAX
}

fn fill_main_pid(
    unit_writer: &mut UnitInfoWriter,
    map: &HashMap<String, OwnedValue>,
    unit: &UnitInfo,
) {
    let main_pid = get_main_pid(map);

    if 0 == main_pid {
        // nothing
        return;
    }

    let exec_val = if let Some(exec) = get_exec(map) {
        exec
    } else {
        unit.display_name()
    };

    let v = &format!("{main_pid} ({exec_val})");
    fill_row(unit_writer, "Main PID:", v)
}

fn get_main_pid(map: &HashMap<String, OwnedValue>) -> u32 {
    let value = get_value!(map, "MainPID", 0);

    if let Value::U32(main_pid) = value as &Value {
        return *main_pid;
    }
    0
}

#[derive(Clone, Value, Debug, OwnedValue)]
struct ExecStart<'a> {
    path: Str<'a>,
    argv: Vec<Str<'a>>,
    ignore_errors: bool,

    //TODO check the param naming
    start_time: u64,
    stop_time: u64,
    field6: u64,
    field7: u64,
    field8: u32,
    code: i32,
    status: i32,
}

// Value: Array(Dynamic { child: Structure(Dynamic { fields: [Str, Array(Dynamic { child: Str }), Bool, U64, U64, U64, U64, U32, I32, I32] }) })
fn get_exec_full(map: &HashMap<String, OwnedValue>) -> Option<ExecStart<'_>> {
    let value = get_value!(map, "ExecStart", None);

    debug!(
        "ExecStart Signature {:?} Value: {:?}",
        value.value_signature(),
        value
    );

    let Value::Array(array) = value as &Value else {
        return None;
    };

    for idx in 0..array.len() {
        let Ok(Some(val)) = array.get::<Value>(idx) else {
            warn!("Can't get value from array");
            continue;
        };

        let exec_start = clean_message!(ExecStart::try_from(val), "ExecStart", None);

        return Some(exec_start);
    }

    None
}

fn get_exec(map: &HashMap<String, OwnedValue>) -> Option<String> {
    if let Some(exec_full) = get_exec_full(map)
        && let Some((_pre, last)) = exec_full.path.rsplit_once('/')
    {
        return Some(last.to_string());
    }
    None
}

fn fill_cpu(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let cpu_usage_nsec = value_to_def(map.get("CPUUsageNSec"), U64MAX);

    if cpu_usage_nsec == U64MAX {
        return;
    }

    let value_str = time_handling::format_timespan(
        cpu_usage_nsec / time_handling::NSEC_PER_USEC,
        time_handling::USEC_PER_MSEC,
    );
    fill_row(unit_writer, "CPU:", &value_str)
}

fn fill_ip(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let ip_ingress_bytes = value_to_def(map.get("IPIngressBytes"), U64MAX);
    let ip_egress_bytes = value_to_def(map.get("IPEgressBytes"), U64MAX);

    if ip_ingress_bytes == U64MAX || ip_egress_bytes == U64MAX {
        return;
    }

    fill_row(
        unit_writer,
        "IP:",
        &format!(
            "{} in, {} out",
            human_bytes(ip_ingress_bytes),
            human_bytes(ip_egress_bytes)
        ),
    );
}

fn fill_io(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let io_read_bytes = value_to_def(map.get("IOReadBytes"), U64MAX);
    let io_write_bytes = value_to_def(map.get("IOWriteBytes"), U64MAX);

    if io_read_bytes == U64MAX || io_write_bytes == U64MAX {
        return;
    }

    fill_row(
        unit_writer,
        "IP:",
        &format!(
            "{} read, {} written",
            human_bytes(io_read_bytes),
            human_bytes(io_write_bytes)
        ),
    );
}

fn fill_tasks(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let tasks_current = value_to_def(map.get("TasksCurrent"), U64MAX);

    if tasks_current == U64MAX {
        return;
    }

    write_key(unit_writer, "Tasks:");

    let tasks_info = tasks_current.to_string();
    unit_writer.insert(&tasks_info);

    let tasks_max = value_to_def(map.get("TasksMax"), U64MAX);
    if tasks_max != U64MAX {
        unit_writer.insert_grey(&format!(" (limit: {tasks_max})"));
    }

    unit_writer.newline();
    //fill_row(unit_writer, "Tasks:", &tasks_info)
}

fn fill_invocation(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let value = get_value!(map, "InvocationID");

    let Value::Array(array) = value as &Value else {
        return;
    };

    if array.is_empty() {
        return;
    };

    let mut invocation = String::with_capacity(32);
    for idx in 0..array.len() {
        let Ok(Some(val)) = array.get::<Value>(idx) else {
            warn!("Can't get value from array at index {idx}");
            continue;
        };

        let Value::U8(converted) = val else {
            warn!("Can't convert value to u8");
            continue;
        };

        let hexa = format!("{converted:x}");

        invocation.push_str(&hexa);
    }

    fill_row(unit_writer, "Invocation:", &invocation)
}

#[derive(Clone, Value, OwnedValue)]
struct TimersCalendar<'a> {
    timer_base: Str<'a>,
    calendar_specification: Str<'a>,
    elapsation_point: u64,
}

fn fill_trigger(
    unit_writer: &mut UnitInfoWriter,
    map: &HashMap<String, OwnedValue>,
    unit: &UnitInfo,
    timestamp_style: TimestampStyle,
) {
    if !systemd::enums::UnitType::Timer.eq(&unit.unit_type()) {
        return;
    }

    let next_elapse_realtime = value_some(map.get(TIME_NEXT_ELAPSE_USEC_REALTIME));
    let next_elapse_monotonic = value_some(map.get(TIME_NEXT_ELAPSE_USEC_MONOTONIC));

    let next_elapse = calc_next_elapse(next_elapse_realtime, next_elapse_monotonic);

    let trigger_msg = if timestamp_is_set!(next_elapse) {
        let (first, second) =
            time_handling::get_since_and_passed_time(next_elapse, timestamp_style);

        format!("{first}; {second}")
    } else {
        "n/a".to_owned()
    };

    fill_row(unit_writer, "Trigger:", &trigger_msg);
}

#[derive(Clone, Value, OwnedValue)]
struct TimersMonotonic<'a> {
    timer_base: Str<'a>,
    usec_offset: u64,
    elapsation_point: u64,
}

fn fill_triggers(
    unit_writer: &mut UnitInfoWriter,
    map: &HashMap<String, OwnedValue>,
    level: UnitDBusLevel,
) {
    let value = get_value!(map, "Triggers");

    let triggers = get_array_str(value);

    if triggers.is_empty() {
        return;
    }

    let mut is_first = true;
    for trigger_unit in triggers {
        let key_label = if is_first {
            is_first = false;
            "Triggers:"
        } else {
            ""
        };

        write_key(unit_writer, key_label);

        match systemd::get_unit_active_state(level, trigger_unit) {
            Ok(state) => {
                unit_writer.insert_state(state);
            }
            Err(e) => {
                warn!("Can't find state of {trigger_unit}, {e:?}");
                unit_writer.insert(" ");
            }
        };

        unit_writer.insert(" ");
        unit_writer.hyperlink(trigger_unit, trigger_unit, HyperLinkType::Unit(level));
        unit_writer.newline();
    }
}

//TODO add units states
fn fill_triggered_by(
    unit_writer: &mut UnitInfoWriter,
    map: &HashMap<String, OwnedValue>,
    level: UnitDBusLevel,
) {
    let value = get_value!(map, "TriggeredBy");

    let triggers = get_array_str(value);

    let mut is_first = true;
    for trigger_unit in triggers {
        let key_label = if is_first {
            is_first = false;
            "TriggeredBy:"
        } else {
            ""
        };

        write_key(unit_writer, key_label);

        match systemd::get_unit_active_state(level, trigger_unit) {
            Ok(state) => {
                unit_writer.insert_state(state);
            }
            Err(e) => {
                warn!("Can't find state of {trigger_unit}, {e:?}");
                unit_writer.insert(" ");
            }
        };

        unit_writer.insert(" ");
        unit_writer.hyperlink(trigger_unit, trigger_unit, HyperLinkType::Unit(level));
        unit_writer.newline();
    }
}

#[derive(Value, OwnedValue)]
struct ListenStruct<'a> {
    listen_type: Str<'a>,
    path: Str<'a>,
}

fn fill_listen(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let value = get_value!(map, "Listen");

    let Value::Array(array) = value as &Value else {
        return;
    };

    for i in 0..array.len() {
        let Ok(Some(val_listen_stc)) = array.get::<Value>(i) else {
            continue;
        };

        let listen_struct = clean_message!(ListenStruct::try_from(val_listen_stc), "Listen info");

        let key = if i == 0 { "Listen:" } else { "" };
        write_key(unit_writer, key);

        let listen = format!("{} ({})\n", listen_struct.path, listen_struct.listen_type);
        unit_writer.insert(&listen);
    }
}

fn fill_accept(unit_writer: &mut UnitInfoWriter, map: &HashMap<String, OwnedValue>) {
    let accept = value_some(map.get("Accept"));

    if accept {
        let n_accepted: u32 = value_some(map.get("NAccepted"));
        let n_connections: u32 = value_some(map.get("NConnections"));

        let line = format!("{n_accepted}; Connected: {n_connections};");
        fill_row(unit_writer, "Accepted:", &line);
        let n_refused: u32 = value_some(map.get("NRefused"));
        if n_refused != 0 {
            fill_row(unit_writer, "Refused:", &n_refused.to_string());
        }
    }
}

fn fill_control_group(
    unit_writer: &mut UnitInfoWriter,
    map: &HashMap<String, OwnedValue>,
    unit: &UnitInfo,
) {
    let value = get_value!(map, "ControlGroup");

    let c_group: &str = value_to(value);

    if c_group.is_empty() {
        return;
    }

    fill_row(unit_writer, "CGroup:", c_group);

    //TODO put in separate thread maybe?
    let mut unit_processes =
        clean_message!(systemd::retreive_unit_processes(unit), "Get processes");

    let main_unit_name = unit.primary();

    // get the main unit first
    if let Some(unit_process_set) = unit_processes.remove(&main_unit_name) {
        for (sub_idx, unit_process) in unit_process_set.iter().enumerate() {
            let is_last = sub_idx == unit_process_set.len() - 1;
            print_process(unit_writer, "", unit_process, is_last);
        }
    }

    for (idx, (_, unit_process_set)) in unit_processes.iter().enumerate() {
        let is_last = idx == unit_processes.len() - 1;
        //let is_first = idx == 0;
        let mut padding = "";

        for (sub_idx, unit_process) in unit_process_set.iter().enumerate() {
            let is_first_sub = sub_idx == 0;
            let is_last_sub = sub_idx == unit_process_set.len() - 1;

            if is_first_sub {
                let glyph = if is_last {
                    SPECIAL_GLYPH_TREE_RIGHT
                } else {
                    SPECIAL_GLYPH_TREE_BRANCH
                };

                unit_writer.insert(&format!("{:KEY_WIDTH$} {}", " ", glyph));
                unit_writer.insert(unit_process.unit_name());
                unit_writer.newline();

                padding = if !is_last {
                    SPECIAL_GLYPH_TREE_VERTICAL
                } else {
                    SPECIAL_GLYPH_TREE_SPACE
                };
            }

            print_process(unit_writer, padding, unit_process, is_last_sub);
        }
    }
}

fn print_process(
    unit_writer: &mut UnitInfoWriter,
    padding: &str,
    unit_process: &UnitProcess,
    is_last_sub: bool,
) {
    let glyph = if !is_last_sub {
        SPECIAL_GLYPH_TREE_BRANCH
    } else {
        SPECIAL_GLYPH_TREE_RIGHT
    };

    unit_writer.insert(&format!("{:KEY_WIDTH$} {}{}", " ", padding, glyph));

    let process_info = format!("{} {}", unit_process.pid, unit_process.name);

    unit_writer.insert_grey(&process_info);
    unit_writer.newline();
}

const SUFFIX: [&str; 9] = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"];
const UNIT: u64 = 1024;

fn value_to<'a, 'b, T>(value: &'a Value<'b>) -> T
where
    T: std::default::Default + std::convert::TryFrom<&'a zvariant::Value<'b>>,
    <T as TryFrom<&'a Value<'b>>>::Error: std::fmt::Debug,
{
    value
        .try_into()
        .inspect_err(|e| warn!("Convetion Error: {e:?}"))
        .unwrap_or_default()
}

fn value_some<'a, T>(value: Option<&'a OwnedValue>) -> T
where
    T: std::default::Default + std::convert::TryFrom<&'a zvariant::OwnedValue>,
    <T as TryFrom<&'a OwnedValue>>::Error: std::fmt::Debug,
{
    value
        .and_then(|v| {
            v.try_into()
                .inspect_err(|e| warn!("Convetion Error: {e:?}"))
                .ok()
        })
        .unwrap_or_default()
}

fn value_to_def<'a, T>(value: Option<&'a OwnedValue>, def: T) -> T
where
    T: std::default::Default + std::convert::TryFrom<&'a zvariant::OwnedValue>,
    <T as TryFrom<&'a OwnedValue>>::Error: std::fmt::Debug,
{
    value
        .and_then(|v| {
            v.try_into()
                .inspect_err(|e| warn!("Convertion Error: {e:?}"))
                .ok()
        })
        .unwrap_or(def)
}

/// Converts bytes to human-readable values in base 10
fn human_bytes(bytes: u64) -> String {
    let mut base: usize = 0;

    let mut byte_new = bytes;

    loop {
        if UNIT > byte_new {
            break;
        }
        base += 1;
        byte_new >>= 10;
    }

    let pbase = UNIT.pow(base as u32);
    let value = bytes as f64 / pbase as f64;

    let mut human_str = if base == 0 {
        bytes.to_string()
    } else {
        format!("{value:.1}")
    };

    if let Some(suffix) = SUFFIX.get(base) {
        human_str.push_str(suffix);
    }

    human_str
}

#[cfg(test)]
mod tests {

    use chrono::Local;

    use super::*;
    #[test]
    fn test1() {
        println!("{}", human_bytes(0));
        println!("{}", human_bytes(3));
        println!("{}", human_bytes(18446744073709551615));
        println!("{}", human_bytes(1024));
        println!("{}", human_bytes(1024));
        println!("{}", human_bytes(2048));
        println!("{}", human_bytes(2000));
        println!("{}", human_bytes(1950));

        println!("{}", human_bytes(2_048_000));
    }

    #[test]
    fn test_timer_mono() {
        let local_result = chrono::TimeZone::timestamp_millis_opt(&Local, 86400000000);
        let fmt = "%b %d %T %Y";
        let date = match local_result {
            chrono::offset::LocalResult::Single(l) => l.format(fmt).to_string(),
            chrono::offset::LocalResult::Ambiguous(a, _b) => a.format(fmt).to_string(),
            chrono::offset::LocalResult::None => "NONE".to_owned(),
        };

        println!("date {date}");

        let local_result = chrono::TimeZone::timestamp_millis_opt(&Local, 173787328907);
        let fmt = "%b %d %T %Y";
        let date = match local_result {
            chrono::offset::LocalResult::Single(l) => l.format(fmt).to_string(),
            chrono::offset::LocalResult::Ambiguous(a, _b) => a.format(fmt).to_string(),
            chrono::offset::LocalResult::None => "NONE".to_owned(),
        };

        println!("date {date}");
    }

    #[test]
    fn test_invocation() {
        let _a = [
            23, 184, 156, 61, 114, 189, 74, 235, 186, 102, 85, 32, 183, 33, 38, 165,
        ];
        //Invocation: 17b89c3d72bd4aebba665520b72126a5
    }

    #[test]
    fn test_strerror() {
        for i in 0..35 {
            let out = strerror(i);
            println!("Error {i} {out:?}");
        }
    }
}
