package collector

import (
	"encoding/json"
	"strings"
	"testing"
)

func TestShouldIncludePCIeDevice(t *testing.T) {
	tests := []struct {
		name   string
		class  string
		vendor string
		device string
		want   bool
	}{
		{name: "usb", class: "USB controller", want: false},
		{name: "system peripheral", class: "System peripheral", want: false},
		{name: "audio", class: "Audio device", want: false},
		{name: "host bridge", class: "Host bridge", want: false},
		{name: "pci bridge", class: "PCI bridge", want: false},
		{name: "smbus", class: "SMBus", want: false},
		{name: "perf", class: "Performance counters", want: false},
		{name: "non essential instrumentation", class: "Non-Essential Instrumentation", want: false},
		{name: "amd dummy function", class: "Encryption controller", vendor: "Advanced Micro Devices, Inc. [AMD]", device: "Starship/Matisse PTDMA", want: false},
		{name: "amd pspcpp", class: "Encryption controller", vendor: "Advanced Micro Devices, Inc. [AMD]", device: "Starship/Matisse Cryptographic Coprocessor PSPCPP", want: false},
		{name: "ethernet", class: "Ethernet controller", want: true},
		{name: "raid", class: "RAID bus controller", want: true},
		{name: "nvme", class: "Non-Volatile memory controller", want: true},
		{name: "vga", class: "VGA compatible controller", want: true},
		{name: "other encryption controller", class: "Encryption controller", vendor: "Intel Corporation", device: "QuickAssist", want: true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := shouldIncludePCIeDevice(tt.class, tt.vendor, tt.device)
			if got != tt.want {
				t.Fatalf("class=%q vendor=%q device=%q include=%v want %v", tt.class, tt.vendor, tt.device, got, tt.want)
			}
		})
	}
}

func TestParseLspci_filtersExcludedClasses(t *testing.T) {
	input := "Slot:\t0000:00:14.0\nClass:\tUSB controller\nVendor:\tIntel Corporation\nDevice:\tUSB 3.0\n\n" +
		"Slot:\t0000:00:18.0\nClass:\tNon-Essential Instrumentation\nVendor:\tAdvanced Micro Devices, Inc. [AMD]\nDevice:\tStarship/Matisse PCIe Dummy Function\n\n" +
		"Slot:\t0000:65:00.0\nClass:\tVGA compatible controller\nVendor:\tNVIDIA Corporation\nDevice:\tH100\n\n"

	devs := parseLspci(input)
	if len(devs) != 1 {
		t.Fatalf("expected 1 filtered device, got %d", len(devs))
	}
	if devs[0].DeviceClass == nil || *devs[0].DeviceClass != "VideoController" {
		t.Fatalf("unexpected remaining class: %v", devs[0].DeviceClass)
	}
	if devs[0].Slot == nil || *devs[0].Slot != "0000:65:00.0" {
		t.Fatalf("slot: got %v", devs[0].Slot)
	}
	if devs[0].BDF == nil || *devs[0].BDF != "0000:65:00.0" {
		t.Fatalf("bdf: got %v", devs[0].BDF)
	}
}

func TestParseLspci_filtersAMDChipsetNoise(t *testing.T) {
	input := "" +
		"Slot:\t0000:1a:00.0\nClass:\tNon-Essential Instrumentation\nVendor:\tAdvanced Micro Devices, Inc. [AMD]\nDevice:\tStarship/Matisse PCIe Dummy Function\n\n" +
		"Slot:\t0000:1a:00.2\nClass:\tEncryption controller\nVendor:\tAdvanced Micro Devices, Inc. [AMD]\nDevice:\tStarship/Matisse PTDMA\n\n" +
		"Slot:\t0000:05:00.0\nClass:\tEthernet controller\nVendor:\tMellanox Technologies\nDevice:\tMT28908 Family [ConnectX-6]\n\n"

	devs := parseLspci(input)
	if len(devs) != 1 {
		t.Fatalf("expected 1 remaining device, got %d", len(devs))
	}
	if devs[0].Model == nil || *devs[0].Model != "MT28908 Family [ConnectX-6]" {
		t.Fatalf("unexpected remaining device: %+v", devs[0])
	}
}

func TestPCIeJSONUsesSlotNotBDF(t *testing.T) {
	input := "Slot:\t0000:65:00.0\nClass:\tVGA compatible controller\nVendor:\tNVIDIA Corporation\nDevice:\tH100\n\n"

	devs := parseLspci(input)
	data, err := json.Marshal(devs[0])
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	text := string(data)
	if !strings.Contains(text, `"slot":"0000:65:00.0"`) {
		t.Fatalf("json missing slot: %s", text)
	}
	if strings.Contains(text, `"bdf"`) {
		t.Fatalf("json should not emit bdf: %s", text)
	}
}

func TestParseLspciUsesNUMANodeFieldWhenSysfsUnavailable(t *testing.T) {
	input := "Slot:\t0000:65:00.0\nClass:\tEthernet controller\nVendor:\tIntel Corporation\nDevice:\tX710\nNUMANode:\t1\n\n"

	devs := parseLspci(input)
	if len(devs) != 1 {
		t.Fatalf("expected 1 device, got %d", len(devs))
	}
	if devs[0].NUMANode == nil || *devs[0].NUMANode != 1 {
		t.Fatalf("numa_node=%v want 1", devs[0].NUMANode)
	}
}

func TestNormalizePCILinkSpeed(t *testing.T) {
	tests := []struct {
		raw  string
		want string
	}{
		{"2.5 GT/s PCIe", "Gen1"},
		{"5.0 GT/s PCIe", "Gen2"},
		{"8.0 GT/s PCIe", "Gen3"},
		{"16.0 GT/s PCIe", "Gen4"},
		{"32.0 GT/s PCIe", "Gen5"},
		{"64.0 GT/s PCIe", "Gen6"},
		{"unknown", ""},
	}
	for _, tt := range tests {
		if got := normalizePCILinkSpeed(tt.raw); got != tt.want {
			t.Fatalf("normalizePCILinkSpeed(%q)=%q want %q", tt.raw, got, tt.want)
		}
	}
}
