// SPDX-License-Identifier: Apache-2.0

use crate::nm::nm_dbus::{
    NmConnection, NmSettingBridge, NmSettingBridgeVlanRange, NmVlanProtocol,
};

use crate::{
    BridgePortTrunkTag, BridgePortVlanConfig, BridgePortVlanMode, Interface,
    LinuxBridgeInterface, LinuxBridgeOptions, LinuxBridgeStpOptions,
    MergedInterface, VlanProtocol,
};

pub(crate) fn gen_nm_br_setting(
    merged_iface: &MergedInterface,
    nm_conn: &mut NmConnection,
) {
    if let (
        Some(Interface::LinuxBridge(br_iface)),
        Interface::LinuxBridge(merged_br_iface),
    ) = (merged_iface.for_apply.as_ref(), &merged_iface.merged)
    {
        let mut nm_br_set =
            nm_conn.bridge.as_ref().cloned().unwrap_or_default();

        if let Some(br_conf) = br_iface.bridge.as_ref() {
            if let Some(br_opts) = br_conf.options.as_ref() {
                apply_br_options(&mut nm_br_set, br_opts)
            }

            if br_conf.port.is_some() {
                nm_br_set.vlan_filtering =
                    Some(merged_br_iface.vlan_filtering_is_enabled());
            }
        }
        nm_conn.bridge = Some(nm_br_set);
    }
}

fn apply_br_options(
    nm_br_set: &mut NmSettingBridge,
    br_opts: &LinuxBridgeOptions,
) {
    if let Some(v) = br_opts.group_addr.as_ref() {
        nm_br_set.group_address = Some(v.to_string());
    }
    if let Some(v) = br_opts.group_forward_mask.as_ref() {
        nm_br_set.group_forward_mask = Some((*v).into());
    }
    if let Some(v) = br_opts.group_fwd_mask.as_ref() {
        nm_br_set.group_forward_mask = Some((*v).into());
    }
    if let Some(v) = br_opts.hash_max.as_ref() {
        nm_br_set.multicast_hash_max = Some(*v);
    }
    if let Some(v) = br_opts.mac_ageing_time.as_ref() {
        nm_br_set.ageing_time = Some(*v);
    }
    if let Some(v) = br_opts.multicast_last_member_count.as_ref() {
        nm_br_set.multicast_last_member_count = Some(*v);
    }
    if let Some(v) = br_opts.multicast_last_member_interval.as_ref() {
        nm_br_set.multicast_last_member_interval = Some(*v);
    }
    if let Some(v) = br_opts.multicast_membership_interval.as_ref() {
        nm_br_set.multicast_membership_interval = Some(*v);
    }
    if let Some(v) = br_opts.multicast_querier.as_ref() {
        nm_br_set.multicast_querier = Some(*v);
    }
    if let Some(v) = br_opts.multicast_querier_interval.as_ref() {
        nm_br_set.multicast_querier_interval = Some(*v);
    }
    if let Some(v) = br_opts.multicast_query_interval.as_ref() {
        nm_br_set.multicast_query_interval = Some(*v);
    }
    if let Some(v) = br_opts.multicast_query_response_interval.as_ref() {
        nm_br_set.multicast_query_response_interval = Some(*v);
    }
    if let Some(v) = br_opts.multicast_query_use_ifaddr.as_ref() {
        nm_br_set.multicast_query_use_ifaddr = Some(*v);
    }
    if let Some(v) = br_opts.multicast_router.as_ref() {
        nm_br_set.multicast_router = Some(format!("{v}"));
    }
    if let Some(v) = br_opts.multicast_snooping.as_ref() {
        nm_br_set.multicast_snooping = Some(*v);
    }
    if let Some(v) = br_opts.multicast_startup_query_count.as_ref() {
        nm_br_set.multicast_startup_query_count = Some(*v);
    }
    if let Some(v) = br_opts.multicast_startup_query_interval.as_ref() {
        nm_br_set.multicast_startup_query_interval = Some(*v);
    }
    if let Some(v) = br_opts.vlan_protocol.as_ref() {
        nm_br_set.vlan_protocol = match v {
            VlanProtocol::Ieee8021Q => Some(NmVlanProtocol::Dot1Q),
            VlanProtocol::Ieee8021Ad => Some(NmVlanProtocol::Dot1Ad),
        }
    }
    if let Some(v) = br_opts.vlan_default_pvid.as_ref() {
        nm_br_set.vlan_default_pvid = Some((*v).into());
    }

    if let Some(stp_opts) = br_opts.stp.as_ref() {
        apply_stp_setting(nm_br_set, stp_opts);
    }
}

fn apply_stp_setting(
    nm_set: &mut NmSettingBridge,
    opts: &LinuxBridgeStpOptions,
) {
    if let Some(v) = opts.enabled {
        nm_set.stp = Some(v);
    }
    if let Some(v) = opts.forward_delay {
        nm_set.forward_delay = Some(v.into());
    }
    if let Some(v) = opts.hello_time {
        nm_set.hello_time = Some(v.into())
    }
    if let Some(v) = opts.max_age {
        nm_set.max_age = Some(v.into())
    };
    if let Some(v) = opts.priority {
        nm_set.priority = Some(v.into())
    };
}

pub(crate) fn gen_nm_br_port_setting(
    br_iface: &LinuxBridgeInterface,
    nm_conn: &mut NmConnection,
) {
    let mut nm_set = nm_conn.bridge_port.as_ref().cloned().unwrap_or_default();
    let br_port_conf = if let Some(i) = nm_conn
        .iface_name()
        .and_then(|iface_name| br_iface.get_port_conf(iface_name))
    {
        i
    } else {
        return;
    };

    if let Some(v) = br_port_conf.stp_hairpin_mode {
        nm_set.hairpin_mode = Some(v);
    }

    if let Some(v) = br_port_conf.stp_path_cost {
        nm_set.path_cost = Some(v);
    }

    if let Some(v) = br_port_conf.stp_priority {
        nm_set.priority = Some(v.into());
    }
    if let Some(v) = br_port_conf.vlan.as_ref() {
        nm_set.vlans = Some(nmstate_port_vlans_to_nm_vlan_range(v));
    }

    nm_conn.bridge_port = Some(nm_set);
}

fn nmstate_port_vlans_to_nm_vlan_range(
    port_vlan_conf: &BridgePortVlanConfig,
) -> Vec<NmSettingBridgeVlanRange> {
    let mut ret = Vec::new();
    match port_vlan_conf.mode {
        Some(BridgePortVlanMode::Trunk) => {
            if let Some(trunk_tags) = &port_vlan_conf.trunk_tags {
                for trunk_tag in trunk_tags.as_slice() {
                    ret.push(trunk_tag_to_nm_vlan_range(trunk_tag));
                }
            }
            if let Some(t) = port_vlan_conf.tag {
                ret.push(access_tag_to_nm_vlan_range(t))
            }
        }
        Some(BridgePortVlanMode::Access) => {
            if let Some(t) = port_vlan_conf.tag {
                ret.push(access_tag_to_nm_vlan_range(t))
            };
        }
        _ => (),
    }

    ret
}

fn trunk_tag_to_nm_vlan_range(
    trunk_tag: &BridgePortTrunkTag,
) -> NmSettingBridgeVlanRange {
    let mut ret = NmSettingBridgeVlanRange::default();
    let (vid_min, vid_max) = trunk_tag.get_vlan_tag_range();
    ret.vid_start = vid_min;
    ret.vid_end = vid_max;
    ret.pvid = false;
    ret.untagged = false;
    ret
}

fn access_tag_to_nm_vlan_range(tag: u16) -> NmSettingBridgeVlanRange {
    let mut ret = NmSettingBridgeVlanRange::default();
    ret.vid_start = tag;
    ret.vid_end = tag;
    ret.pvid = true;
    ret.untagged = true;
    ret
}
