package collector

import (
	"bee/audit/internal/schema"
	"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: "co-processor", class: "Co-processor", 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: "ibmc vga", class: "VGA compatible controller", vendor: "Huawei Technologies Co., Ltd.", device: "Hi171x Series [iBMC Intelligent Management system chip w/VGA support]", want: false},
		{name: "aspeed vga", class: "VGA compatible controller", vendor: "ASPEED Technology, Inc.", device: "ASPEED Graphics Family", want: false},
		{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 TestParseLspci_filtersCoProcessors(t *testing.T) {
	input := "" +
		"Slot:\t0000:01:00.0\nClass:\tCo-processor\nVendor:\tIntel Corporation\nDevice:\t402xx Series QAT\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 remaining device, got %d", len(devs))
	}
	if devs[0].Model == nil || *devs[0].Model != "H100" {
		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)
		}
	}
}

func TestApplyPCIeLinkSpeedWarning(t *testing.T) {
	ptr := func(s string) *string { return &s }

	tests := []struct {
		name        string
		linkSpeed   *string
		maxSpeed    *string
		wantWarning bool
		wantGenIn   string // substring expected in ErrorDescription when warning
	}{
		{
			name:        "degraded Gen1 vs Gen5",
			linkSpeed:   ptr("Gen1"),
			maxSpeed:    ptr("Gen5"),
			wantWarning: true,
			wantGenIn:   "Gen1",
		},
		{
			name:        "at max Gen5",
			linkSpeed:   ptr("Gen5"),
			maxSpeed:    ptr("Gen5"),
			wantWarning: false,
		},
		{
			name:        "degraded Gen4 vs Gen5",
			linkSpeed:   ptr("Gen4"),
			maxSpeed:    ptr("Gen5"),
			wantWarning: true,
			wantGenIn:   "Gen4",
		},
		{
			name:        "missing current speed — no warning",
			linkSpeed:   nil,
			maxSpeed:    ptr("Gen5"),
			wantWarning: false,
		},
		{
			name:        "missing max speed — no warning",
			linkSpeed:   ptr("Gen1"),
			maxSpeed:    nil,
			wantWarning: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			dev := schema.HardwarePCIeDevice{}
			ok := statusOK
			dev.Status = &ok
			dev.LinkSpeed = tt.linkSpeed
			dev.MaxLinkSpeed = tt.maxSpeed

			applyPCIeLinkSpeedWarning(&dev)

			gotWarn := dev.Status != nil && *dev.Status == statusWarning
			if gotWarn != tt.wantWarning {
				t.Fatalf("wantWarning=%v gotWarning=%v (status=%v)", tt.wantWarning, gotWarn, dev.Status)
			}
			if tt.wantWarning {
				if dev.ErrorDescription == nil {
					t.Fatal("expected ErrorDescription to be set")
				}
				if !strings.Contains(*dev.ErrorDescription, tt.wantGenIn) {
					t.Fatalf("ErrorDescription %q does not contain %q", *dev.ErrorDescription, tt.wantGenIn)
				}
			} else {
				if dev.ErrorDescription != nil {
					t.Fatalf("unexpected ErrorDescription: %s", *dev.ErrorDescription)
				}
			}
		})
	}
}
