//go:build linux

package ethtool

import (
	"errors"
	"net"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/influxdata/telegraf/testutil"
)

var (
	eth          *Ethtool
	interfaceMap map[string]*interfaceMock
)

type interfaceMock struct {
	name          string
	driverName    string
	namespaceName string
	stat          map[string]uint64
	loopBack      bool
	interfaceUp   bool
	cmdGet        map[string]uint64
}

type namespaceMock struct {
	namespaceName string
}

func (n *namespaceMock) name() string {
	return n.namespaceName
}

func (n *namespaceMock) interfaces() ([]namespacedInterface, error) {
	return nil, errors.New("it is a test bug to invoke this function")
}

func (n *namespaceMock) driverName(_ namespacedInterface) (string, error) {
	return "", errors.New("it is a test bug to invoke this function")
}

func (n *namespaceMock) stats(_ namespacedInterface) (map[string]uint64, error) {
	return nil, errors.New("it is a test bug to invoke this function")
}

func (n *namespaceMock) get(_ namespacedInterface) (map[string]uint64, error) {
	return nil, errors.New("it is a test bug to invoke this function")
}

type commandEthtoolMock struct {
	interfaceMap map[string]*interfaceMock
}

func (c *commandEthtoolMock) init() error {
	// Not required for test mock
	return nil
}

func (c *commandEthtoolMock) driverName(intf namespacedInterface) (string, error) {
	i := c.interfaceMap[intf.Name]
	if i != nil {
		return i.driverName, nil
	}
	return "", errors.New("interface not found")
}

func (c *commandEthtoolMock) interfaces(includeNamespaces bool) ([]namespacedInterface, error) {
	namespaces := map[string]*namespaceMock{"": {namespaceName: ""}}

	interfaces := make([]namespacedInterface, 0)
	for k, v := range c.interfaceMap {
		if v.namespaceName != "" && !includeNamespaces {
			continue
		}

		var flag net.Flags
		// When interface is up
		if v.interfaceUp {
			flag |= net.FlagUp
		}
		// For loopback interface
		if v.loopBack {
			flag |= net.FlagLoopback
		}

		// Create a dummy interface
		iface := net.Interface{
			Index:        0,
			MTU:          1500,
			Name:         k,
			HardwareAddr: nil,
			Flags:        flag,
		}

		// Ensure there is a namespace if necessary
		if _, ok := namespaces[v.namespaceName]; !ok {
			namespaces[v.namespaceName] = &namespaceMock{
				namespaceName: v.namespaceName,
			}
		}

		interfaces = append(
			interfaces,
			namespacedInterface{
				Interface: iface,
				namespace: namespaces[v.namespaceName],
			},
		)
	}
	return interfaces, nil
}

func (c *commandEthtoolMock) stats(intf namespacedInterface) (map[string]uint64, error) {
	i := c.interfaceMap[intf.Name]
	if i != nil {
		return i.stat, nil
	}
	return nil, errors.New("interface not found")
}

func (c *commandEthtoolMock) get(intf namespacedInterface) (map[string]uint64, error) {
	i := c.interfaceMap[intf.Name]
	if i != nil {
		return i.cmdGet, nil
	}
	return nil, errors.New("interface not found")
}

func setup() {
	interfaceMap = make(map[string]*interfaceMock)

	eth1Stat := map[string]uint64{
		"interface_up":                   1,
		"port_rx_1024_to_15xx":           25167245,
		"port_rx_128_to_255":             1573526387,
		"port_rx_15xx_to_jumbo":          137819058,
		"port_rx_256_to_511":             772038107,
		"port_rx_512_to_1023":            78294457,
		"port_rx_64":                     8798065,
		"port_rx_65_to_127":              450348015,
		"port_rx_bad":                    0,
		"port_rx_bad_bytes":              0,
		"port_rx_bad_gtjumbo":            0,
		"port_rx_broadcast":              6428250,
		"port_rx_bytes":                  893460472634,
		"port_rx_control":                0,
		"port_rx_dp_di_dropped_packets":  2772680304,
		"port_rx_dp_hlb_fetch":           0,
		"port_rx_dp_hlb_wait":            0,
		"port_rx_dp_q_disabled_packets":  0,
		"port_rx_dp_streaming_packets":   0,
		"port_rx_good":                   3045991334,
		"port_rx_good_bytes":             893460472927,
		"port_rx_gtjumbo":                0,
		"port_rx_lt64":                   0,
		"port_rx_multicast":              1639566045,
		"port_rx_nodesc_drops":           0,
		"port_rx_overflow":               0,
		"port_rx_packets":                3045991334,
		"port_rx_pause":                  0,
		"port_rx_pm_discard_bb_overflow": 0,
		"port_rx_pm_discard_mapping":     0,
		"port_rx_pm_discard_qbb":         0,
		"port_rx_pm_discard_vfifo_full":  0,
		"port_rx_pm_trunc_bb_overflow":   0,
		"port_rx_pm_trunc_qbb":           0,
		"port_rx_pm_trunc_vfifo_full":    0,
		"port_rx_unicast":                1399997040,
		"port_tx_1024_to_15xx":           236,
		"port_tx_128_to_255":             275090219,
		"port_tx_15xx_to_jumbo":          926,
		"port_tx_256_to_511":             48567221,
		"port_tx_512_to_1023":            5142016,
		"port_tx_64":                     113903973,
		"port_tx_65_to_127":              161935699,
		"port_tx_broadcast":              8,
		"port_tx_bytes":                  94357131016,
		"port_tx_control":                0,
		"port_tx_lt64":                   0,
		"port_tx_multicast":              325891647,
		"port_tx_packets":                604640290,
		"port_tx_pause":                  0,
		"port_tx_unicast":                278748635,
		"ptp_bad_syncs":                  1,
		"ptp_fast_syncs":                 1,
		"ptp_filter_matches":             0,
		"ptp_good_syncs":                 136151,
		"ptp_invalid_sync_windows":       0,
		"ptp_no_time_syncs":              1,
		"ptp_non_filter_matches":         0,
		"ptp_oversize_sync_windows":      53,
		"ptp_rx_no_timestamp":            0,
		"ptp_rx_timestamp_packets":       0,
		"ptp_sync_timeouts":              1,
		"ptp_timestamp_packets":          0,
		"ptp_tx_timestamp_packets":       0,
		"ptp_undersize_sync_windows":     3,
		"rx-0.rx_packets":                55659234,
		"rx-1.rx_packets":                87880538,
		"rx-2.rx_packets":                26746234,
		"rx-3.rx_packets":                103026471,
		"rx-4.rx_packets":                0,
		"rx_eth_crc_err":                 0,
		"rx_frm_trunc":                   0,
		"rx_inner_ip_hdr_chksum_err":     0,
		"rx_inner_tcp_udp_chksum_err":    0,
		"rx_ip_hdr_chksum_err":           0,
		"rx_mcast_mismatch":              0,
		"rx_merge_events":                0,
		"rx_merge_packets":               0,
		"rx_nodesc_trunc":                0,
		"rx_noskb_drops":                 0,
		"rx_outer_ip_hdr_chksum_err":     0,
		"rx_outer_tcp_udp_chksum_err":    0,
		"rx_reset":                       0,
		"rx_tcp_udp_chksum_err":          0,
		"rx_tobe_disc":                   0,
		"tx-0.tx_packets":                85843565,
		"tx-1.tx_packets":                108642725,
		"tx-2.tx_packets":                202596078,
		"tx-3.tx_packets":                207561010,
		"tx-4.tx_packets":                0,
		"tx_cb_packets":                  4,
		"tx_merge_events":                11025,
		"tx_pio_packets":                 531928114,
		"tx_pushes":                      604643378,
		"tx_tso_bursts":                  0,
		"tx_tso_fallbacks":               0,
		"tx_tso_long_headers":            0,
	}
	eth1Get := map[string]uint64{
		"autoneg": 1,
		"duplex":  1,
		"link":    1,
		"speed":   1000,
	}
	eth1 := &interfaceMock{"eth1", "driver1", "", eth1Stat, false, true, eth1Get}
	interfaceMap[eth1.name] = eth1

	eth2Stat := map[string]uint64{
		"interface_up":                   0,
		"port_rx_1024_to_15xx":           11529312,
		"port_rx_128_to_255":             1868952037,
		"port_rx_15xx_to_jumbo":          130339387,
		"port_rx_256_to_511":             843846270,
		"port_rx_512_to_1023":            173194372,
		"port_rx_64":                     9190374,
		"port_rx_65_to_127":              507806115,
		"port_rx_bad":                    0,
		"port_rx_bad_bytes":              0,
		"port_rx_bad_gtjumbo":            0,
		"port_rx_broadcast":              6648019,
		"port_rx_bytes":                  1007358162202,
		"port_rx_control":                0,
		"port_rx_dp_di_dropped_packets":  3164124639,
		"port_rx_dp_hlb_fetch":           0,
		"port_rx_dp_hlb_wait":            0,
		"port_rx_dp_q_disabled_packets":  0,
		"port_rx_dp_streaming_packets":   0,
		"port_rx_good":                   3544857867,
		"port_rx_good_bytes":             1007358162202,
		"port_rx_gtjumbo":                0,
		"port_rx_lt64":                   0,
		"port_rx_multicast":              2231999743,
		"port_rx_nodesc_drops":           0,
		"port_rx_overflow":               0,
		"port_rx_packets":                3544857867,
		"port_rx_pause":                  0,
		"port_rx_pm_discard_bb_overflow": 0,
		"port_rx_pm_discard_mapping":     0,
		"port_rx_pm_discard_qbb":         0,
		"port_rx_pm_discard_vfifo_full":  0,
		"port_rx_pm_trunc_bb_overflow":   0,
		"port_rx_pm_trunc_qbb":           0,
		"port_rx_pm_trunc_vfifo_full":    0,
		"port_rx_unicast":                1306210105,
		"port_tx_1024_to_15xx":           379,
		"port_tx_128_to_255":             202767251,
		"port_tx_15xx_to_jumbo":          558,
		"port_tx_256_to_511":             31454719,
		"port_tx_512_to_1023":            6865731,
		"port_tx_64":                     17268276,
		"port_tx_65_to_127":              272816313,
		"port_tx_broadcast":              6,
		"port_tx_bytes":                  78071946593,
		"port_tx_control":                0,
		"port_tx_lt64":                   0,
		"port_tx_multicast":              239510586,
		"port_tx_packets":                531173227,
		"port_tx_pause":                  0,
		"port_tx_unicast":                291662635,
		"ptp_bad_syncs":                  0,
		"ptp_fast_syncs":                 0,
		"ptp_filter_matches":             0,
		"ptp_good_syncs":                 0,
		"ptp_invalid_sync_windows":       0,
		"ptp_no_time_syncs":              0,
		"ptp_non_filter_matches":         0,
		"ptp_oversize_sync_windows":      0,
		"ptp_rx_no_timestamp":            0,
		"ptp_rx_timestamp_packets":       0,
		"ptp_sync_timeouts":              0,
		"ptp_timestamp_packets":          0,
		"ptp_tx_timestamp_packets":       0,
		"ptp_undersize_sync_windows":     0,
		"rx-0.rx_packets":                84587075,
		"rx-1.rx_packets":                74029305,
		"rx-2.rx_packets":                134586471,
		"rx-3.rx_packets":                87531322,
		"rx-4.rx_packets":                0,
		"rx_eth_crc_err":                 0,
		"rx_frm_trunc":                   0,
		"rx_inner_ip_hdr_chksum_err":     0,
		"rx_inner_tcp_udp_chksum_err":    0,
		"rx_ip_hdr_chksum_err":           0,
		"rx_mcast_mismatch":              0,
		"rx_merge_events":                0,
		"rx_merge_packets":               0,
		"rx_nodesc_trunc":                0,
		"rx_noskb_drops":                 0,
		"rx_outer_ip_hdr_chksum_err":     0,
		"rx_outer_tcp_udp_chksum_err":    0,
		"rx_reset":                       0,
		"rx_tcp_udp_chksum_err":          0,
		"rx_tobe_disc":                   0,
		"tx-0.tx_packets":                232521451,
		"tx-1.tx_packets":                97876137,
		"tx-2.tx_packets":                106822111,
		"tx-3.tx_packets":                93955050,
		"tx-4.tx_packets":                0,
		"tx_cb_packets":                  1,
		"tx_merge_events":                8402,
		"tx_pio_packets":                 481040054,
		"tx_pushes":                      531174491,
		"tx_tso_bursts":                  128,
		"tx_tso_fallbacks":               0,
		"tx_tso_long_headers":            0,
	}
	eth2Get := map[string]uint64{
		"autoneg": 0,
		"duplex":  255,
		"link":    0,
		"speed":   9223372036854775807,
	}
	eth2 := &interfaceMock{"eth2", "driver1", "", eth2Stat, false, false, eth2Get}
	interfaceMap[eth2.name] = eth2

	eth3Stat := map[string]uint64{
		"interface_up":                   1,
		"port_rx_1024_to_15xx":           25167245,
		"port_rx_128_to_255":             1573526387,
		"port_rx_15xx_to_jumbo":          137819058,
		"port_rx_256_to_511":             772038107,
		"port_rx_512_to_1023":            78294457,
		"port_rx_64":                     8798065,
		"port_rx_65_to_127":              450348015,
		"port_rx_bad":                    0,
		"port_rx_bad_bytes":              0,
		"port_rx_bad_gtjumbo":            0,
		"port_rx_broadcast":              6428250,
		"port_rx_bytes":                  893460472634,
		"port_rx_control":                0,
		"port_rx_dp_di_dropped_packets":  2772680304,
		"port_rx_dp_hlb_fetch":           0,
		"port_rx_dp_hlb_wait":            0,
		"port_rx_dp_q_disabled_packets":  0,
		"port_rx_dp_streaming_packets":   0,
		"port_rx_good":                   3045991334,
		"port_rx_good_bytes":             893460472927,
		"port_rx_gtjumbo":                0,
		"port_rx_lt64":                   0,
		"port_rx_multicast":              1639566045,
		"port_rx_nodesc_drops":           0,
		"port_rx_overflow":               0,
		"port_rx_packets":                3045991334,
		"port_rx_pause":                  0,
		"port_rx_pm_discard_bb_overflow": 0,
		"port_rx_pm_discard_mapping":     0,
		"port_rx_pm_discard_qbb":         0,
		"port_rx_pm_discard_vfifo_full":  0,
		"port_rx_pm_trunc_bb_overflow":   0,
		"port_rx_pm_trunc_qbb":           0,
		"port_rx_pm_trunc_vfifo_full":    0,
		"port_rx_unicast":                1399997040,
		"port_tx_1024_to_15xx":           236,
		"port_tx_128_to_255":             275090219,
		"port_tx_15xx_to_jumbo":          926,
		"port_tx_256_to_511":             48567221,
		"port_tx_512_to_1023":            5142016,
		"port_tx_64":                     113903973,
		"port_tx_65_to_127":              161935699,
		"port_tx_broadcast":              8,
		"port_tx_bytes":                  94357131016,
		"port_tx_control":                0,
		"port_tx_lt64":                   0,
		"port_tx_multicast":              325891647,
		"port_tx_packets":                604640290,
		"port_tx_pause":                  0,
		"port_tx_unicast":                278748635,
		"ptp_bad_syncs":                  1,
		"ptp_fast_syncs":                 1,
		"ptp_filter_matches":             0,
		"ptp_good_syncs":                 136151,
		"ptp_invalid_sync_windows":       0,
		"ptp_no_time_syncs":              1,
		"ptp_non_filter_matches":         0,
		"ptp_oversize_sync_windows":      53,
		"ptp_rx_no_timestamp":            0,
		"ptp_rx_timestamp_packets":       0,
		"ptp_sync_timeouts":              1,
		"ptp_timestamp_packets":          0,
		"ptp_tx_timestamp_packets":       0,
		"ptp_undersize_sync_windows":     3,
		"rx-0.rx_packets":                55659234,
		"rx-1.rx_packets":                87880538,
		"rx-2.rx_packets":                26746234,
		"rx-3.rx_packets":                103026471,
		"rx-4.rx_packets":                0,
		"rx_eth_crc_err":                 0,
		"rx_frm_trunc":                   0,
		"rx_inner_ip_hdr_chksum_err":     0,
		"rx_inner_tcp_udp_chksum_err":    0,
		"rx_ip_hdr_chksum_err":           0,
		"rx_mcast_mismatch":              0,
		"rx_merge_events":                0,
		"rx_merge_packets":               0,
		"rx_nodesc_trunc":                0,
		"rx_noskb_drops":                 0,
		"rx_outer_ip_hdr_chksum_err":     0,
		"rx_outer_tcp_udp_chksum_err":    0,
		"rx_reset":                       0,
		"rx_tcp_udp_chksum_err":          0,
		"rx_tobe_disc":                   0,
		"tx-0.tx_packets":                85843565,
		"tx-1.tx_packets":                108642725,
		"tx-2.tx_packets":                202596078,
		"tx-3.tx_packets":                207561010,
		"tx-4.tx_packets":                0,
		"tx_cb_packets":                  4,
		"tx_merge_events":                11025,
		"tx_pio_packets":                 531928114,
		"tx_pushes":                      604643378,
		"tx_tso_bursts":                  0,
		"tx_tso_fallbacks":               0,
		"tx_tso_long_headers":            0,
	}
	eth3Get := map[string]uint64{
		"autoneg": 1,
		"duplex":  1,
		"link":    1,
		"speed":   1000,
	}
	eth3 := &interfaceMock{"eth3", "driver1", "namespace1", eth3Stat, false, true, eth3Get}
	interfaceMap[eth3.name] = eth3

	eth4Stat := map[string]uint64{
		"interface_up":                   1,
		"port_rx_1024_to_15xx":           25167245,
		"port_rx_128_to_255":             1573526387,
		"port_rx_15xx_to_jumbo":          137819058,
		"port_rx_256_to_511":             772038107,
		"port_rx_512_to_1023":            78294457,
		"port_rx_64":                     8798065,
		"port_rx_65_to_127":              450348015,
		"port_rx_bad":                    0,
		"port_rx_bad_bytes":              0,
		"port_rx_bad_gtjumbo":            0,
		"port_rx_broadcast":              6428250,
		"port_rx_bytes":                  893460472634,
		"port_rx_control":                0,
		"port_rx_dp_di_dropped_packets":  2772680304,
		"port_rx_dp_hlb_fetch":           0,
		"port_rx_dp_hlb_wait":            0,
		"port_rx_dp_q_disabled_packets":  0,
		"port_rx_dp_streaming_packets":   0,
		"port_rx_good":                   3045991334,
		"port_rx_good_bytes":             893460472927,
		"port_rx_gtjumbo":                0,
		"port_rx_lt64":                   0,
		"port_rx_multicast":              1639566045,
		"port_rx_nodesc_drops":           0,
		"port_rx_overflow":               0,
		"port_rx_packets":                3045991334,
		"port_rx_pause":                  0,
		"port_rx_pm_discard_bb_overflow": 0,
		"port_rx_pm_discard_mapping":     0,
		"port_rx_pm_discard_qbb":         0,
		"port_rx_pm_discard_vfifo_full":  0,
		"port_rx_pm_trunc_bb_overflow":   0,
		"port_rx_pm_trunc_qbb":           0,
		"port_rx_pm_trunc_vfifo_full":    0,
		"port_rx_unicast":                1399997040,
		"port_tx_1024_to_15xx":           236,
		"port_tx_128_to_255":             275090219,
		"port_tx_15xx_to_jumbo":          926,
		"port_tx_256_to_511":             48567221,
		"port_tx_512_to_1023":            5142016,
		"port_tx_64":                     113903973,
		"port_tx_65_to_127":              161935699,
		"port_tx_broadcast":              8,
		"port_tx_bytes":                  94357131016,
		"port_tx_control":                0,
		"port_tx_lt64":                   0,
		"port_tx_multicast":              325891647,
		"port_tx_packets":                604640290,
		"port_tx_pause":                  0,
		"port_tx_unicast":                278748635,
		"ptp_bad_syncs":                  1,
		"ptp_fast_syncs":                 1,
		"ptp_filter_matches":             0,
		"ptp_good_syncs":                 136151,
		"ptp_invalid_sync_windows":       0,
		"ptp_no_time_syncs":              1,
		"ptp_non_filter_matches":         0,
		"ptp_oversize_sync_windows":      53,
		"ptp_rx_no_timestamp":            0,
		"ptp_rx_timestamp_packets":       0,
		"ptp_sync_timeouts":              1,
		"ptp_timestamp_packets":          0,
		"ptp_tx_timestamp_packets":       0,
		"ptp_undersize_sync_windows":     3,
		"rx-0.rx_packets":                55659234,
		"rx-1.rx_packets":                87880538,
		"rx-2.rx_packets":                26746234,
		"rx-3.rx_packets":                103026471,
		"rx-4.rx_packets":                0,
		"rx_eth_crc_err":                 0,
		"rx_frm_trunc":                   0,
		"rx_inner_ip_hdr_chksum_err":     0,
		"rx_inner_tcp_udp_chksum_err":    0,
		"rx_ip_hdr_chksum_err":           0,
		"rx_mcast_mismatch":              0,
		"rx_merge_events":                0,
		"rx_merge_packets":               0,
		"rx_nodesc_trunc":                0,
		"rx_noskb_drops":                 0,
		"rx_outer_ip_hdr_chksum_err":     0,
		"rx_outer_tcp_udp_chksum_err":    0,
		"rx_reset":                       0,
		"rx_tcp_udp_chksum_err":          0,
		"rx_tobe_disc":                   0,
		"tx-0.tx_packets":                85843565,
		"tx-1.tx_packets":                108642725,
		"tx-2.tx_packets":                202596078,
		"tx-3.tx_packets":                207561010,
		"tx-4.tx_packets":                0,
		"tx_cb_packets":                  4,
		"tx_merge_events":                11025,
		"tx_pio_packets":                 531928114,
		"tx_pushes":                      604643378,
		"tx_tso_bursts":                  0,
		"tx_tso_fallbacks":               0,
		"tx_tso_long_headers":            0,
	}
	eth4Get := map[string]uint64{
		"autoneg": 1,
		"duplex":  1,
		"link":    1,
		"speed":   100,
	}
	eth4 := &interfaceMock{"eth4", "driver1", "namespace2", eth4Stat, false, true, eth4Get}
	interfaceMap[eth4.name] = eth4

	// dummy loopback including dummy stat to ensure that the ignore feature is working
	lo0Stat := map[string]uint64{
		"dummy": 0,
	}
	lo0Get := map[string]uint64{
		"autoneg": 1,
		"duplex":  1,
		"link":    1,
		"speed":   1000,
	}
	lo0 := &interfaceMock{"lo0", "", "", lo0Stat, true, true, lo0Get}
	interfaceMap[lo0.name] = lo0

	c := &commandEthtoolMock{interfaceMap}
	eth = &Ethtool{
		InterfaceInclude: []string{},
		InterfaceExclude: []string{},
		DownInterfaces:   "expose",
		command:          c,
	}
}

func toStringMapInterface(in map[string]uint64) map[string]interface{} {
	m := map[string]interface{}{}
	for k, v := range in {
		m[k] = v
	}
	return m
}

func toStringMapUint(in map[string]interface{}) map[string]uint64 {
	m := map[string]uint64{}
	for k, v := range in {
		t := v.(uint64)
		m[k] = t
	}
	return m
}

func TestGather(t *testing.T) {
	setup()

	err := eth.Init()
	require.NoError(t, err)

	var acc testutil.Accumulator
	err = eth.Gather(&acc)
	require.NoError(t, err)
	require.Len(t, acc.Metrics, 2)

	expectedFieldsEth1 := toStringMapInterface(interfaceMap["eth1"].stat)
	for k, v := range interfaceMap["eth1"].cmdGet {
		expectedFieldsEth1[k] = v
	}
	expectedFieldsEth1["interface_up_counter"] = expectedFieldsEth1["interface_up"]
	expectedFieldsEth1["interface_up"] = true

	expectedTagsEth1 := map[string]string{
		"interface": "eth1",
		"driver":    "driver1",
		"namespace": "",
	}
	acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth1, expectedTagsEth1)

	expectedFieldsEth2 := toStringMapInterface(interfaceMap["eth2"].stat)
	for k, v := range interfaceMap["eth2"].cmdGet {
		expectedFieldsEth2[k] = v
	}
	expectedFieldsEth2["interface_up_counter"] = expectedFieldsEth2["interface_up"]
	expectedFieldsEth2["interface_up"] = false
	expectedTagsEth2 := map[string]string{
		"driver":    "driver1",
		"interface": "eth2",
		"namespace": "",
	}
	acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
}

func TestGatherIncludeInterfaces(t *testing.T) {
	setup()

	eth.InterfaceInclude = append(eth.InterfaceInclude, "eth1")

	err := eth.Init()
	require.NoError(t, err)

	var acc testutil.Accumulator
	err = eth.Gather(&acc)
	require.NoError(t, err)
	require.Len(t, acc.Metrics, 1)

	// Should contain eth1
	expectedFieldsEth1 := toStringMapInterface(interfaceMap["eth1"].stat)
	for k, v := range interfaceMap["eth1"].cmdGet {
		expectedFieldsEth1[k] = v
	}
	expectedFieldsEth1["interface_up_counter"] = expectedFieldsEth1["interface_up"]
	expectedFieldsEth1["interface_up"] = true
	expectedTagsEth1 := map[string]string{
		"interface": "eth1",
		"driver":    "driver1",
		"namespace": "",
	}
	acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth1, expectedTagsEth1)

	// Should not contain eth2
	expectedFieldsEth2 := toStringMapInterface(interfaceMap["eth2"].stat)
	for k, v := range interfaceMap["eth2"].cmdGet {
		expectedFieldsEth2[k] = v
	}
	expectedFieldsEth2["interface_up_counter"] = expectedFieldsEth2["interface_up"]
	expectedFieldsEth2["interface_up"] = false
	expectedTagsEth2 := map[string]string{
		"interface": "eth2",
		"driver":    "driver1",
		"namespace": "",
	}
	acc.AssertDoesNotContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
}

func TestGatherIgnoreInterfaces(t *testing.T) {
	setup()

	eth.InterfaceExclude = append(eth.InterfaceExclude, "eth1")

	err := eth.Init()
	require.NoError(t, err)

	var acc testutil.Accumulator
	err = eth.Gather(&acc)
	require.NoError(t, err)
	require.Len(t, acc.Metrics, 1)

	// Should not contain eth1
	expectedFieldsEth1 := toStringMapInterface(interfaceMap["eth1"].stat)
	for k, v := range interfaceMap["eth1"].cmdGet {
		expectedFieldsEth1[k] = v
	}
	expectedFieldsEth1["interface_up_counter"] = expectedFieldsEth1["interface_up"]
	expectedFieldsEth1["interface_up"] = true
	expectedTagsEth1 := map[string]string{
		"interface": "eth1",
		"driver":    "driver1",
		"namespace": "",
	}
	acc.AssertDoesNotContainsTaggedFields(t, pluginName, expectedFieldsEth1, expectedTagsEth1)

	// Should contain eth2
	expectedFieldsEth2 := toStringMapInterface(interfaceMap["eth2"].stat)
	for k, v := range interfaceMap["eth2"].cmdGet {
		expectedFieldsEth2[k] = v
	}
	expectedFieldsEth2["interface_up_counter"] = expectedFieldsEth2["interface_up"]
	expectedFieldsEth2["interface_up"] = false
	expectedTagsEth2 := map[string]string{
		"interface": "eth2",
		"driver":    "driver1",
		"namespace": "",
	}
	acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
}

func TestSkipMetricsForInterfaceDown(t *testing.T) {
	setup()

	eth.DownInterfaces = "skip"

	err := eth.Init()
	require.NoError(t, err)

	var acc testutil.Accumulator
	err = eth.Gather(&acc)
	require.NoError(t, err)
	require.Len(t, acc.Metrics, 1)

	expectedFieldsEth1 := toStringMapInterface(interfaceMap["eth1"].stat)
	for k, v := range interfaceMap["eth1"].cmdGet {
		expectedFieldsEth1[k] = v
	}
	expectedFieldsEth1["interface_up_counter"] = expectedFieldsEth1["interface_up"]
	expectedFieldsEth1["interface_up"] = true

	expectedTagsEth1 := map[string]string{
		"interface": "eth1",
		"driver":    "driver1",
		"namespace": "",
	}
	acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth1, expectedTagsEth1)
}

func TestGatherIncludeNamespaces(t *testing.T) {
	setup()
	var acc testutil.Accumulator

	eth.NamespaceInclude = append(eth.NamespaceInclude, "namespace1")

	err := eth.Init()
	require.NoError(t, err)

	err = eth.Gather(&acc)
	require.NoError(t, err)
	require.Len(t, acc.Metrics, 1)

	// Should contain eth3
	expectedFieldsEth3 := toStringMapInterface(interfaceMap["eth3"].stat)
	for k, v := range interfaceMap["eth3"].cmdGet {
		expectedFieldsEth3[k] = v
	}
	expectedFieldsEth3["interface_up_counter"] = expectedFieldsEth3["interface_up"]
	expectedFieldsEth3["interface_up"] = true
	expectedTagsEth3 := map[string]string{
		"interface": "eth3",
		"driver":    "driver1",
		"namespace": "namespace1",
	}
	acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth3, expectedTagsEth3)

	// Should not contain eth2
	expectedFieldsEth2 := toStringMapInterface(interfaceMap["eth2"].stat)
	for k, v := range interfaceMap["eth2"].cmdGet {
		expectedFieldsEth2[k] = v
	}
	expectedFieldsEth2["interface_up_counter"] = expectedFieldsEth2["interface_up"]
	expectedFieldsEth2["interface_up"] = false
	expectedTagsEth2 := map[string]string{
		"interface": "eth2",
		"driver":    "driver1",
		"namespace": "",
	}
	acc.AssertDoesNotContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)
}

func TestGatherIgnoreNamespaces(t *testing.T) {
	setup()
	var acc testutil.Accumulator

	eth.NamespaceExclude = append(eth.NamespaceExclude, "namespace2")

	err := eth.Init()
	require.NoError(t, err)

	err = eth.Gather(&acc)
	require.NoError(t, err)
	require.Len(t, acc.Metrics, 3)

	// Should not contain eth4
	expectedFieldsEth4 := toStringMapInterface(interfaceMap["eth4"].stat)
	for k, v := range interfaceMap["eth4"].cmdGet {
		expectedFieldsEth4[k] = v
	}
	expectedFieldsEth4["interface_up_counter"] = expectedFieldsEth4["interface_up"]
	expectedFieldsEth4["interface_up"] = true
	expectedTagsEth4 := map[string]string{
		"interface": "eth4",
		"driver":    "driver1",
		"namespace": "namespace2",
	}
	acc.AssertDoesNotContainsTaggedFields(t, pluginName, expectedFieldsEth4, expectedTagsEth4)

	// Should contain eth2
	expectedFieldsEth2 := toStringMapInterface(interfaceMap["eth2"].stat)
	for k, v := range interfaceMap["eth2"].cmdGet {
		expectedFieldsEth2[k] = v
	}
	expectedFieldsEth2["interface_up_counter"] = expectedFieldsEth2["interface_up"]
	expectedFieldsEth2["interface_up"] = false
	expectedTagsEth2 := map[string]string{
		"interface": "eth2",
		"driver":    "driver1",
		"namespace": "",
	}
	acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth2, expectedTagsEth2)

	// Should contain eth3
	expectedFieldsEth3 := toStringMapInterface(interfaceMap["eth3"].stat)
	for k, v := range interfaceMap["eth3"].cmdGet {
		expectedFieldsEth3[k] = v
	}
	expectedFieldsEth3["interface_up_counter"] = expectedFieldsEth3["interface_up"]
	expectedFieldsEth3["interface_up"] = true
	expectedTagsEth3 := map[string]string{
		"interface": "eth3",
		"driver":    "driver1",
		"namespace": "namespace1",
	}
	acc.AssertContainsTaggedFields(t, pluginName, expectedFieldsEth3, expectedTagsEth3)
}

type testCase struct {
	normalization  []string
	stats          map[string]interface{}
	expectedFields map[string]interface{}
}

func TestNormalizedKeys(t *testing.T) {
	cases := []testCase{
		{
			normalization: []string{"underscore"},
			stats: map[string]interface{}{
				"port rx":      uint64(1),
				" Port_tx":     uint64(0),
				"interface_up": uint64(0),
			},
			expectedFields: map[string]interface{}{
				"port_rx":              uint64(1),
				"_Port_tx":             uint64(0),
				"interface_up":         true,
				"interface_up_counter": uint64(0),
			},
		},
		{
			normalization: []string{"underscore", "lower"},
			stats: map[string]interface{}{
				"Port rx":      uint64(1),
				" Port_tx":     uint64(0),
				"interface_up": uint64(0),
			},
			expectedFields: map[string]interface{}{
				"port_rx":              uint64(1),
				"_port_tx":             uint64(0),
				"interface_up":         true,
				"interface_up_counter": uint64(0),
			},
		},
		{
			normalization: []string{"underscore", "lower", "trim"},
			stats: map[string]interface{}{
				"  Port RX ":   uint64(1),
				" Port_tx":     uint64(0),
				"interface_up": uint64(0),
			},
			expectedFields: map[string]interface{}{
				"port_rx":              uint64(1),
				"port_tx":              uint64(0),
				"interface_up":         true,
				"interface_up_counter": uint64(0),
			},
		},
		{
			normalization: []string{"underscore", "lower", "snakecase", "trim"},
			stats: map[string]interface{}{
				"  Port RX ":   uint64(1),
				" Port_tx":     uint64(0),
				"interface_up": uint64(0),
			},
			expectedFields: map[string]interface{}{
				"port_rx":              uint64(1),
				"port_tx":              uint64(0),
				"interface_up":         true,
				"interface_up_counter": uint64(0),
			},
		},
		{
			normalization: []string{"snakecase"},
			stats: map[string]interface{}{
				"  PortRX ":    uint64(1),
				" PortTX":      uint64(0),
				"interface_up": uint64(0),
			},
			expectedFields: map[string]interface{}{
				"port_rx":              uint64(1),
				"port_tx":              uint64(0),
				"interface_up":         true,
				"interface_up_counter": uint64(0),
			},
		},
		{
			normalization: []string{},
			stats: map[string]interface{}{
				"  Port RX ":   uint64(1),
				" Port_tx":     uint64(0),
				"interface_up": uint64(0),
			},
			expectedFields: map[string]interface{}{
				"  Port RX ":           uint64(1),
				" Port_tx":             uint64(0),
				"interface_up":         true,
				"interface_up_counter": uint64(0),
			},
		},
		{
			normalization: []string{},
			stats: map[string]interface{}{
				"  Port RX ": uint64(1),
				" Port_tx":   uint64(0),
			},
			expectedFields: map[string]interface{}{
				"  Port RX ":   uint64(1),
				" Port_tx":     uint64(0),
				"interface_up": true,
			},
		},
	}

	for _, c := range cases {
		eth0 := &interfaceMock{"eth0", "e1000e", "", toStringMapUint(c.stats), false, true, map[string]uint64{}}
		expectedTags := map[string]string{
			"interface": eth0.name,
			"driver":    eth0.driverName,
			"namespace": "",
		}

		interfaceMap = make(map[string]*interfaceMock)
		interfaceMap[eth0.name] = eth0

		cmd := &commandEthtoolMock{interfaceMap}
		eth = &Ethtool{
			InterfaceInclude: []string{},
			InterfaceExclude: []string{},
			NormalizeKeys:    c.normalization,
			command:          cmd,
		}

		err := eth.Init()
		require.NoError(t, err)

		var acc testutil.Accumulator
		err = eth.Gather(&acc)

		require.NoError(t, err)
		require.Len(t, acc.Metrics, 1)

		acc.AssertContainsFields(t, pluginName, c.expectedFields)
		acc.AssertContainsTaggedFields(t, pluginName, c.expectedFields, expectedTags)
	}
}
