// SPDX-License-Identifier: Apache-2.0

use crate::{
    BondAdSelect, BondAllPortsActive, BondArpAllTargets, BondArpValidate,
    BondFailOverMac, BondInterface, BondLacpRate, BondMode,
    BondPrimaryReselect, BondXmitHashPolicy, ErrorKind, Interface, Interfaces,
    MergedInterface,
};

#[test]
fn test_bond_validate_mac_restricted_with_mac_undefined() {
    let iface: Interface = serde_yaml::from_str(
        r"---
name: bond99
type: bond
state: up
link-aggregation:
  mode: active-backup
  options:
    fail_over_mac: active
",
    )
    .unwrap();
    let mut merged_iface = MergedInterface::new(Some(iface), None).unwrap();
    merged_iface.post_inter_ifaces_process_bond().unwrap();
}

#[test]
fn test_bond_validate_mac_restricted_with_mac_defined() {
    let iface: Interface = serde_yaml::from_str(
        r"---
name: bond99
type: bond
state: up
mac-address: 00:01:02:03:04:05
link-aggregation:
  mode: active-backup
  options:
    fail_over_mac: active
",
    )
    .unwrap();
    let mut merged_iface = MergedInterface::new(Some(iface), None).unwrap();
    let result = merged_iface.post_inter_ifaces_process_bond();
    assert!(result.is_err());
    if let Err(e) = result {
        assert_eq!(e.kind(), ErrorKind::InvalidArgument);
    }
}

#[test]
fn test_bond_validate_mac_restricted_with_mac_defined_for_exist_bond() {
    let des_iface: Interface = serde_yaml::from_str(
        r"---
name: bond99
type: bond
state: up
mac-address: 00:01:02:03:04:05
",
    )
    .unwrap();
    let cur_iface: Interface = serde_yaml::from_str(
        r"---
name: bond99
type: bond
state: up
link-aggregation:
  mode: active-backup
  options:
    fail_over_mac: active
",
    )
    .unwrap();
    let mut merged_iface =
        MergedInterface::new(Some(des_iface), Some(cur_iface)).unwrap();
    let result = merged_iface.post_inter_ifaces_process_bond();
    assert!(result.is_err());
    if let Err(e) = result {
        assert_eq!(e.kind(), ErrorKind::InvalidArgument);
    }
}

#[test]
fn test_bond_validate_mac_restricted_with_mac_defined_changing_mode() {
    let des_iface: Interface = serde_yaml::from_str(
        r"---
name: bond99
type: bond
state: up
mac-address: 00:01:02:03:04:05
link-aggregation:
  mode: 802.3ad
",
    )
    .unwrap();
    let cur_iface: Interface = serde_yaml::from_str(
        r"---
name: bond99
type: bond
state: up
link-aggregation:
  mode: active-backup
  options:
    fail_over_mac: active
",
    )
    .unwrap();
    let mut merged_iface =
        MergedInterface::new(Some(des_iface), Some(cur_iface)).unwrap();
    merged_iface.post_inter_ifaces_process_bond().unwrap();
}

#[test]
fn test_bond_validate_mac_restricted_with_mac_defined_changing_opt() {
    let des_iface: Interface = serde_yaml::from_str(
        r"---
name: bond99
type: bond
state: up
mac-address: 00:01:02:03:04:05
link-aggregation:
  mode: active-backup
  options:
    fail_over_mac: follow
",
    )
    .unwrap();
    let cur_iface: Interface = serde_yaml::from_str(
        r"---
name: bond99
type: bond
state: up
link-aggregation:
  mode: active-backup
  options:
    fail_over_mac: active
",
    )
    .unwrap();
    let mut merged_iface =
        MergedInterface::new(Some(des_iface), Some(cur_iface)).unwrap();
    merged_iface.post_inter_ifaces_process_bond().unwrap();
}

#[test]
fn test_bond_validate_bond_mode_not_defined_for_new_iface() {
    let des_iface: Interface = serde_yaml::from_str(
        r"---
name: bond99
type: bond
state: up
",
    )
    .unwrap();
    let mut merged_iface = MergedInterface::new(Some(des_iface), None).unwrap();
    let result = merged_iface.post_inter_ifaces_process_bond();
    assert!(result.is_err());
    if let Err(e) = result {
        assert_eq!(e.kind(), ErrorKind::InvalidArgument);
    }
}

#[test]
fn test_bond_validate_ad_actor_system_mac_address() {
    let des_iface: Interface = serde_yaml::from_str(
        r#"---
name: bond99
type: bond
state: up
link-aggregation:
  mode: 802.3ad
  options:
    ad_actor_system: "01:00:5E:00:0f:01"
"#,
    )
    .unwrap();
    let mut merged_iface = MergedInterface::new(Some(des_iface), None).unwrap();
    let result = merged_iface.post_inter_ifaces_process_bond();
    assert!(result.is_err());
    if let Err(e) = result {
        assert_eq!(e.kind(), ErrorKind::InvalidArgument);
    }
}

#[test]
fn test_bond_validate_miimon_and_arp_interval() {
    let des_iface: Interface = serde_yaml::from_str(
        r"---
name: bond99
type: bond
state: up
link-aggregation:
  mode: 802.3ad
  options:
    miimon: 100
    arp_interval: 60
",
    )
    .unwrap();
    let mut merged_iface = MergedInterface::new(Some(des_iface), None).unwrap();
    let result = merged_iface.post_inter_ifaces_process_bond();
    assert!(result.is_err());
    if let Err(e) = result {
        assert_eq!(e.kind(), ErrorKind::InvalidArgument);
    }
}

#[test]
fn test_bond_stringlized_attributes() {
    let iface: BondInterface = serde_yaml::from_str(
        r#"---
name: bond99
type: bond
state: up
link-aggregation:
  mode: 802.3ad
  options:
    ad_actor_sys_prio: "555"
    ad_select: "1"
    ad_user_port_key: "16"
    all_slaves_active: "1"
    arp_interval: "100"
    arp_validate: "1"
    downdelay: "50"
    fail_over_mac: "1"
    lacp_rate: "0"
    lp_interval: "101"
    miimon: "200"
    min_links: "2"
    num_grat_arp: "3"
    num_unsol_na: "4"
    packets_per_slave: "1000"
    primary_reselect: "1"
    resend_igmp: "103"
    tlb_dynamic_lb: "true"
    updelay: "104"
    use_carrier: "false"
"#,
    )
    .unwrap();
    let bond_opts = iface.bond.unwrap().options.unwrap();
    assert_eq!(bond_opts.ad_actor_sys_prio, Some(555));
    assert_eq!(bond_opts.ad_select, Some(BondAdSelect::Bandwidth));
    assert_eq!(bond_opts.ad_user_port_key, Some(16));
    assert_eq!(
        bond_opts.all_slaves_active,
        Some(BondAllPortsActive::Delivered)
    );
    assert_eq!(bond_opts.arp_interval, Some(100));
    assert_eq!(bond_opts.arp_validate, Some(BondArpValidate::Active));
    assert_eq!(bond_opts.downdelay, Some(50));
    assert_eq!(bond_opts.fail_over_mac, Some(BondFailOverMac::Active));
    assert_eq!(bond_opts.lacp_rate, Some(BondLacpRate::Slow));
    assert_eq!(bond_opts.lp_interval, Some(101));
    assert_eq!(bond_opts.miimon, Some(200));
    assert_eq!(bond_opts.min_links, Some(2));
    assert_eq!(bond_opts.num_grat_arp, Some(3));
    assert_eq!(bond_opts.num_unsol_na, Some(4));
    assert_eq!(bond_opts.packets_per_slave, Some(1000));
    assert_eq!(
        bond_opts.primary_reselect,
        Some(BondPrimaryReselect::Better)
    );
    assert_eq!(bond_opts.resend_igmp, Some(103));
    assert_eq!(bond_opts.tlb_dynamic_lb, Some(true));
    assert_eq!(bond_opts.updelay, Some(104));
    assert_eq!(bond_opts.use_carrier, Some(false));
}

#[test]
fn test_integer_bond_mode() {
    let ifaces: Interfaces = serde_yaml::from_str(
        r#"---
- name: bond0
  type: bond
  state: up
  link-aggregation:
    mode: 0
- name: bond00
  type: bond
  state: up
  link-aggregation:
    mode: "0"
- name: bond1
  type: bond
  state: up
  link-aggregation:
    mode: 1
- name: bond10
  type: bond
  state: up
  link-aggregation:
    mode: "1"
- name: bond2
  type: bond
  state: up
  link-aggregation:
    mode: 2
- name: bond20
  type: bond
  state: up
  link-aggregation:
    mode: "2"
- name: bond3
  type: bond
  state: up
  link-aggregation:
    mode: 3
- name: bond30
  type: bond
  state: up
  link-aggregation:
    mode: "3"
- name: bond4
  type: bond
  state: up
  link-aggregation:
    mode: 4
- name: bond40
  type: bond
  state: up
  link-aggregation:
    mode: "4"
- name: bond5
  type: bond
  state: up
  link-aggregation:
    mode: 5
- name: bond50
  type: bond
  state: up
  link-aggregation:
    mode: "5"
- name: bond6
  type: bond
  state: up
  link-aggregation:
    mode: 6
- name: bond60
  type: bond
  state: up
  link-aggregation:
    mode: "6"
"#,
    )
    .unwrap();
    let mut bond_ifaces = Vec::new();
    for iface in ifaces.to_vec() {
        if let Interface::Bond(bond_iface) = iface {
            bond_ifaces.push(bond_iface);
        }
    }
    for (i, expected_bond_mode) in [
        BondMode::RoundRobin,
        BondMode::ActiveBackup,
        BondMode::XOR,
        BondMode::Broadcast,
        BondMode::LACP,
        BondMode::TLB,
        BondMode::ALB,
    ]
    .iter()
    .enumerate()
    {
        assert_eq!(
            expected_bond_mode,
            &bond_ifaces[i * 2].bond.as_ref().unwrap().mode.unwrap()
        );
        assert_eq!(
            expected_bond_mode,
            &bond_ifaces[i * 2 + 1].bond.as_ref().unwrap().mode.unwrap()
        );
    }
}

#[test]
fn test_integer_bond_opts() {
    let ifaces: Interfaces = serde_yaml::from_str(
        r"---
- name: bond60
  type: bond
  state: up
  link-aggregation:
    mode: 6
    options:
      ad_select: 0
      lacp_rate: 1
      all_slaves_active: 1
      arp_all_targets: 0
      arp_validate: 6
      fail_over_mac: 2
      primary_reselect: 2
      xmit_hash_policy: 5",
    )
    .unwrap();
    if let Interface::Bond(bond_iface) = ifaces.to_vec()[0] {
        let bond_opts = bond_iface
            .bond
            .as_ref()
            .unwrap()
            .options
            .as_ref()
            .unwrap()
            .clone();
        assert_eq!(bond_opts.ad_select.unwrap(), BondAdSelect::Stable);
        assert_eq!(bond_opts.lacp_rate.unwrap(), BondLacpRate::Fast);
        assert_eq!(
            bond_opts.all_slaves_active.unwrap(),
            BondAllPortsActive::Delivered
        );
        assert_eq!(bond_opts.arp_all_targets.unwrap(), BondArpAllTargets::Any);
        assert_eq!(
            bond_opts.arp_validate.unwrap(),
            BondArpValidate::FilterBackup
        );
        assert_eq!(bond_opts.fail_over_mac.unwrap(), BondFailOverMac::Follow);
        assert_eq!(
            bond_opts.primary_reselect.unwrap(),
            BondPrimaryReselect::Failure
        );
        assert_eq!(
            bond_opts.xmit_hash_policy.unwrap(),
            BondXmitHashPolicy::VlanSrcMac
        );
    } else {
        panic!("Failed to find bond interface")
    }
}

#[test]
fn test_bond_ports() {
    let ifaces: Interfaces = serde_yaml::from_str(
        r"---
        - name: bond99
          type: bond
          state: up
          link-aggregation:
            mode: balance-rr
            ports:
            - eth1
            - eth2
        ",
    )
    .unwrap();
    assert_eq!(ifaces.to_vec()[0].ports(), Some(vec!["eth1", "eth2"]));
}

#[test]
fn test_balance_slb_invalid_mode_from_current() {
    let des_iface: Interface = serde_yaml::from_str(
        r"---
        name: bond99
        type: bond
        state: up
        link-aggregation:
          ports:
          - eth1
          - eth2
          options:
            balance-slb: 1
            xmit_hash_policy: vlan+srcmac
        ",
    )
    .unwrap();
    let cur_iface: Interface = serde_yaml::from_str(
        r"---
        name: bond99
        type: bond
        state: up
        link-aggregation:
          mode: balance-rr
          ports:
          - eth1
          - eth2
        ",
    )
    .unwrap();
    let mut merged_iface =
        MergedInterface::new(Some(des_iface), Some(cur_iface)).unwrap();
    let result = merged_iface.post_inter_ifaces_process_bond();
    assert!(result.is_err());
    if let Err(e) = result {
        assert_eq!(e.kind(), ErrorKind::InvalidArgument);
    }
}

#[test]
fn test_balance_slb_invalid_xmit_from_current() {
    let des_iface: Interface = serde_yaml::from_str(
        r"---
        name: bond99
        type: bond
        state: up
        link-aggregation:
          ports:
          - eth1
          - eth2
          options:
            balance-slb: 1
        ",
    )
    .unwrap();
    let cur_iface: Interface = serde_yaml::from_str(
        r"---
        name: bond99
        type: bond
        state: up
        link-aggregation:
          mode: balance-xor
          ports:
          - eth1
          - eth2
          options:
            xmit_hash_policy: layer2
        ",
    )
    .unwrap();
    let mut merged_iface =
        MergedInterface::new(Some(des_iface), Some(cur_iface)).unwrap();
    let result = merged_iface.post_inter_ifaces_process_bond();
    assert!(result.is_err());
    if let Err(e) = result {
        assert_eq!(e.kind(), ErrorKind::InvalidArgument);
    }
}

#[test]
fn test_balance_slb_valid_override_current() {
    let des_iface: Interface = serde_yaml::from_str(
        r"---
        name: bond99
        type: bond
        state: up
        link-aggregation:
          ports:
          - eth1
          - eth2
          options:
            balance-slb: 1
            xmit_hash_policy: vlan+srcmac
        ",
    )
    .unwrap();
    let cur_iface: Interface = serde_yaml::from_str(
        r"---
        name: bond99
        type: bond
        state: up
        link-aggregation:
          mode: balance-xor
          ports:
          - eth1
          - eth2
          options:
            xmit_hash_policy: layer2
        ",
    )
    .unwrap();
    let mut merged_iface =
        MergedInterface::new(Some(des_iface), Some(cur_iface)).unwrap();
    merged_iface.post_inter_ifaces_process_bond().unwrap();
}

#[test]
fn test_disable_balance_slb_valid_override_current() {
    let des_iface: Interface = serde_yaml::from_str(
        r"---
        name: bond99
        type: bond
        state: up
        link-aggregation:
          ports:
          - eth1
          - eth2
          options:
            balance-slb: 0
            xmit_hash_policy: layer2
        ",
    )
    .unwrap();
    let cur_iface: Interface = serde_yaml::from_str(
        r"---
        name: bond99
        type: bond
        state: up
        link-aggregation:
          mode: balance-xor
          ports:
          - eth1
          - eth2
          options:
            balance-slb: 1
            xmit_hash_policy: vlan+srcmac
        ",
    )
    .unwrap();
    let mut merged_iface =
        MergedInterface::new(Some(des_iface), Some(cur_iface)).unwrap();
    merged_iface.post_inter_ifaces_process_bond().unwrap();
}

#[test]
fn test_bond_port_queue_id_overlap() {
    let mut des_iface: BondInterface = serde_yaml::from_str(
        r"---
        name: bond99
        type: bond
        state: up
        link-aggregation:
          mode: active-backup
          ports-config:
          - name: eth1
            queue-id: 1
          - name: eth2
            queue-id: 1
        ",
    )
    .unwrap();

    let result = des_iface.sanitize();

    assert!(result.is_err());

    if let Err(e) = result {
        assert_eq!(e.kind(), ErrorKind::InvalidArgument);
    }
}

#[test]
fn test_bond_port_queue_id_not_overlap_on_default() {
    let mut des_iface: BondInterface = serde_yaml::from_str(
        r"---
        name: bond99
        type: bond
        state: up
        link-aggregation:
          mode: active-backup
          ports-config:
          - name: eth1
            queue-id: 0
          - name: eth2
            queue-id: 0
        ",
    )
    .unwrap();

    des_iface.sanitize().unwrap();
}
