// Copyright 2015 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !nonetdev

package collector

import (
	"io"
	"log/slog"
	"testing"

	"github.com/jsimonetti/rtnetlink/v2"
)

var links = []rtnetlink.LinkMessage{
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name: "tun0",
			Stats64: &rtnetlink.LinkStats64{
				RXPackets: 24,
				TXPackets: 934,
				RXBytes:   1888,
				TXBytes:   67120,
			},
		},
	},
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name: "veth4B09XN",
			Stats64: &rtnetlink.LinkStats64{
				RXPackets: 8,
				TXPackets: 10640,
				RXBytes:   648,
				TXBytes:   1943284,
			},
		},
	},
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name: "lo",
			Stats64: &rtnetlink.LinkStats64{
				RXPackets: 1832522,
				TXPackets: 1832522,
				RXBytes:   435303245,
				TXBytes:   435303245,
			},
		},
	},
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name: "eth0",
			Stats64: &rtnetlink.LinkStats64{
				RXPackets: 520993275,
				TXPackets: 43451486,
				RXBytes:   68210035552,
				TXBytes:   9315587528,
			},
		},
	},
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name: "lxcbr0",
			Stats64: &rtnetlink.LinkStats64{
				TXPackets: 28339,
				TXBytes:   2630299,
			},
		},
	},
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name: "wlan0",
			Stats64: &rtnetlink.LinkStats64{
				RXPackets: 13899359,
				TXPackets: 11726200,
				RXBytes:   10437182923,
				TXBytes:   2851649360,
			},
		},
	},
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name: "docker0",
			Stats64: &rtnetlink.LinkStats64{
				RXPackets: 1065585,
				TXPackets: 1929779,
				RXBytes:   64910168,
				TXBytes:   2681662018,
			},
		},
	},
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name:    "ibr10:30",
			Stats64: &rtnetlink.LinkStats64{},
		},
	},
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name: "flannel.1",
			Stats64: &rtnetlink.LinkStats64{
				RXPackets: 228499337,
				TXPackets: 258369223,
				RXBytes:   18144009813,
				TXBytes:   20758990068,
				TXDropped: 64,
			},
		},
	},
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name: "💩0",
			Stats64: &rtnetlink.LinkStats64{
				RXPackets: 105557,
				TXPackets: 304261,
				RXBytes:   57750104,
				TXBytes:   404570255,
				Multicast: 72,
			},
		},
	},
	{
		Attributes: &rtnetlink.LinkAttributes{
			Name: "enp0s0f0",
			Stats64: &rtnetlink.LinkStats64{
				RXPackets:         226,
				TXPackets:         803,
				RXBytes:           231424,
				TXBytes:           822272,
				RXErrors:          14,
				TXErrors:          2,
				RXDropped:         10,
				TXDropped:         17,
				Multicast:         285,
				Collisions:        30,
				RXLengthErrors:    5,
				RXOverErrors:      3,
				RXCRCErrors:       1,
				RXFrameErrors:     4,
				RXFIFOErrors:      6,
				RXMissedErrors:    21,
				TXAbortedErrors:   22,
				TXCarrierErrors:   7,
				TXFIFOErrors:      24,
				TXHeartbeatErrors: 9,
				TXWindowErrors:    19,
				RXCompressed:      23,
				TXCompressed:      20,
				RXNoHandler:       62,
			},
		},
	},
}

func TestNetDevStatsIgnore(t *testing.T) {
	filter := newDeviceFilter("^veth", "")

	netStats := parseNetlinkStats(links, &filter, slog.New(slog.NewTextHandler(io.Discard, nil)))

	if want, got := uint64(10437182923), netStats["wlan0"]["receive_bytes"]; want != got {
		t.Errorf("want netstat wlan0 bytes %v, got %v", want, got)
	}

	if want, got := uint64(68210035552), netStats["eth0"]["receive_bytes"]; want != got {
		t.Errorf("want netstat eth0 bytes %v, got %v", want, got)
	}

	if want, got := uint64(934), netStats["tun0"]["transmit_packets"]; want != got {
		t.Errorf("want netstat tun0 packets %v, got %v", want, got)
	}

	if want, got := 10, len(netStats); want != got {
		t.Errorf("want count of devices to be %d, got %d", want, got)
	}

	if _, ok := netStats["veth4B09XN"]["transmit_bytes"]; ok {
		t.Error("want fixture interface veth4B09XN to not exist, but it does")
	}

	if want, got := uint64(0), netStats["ibr10:30"]["receive_fifo"]; want != got {
		t.Error("want fixture interface ibr10:30 to exist, but it does not")
	}

	if want, got := uint64(72), netStats["💩0"]["multicast"]; want != got {
		t.Error("want fixture interface 💩0 to exist, but it does not")
	}
}

func TestNetDevStatsAccept(t *testing.T) {
	filter := newDeviceFilter("", "^💩0$")
	netStats := parseNetlinkStats(links, &filter, slog.New(slog.NewTextHandler(io.Discard, nil)))

	if want, got := 1, len(netStats); want != got {
		t.Errorf("want count of devices to be %d, got %d", want, got)
	}
	if want, got := uint64(72), netStats["💩0"]["multicast"]; want != got {
		t.Error("want fixture interface 💩0 to exist, but it does not")
	}
}

func TestNetDevLegacyMetricNames(t *testing.T) {
	expected := []string{
		"receive_packets",
		"transmit_packets",
		"receive_bytes",
		"transmit_bytes",
		"receive_errs",
		"transmit_errs",
		"receive_drop",
		"transmit_drop",
		"receive_multicast",
		"transmit_colls",
		"receive_frame",
		"receive_fifo",
		"transmit_carrier",
		"transmit_fifo",
		"receive_compressed",
		"transmit_compressed",
	}

	filter := newDeviceFilter("", "")
	netStats := parseNetlinkStats(links, &filter, slog.New(slog.NewTextHandler(io.Discard, nil)))

	for dev, devStats := range netStats {
		legacy(devStats)
		for _, name := range expected {
			if _, ok := devStats[name]; !ok {
				t.Errorf("metric %s should be defined on interface %s", name, dev)
			}
		}
	}
}

func TestNetDevLegacyMetricValues(t *testing.T) {
	expected := map[string]uint64{
		"receive_packets":     226,
		"transmit_packets":    803,
		"receive_bytes":       231424,
		"transmit_bytes":      822272,
		"receive_errs":        14,
		"transmit_errs":       2,
		"receive_drop":        10 + 21,
		"transmit_drop":       17,
		"receive_multicast":   285,
		"transmit_colls":      30,
		"receive_frame":       5 + 3 + 1 + 4,
		"receive_fifo":        6,
		"transmit_carrier":    22 + 7 + 9 + 19,
		"transmit_fifo":       24,
		"receive_compressed":  23,
		"transmit_compressed": 20,
	}

	filter := newDeviceFilter("", "^enp0s0f0$")
	netStats := parseNetlinkStats(links, &filter, slog.New(slog.NewTextHandler(io.Discard, nil)))
	metrics, ok := netStats["enp0s0f0"]
	if !ok {
		t.Error("expected stats for interface enp0s0f0")
	}

	legacy(metrics)

	for name, want := range expected {
		got, ok := metrics[name]
		if !ok {
			t.Errorf("metric %s should be defined on interface enp0s0f0", name)
			continue
		}
		if want != got {
			t.Errorf("want %s %d, got %d", name, want, got)
		}
	}
}

func TestNetDevMetricValues(t *testing.T) {
	filter := newDeviceFilter("", "")
	netStats := parseNetlinkStats(links, &filter, slog.New(slog.NewTextHandler(io.Discard, nil)))

	for _, msg := range links {
		device := msg.Attributes.Name
		stats := msg.Attributes.Stats64

		expected := map[string]uint64{
			"receive_packets":  stats.RXPackets,
			"transmit_packets": stats.TXPackets,
			"receive_bytes":    stats.RXBytes,
			"transmit_bytes":   stats.TXBytes,
			"receive_errors":   stats.RXErrors,
			"transmit_errors":  stats.TXErrors,
			"receive_dropped":  stats.RXDropped,
			"transmit_dropped": stats.TXDropped,
			"multicast":        stats.Multicast,
			"collisions":       stats.Collisions,

			// detailed rx_errors
			"receive_length_errors": stats.RXLengthErrors,
			"receive_over_errors":   stats.RXOverErrors,
			"receive_crc_errors":    stats.RXCRCErrors,
			"receive_frame_errors":  stats.RXFrameErrors,
			"receive_fifo_errors":   stats.RXFIFOErrors,
			"receive_missed_errors": stats.RXMissedErrors,

			// detailed tx_errors
			"transmit_aborted_errors":   stats.TXAbortedErrors,
			"transmit_carrier_errors":   stats.TXCarrierErrors,
			"transmit_fifo_errors":      stats.TXFIFOErrors,
			"transmit_heartbeat_errors": stats.TXHeartbeatErrors,
			"transmit_window_errors":    stats.TXWindowErrors,

			// for cslip etc
			"receive_compressed":  stats.RXCompressed,
			"transmit_compressed": stats.TXCompressed,
			"receive_nohandler":   stats.RXNoHandler,
		}

		for name, want := range expected {
			devStats, ok := netStats[device]
			if !ok {
				t.Errorf("expected stats for interface %s", device)
			}
			got, ok := devStats[name]
			if !ok {
				t.Errorf("metric %s should be defined on interface %s", name, device)
			}
			if want != got {
				t.Errorf("want %s %d, got %d", name, want, got)
			}
		}
	}
}
