package collector

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"git.mchus.pro/mchus/logpile/internal/models"
)

func TestRedfishConnectorCollect(t *testing.T) {
	mux := http.NewServeMux()
	register := func(path string, payload interface{}) {
		mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			_ = json.NewEncoder(w).Encode(payload)
		})
	}

	register("/redfish/v1", map[string]interface{}{"Name": "ServiceRoot"})
	register("/redfish/v1/Systems/1", map[string]interface{}{
		"Manufacturer": "Supermicro",
		"Model":        "SYS-TEST",
		"SerialNumber": "SYS123",
		"BiosVersion":  "2.1a",
	})
	register("/redfish/v1/Systems/1/Bios", map[string]interface{}{"Version": "2.1a"})
	register("/redfish/v1/Systems/1/SecureBoot", map[string]interface{}{"SecureBootCurrentBoot": "Enabled"})
	register("/redfish/v1/Systems/1/Processors", map[string]interface{}{
		"Members": []map[string]string{
			{"@odata.id": "/redfish/v1/Systems/1/Processors/CPU1"},
		},
	})
	register("/redfish/v1/Systems/1/Processors/CPU1", map[string]interface{}{
		"Name":         "CPU1",
		"Model":        "Xeon Gold",
		"TotalCores":   32,
		"TotalThreads": 64,
		"MaxSpeedMHz":  3600,
	})
	register("/redfish/v1/Systems/1/Memory", map[string]interface{}{
		"Members": []map[string]string{
			{"@odata.id": "/redfish/v1/Systems/1/Memory/DIMM1"},
		},
	})
	register("/redfish/v1/Systems/1/Memory/DIMM1", map[string]interface{}{
		"Name":              "DIMM A1",
		"CapacityMiB":       32768,
		"MemoryDeviceType":  "DDR5",
		"OperatingSpeedMhz": 4800,
		"Status": map[string]interface{}{
			"Health": "OK",
		},
	})
	register("/redfish/v1/Systems/1/Storage", map[string]interface{}{
		"Members": []map[string]string{
			{"@odata.id": "/redfish/v1/Systems/1/Storage/1"},
		},
	})
	register("/redfish/v1/Systems/1/Storage/1", map[string]interface{}{
		"Drives": []map[string]string{
			{"@odata.id": "/redfish/v1/Systems/1/Storage/1/Drives/1"},
		},
	})
	register("/redfish/v1/Systems/1/Storage/1/Drives/1", map[string]interface{}{
		"Name":         "Drive1",
		"Model":        "NVMe Test",
		"MediaType":    "SSD",
		"Protocol":     "NVMe",
		"CapacityGB":   960,
		"SerialNumber": "SN123",
	})
	register("/redfish/v1/Systems/1/PCIeDevices", map[string]interface{}{
		"Members": []map[string]string{
			{"@odata.id": "/redfish/v1/Systems/1/PCIeDevices/GPU1"},
		},
	})
	register("/redfish/v1/Systems/1/PCIeDevices/GPU1", map[string]interface{}{
		"Id":           "GPU1",
		"Name":         "NVIDIA H100",
		"Model":        "NVIDIA H100 PCIe",
		"Manufacturer": "NVIDIA",
		"SerialNumber": "GPU-SN-001",
		"PCIeFunctions": map[string]interface{}{
			"@odata.id": "/redfish/v1/Systems/1/PCIeDevices/GPU1/PCIeFunctions",
		},
	})
	register("/redfish/v1/Systems/1/PCIeDevices/GPU1/PCIeFunctions", map[string]interface{}{
		"Members": []map[string]string{
			{"@odata.id": "/redfish/v1/Systems/1/PCIeFunctions/GPU1F0"},
		},
	})
	register("/redfish/v1/Systems/1/PCIeFunctions/GPU1F0", map[string]interface{}{
		"FunctionId":       "0000:65:00.0",
		"VendorId":         "0x10DE",
		"DeviceId":         "0x2331",
		"ClassCode":        "0x030200",
		"CurrentLinkWidth": 16,
		"CurrentLinkSpeed": "16.0 GT/s",
		"MaxLinkWidth":     16,
		"MaxLinkSpeed":     "16.0 GT/s",
	})
	register("/redfish/v1/Chassis/1/NetworkAdapters", map[string]interface{}{
		"Members": []map[string]string{
			{"@odata.id": "/redfish/v1/Chassis/1/NetworkAdapters/1"},
		},
	})
	register("/redfish/v1/Chassis/1/Power", map[string]interface{}{
		"PowerSupplies": []map[string]interface{}{
			{
				"MemberId":             "PSU1",
				"Name":                 "PSU Slot 1",
				"Model":                "PWS-2K01A-1R",
				"Manufacturer":         "Delta",
				"PowerCapacityWatts":   2000,
				"PowerInputWatts":      1600,
				"LastPowerOutputWatts": 1200,
				"LineInputVoltage":     230,
				"Status": map[string]interface{}{
					"Health": "OK",
					"State":  "Enabled",
				},
			},
		},
	})
	register("/redfish/v1/Chassis/1/NetworkAdapters/1", map[string]interface{}{
		"Name":         "Mellanox",
		"Model":        "ConnectX-6",
		"SerialNumber": "NIC123",
	})
	register("/redfish/v1/Managers/1", map[string]interface{}{
		"FirmwareVersion": "1.25",
	})
	register("/redfish/v1/Managers/1/NetworkProtocol", map[string]interface{}{
		"Id": "NetworkProtocol",
	})

	ts := httptest.NewServer(mux)
	defer ts.Close()

	c := NewRedfishConnector()
	result, err := c.Collect(context.Background(), Request{
		Host:     ts.URL,
		Port:     443,
		Protocol: "redfish",
		Username: "admin",
		AuthType: "password",
		Password: "secret",
		TLSMode:  "strict",
	}, nil)
	if err != nil {
		t.Fatalf("collect failed: %v", err)
	}

	if result.Hardware == nil {
		t.Fatalf("expected hardware config")
	}
	if result.Hardware.BoardInfo.ProductName != "SYS-TEST" {
		t.Fatalf("unexpected board model: %q", result.Hardware.BoardInfo.ProductName)
	}
	if len(result.Hardware.CPUs) != 1 {
		t.Fatalf("expected one CPU, got %d", len(result.Hardware.CPUs))
	}
	if len(result.Hardware.Memory) != 1 {
		t.Fatalf("expected one DIMM, got %d", len(result.Hardware.Memory))
	}
	if len(result.Hardware.Storage) != 1 {
		t.Fatalf("expected one drive, got %d", len(result.Hardware.Storage))
	}
	if len(result.Hardware.NetworkAdapters) != 1 {
		t.Fatalf("expected one nic, got %d", len(result.Hardware.NetworkAdapters))
	}
	if len(result.Hardware.GPUs) != 1 {
		t.Fatalf("expected one gpu, got %d", len(result.Hardware.GPUs))
	}
	if result.Hardware.GPUs[0].BDF != "0000:65:00.0" {
		t.Fatalf("unexpected gpu BDF: %q", result.Hardware.GPUs[0].BDF)
	}
	if len(result.Hardware.PCIeDevices) != 1 {
		t.Fatalf("expected one pcie device, got %d", len(result.Hardware.PCIeDevices))
	}
	if len(result.Hardware.PowerSupply) != 1 {
		t.Fatalf("expected one psu, got %d", len(result.Hardware.PowerSupply))
	}
	if result.Hardware.PowerSupply[0].WattageW != 2000 {
		t.Fatalf("unexpected psu wattage: %d", result.Hardware.PowerSupply[0].WattageW)
	}
	if len(result.Hardware.Firmware) == 0 {
		t.Fatalf("expected firmware entries")
	}
	if result.RawPayloads == nil {
		t.Fatalf("expected raw payloads")
	}
	treeAny, ok := result.RawPayloads["redfish_tree"]
	if !ok {
		t.Fatalf("expected redfish_tree in raw payloads")
	}
	tree, ok := treeAny.(map[string]interface{})
	if !ok || len(tree) == 0 {
		t.Fatalf("expected non-empty redfish_tree, got %#v", treeAny)
	}
}

func TestParsePCIeDeviceSlot_FromNestedRedfishSlotLocation(t *testing.T) {
	doc := map[string]interface{}{
		"Id": "NIC1",
		"Slot": map[string]interface{}{
			"Lanes": 16,
			"Location": map[string]interface{}{
				"PartLocation": map[string]interface{}{
					"LocationOrdinalValue": 1,
					"LocationType":         "Slot",
					"ServiceLabel":         "PCIe Slot 1 (1)",
				},
			},
			"PCIeType": "Gen5",
			"SlotType": "FullLength",
		},
	}

	got := parsePCIeDevice(doc, nil)
	if got.Slot != "PCIe Slot 1 (1)" {
		t.Fatalf("unexpected slot: %q", got.Slot)
	}
}

func TestParsePCIeDeviceSlot_EmptyMapFallsBackToID(t *testing.T) {
	doc := map[string]interface{}{
		"Id":   "NIC42",
		"Slot": map[string]interface{}{},
	}

	got := parsePCIeDevice(doc, nil)
	if got.Slot != "NIC42" {
		t.Fatalf("unexpected slot fallback: %q", got.Slot)
	}
	if got.Slot == "map[]" {
		t.Fatalf("slot should not stringify empty map")
	}
}

func TestReplayRedfishFromRawPayloads_FallbackCollectionMembersByPrefix(t *testing.T) {
	raw := map[string]any{
		"redfish_tree": map[string]interface{}{
			"/redfish/v1": map[string]interface{}{
				"Systems":  map[string]interface{}{"@odata.id": "/redfish/v1/Systems"},
				"Chassis":  map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"},
				"Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"},
			},
			"/redfish/v1/Systems": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"},
				},
			},
			"/redfish/v1/Systems/1": map[string]interface{}{
				"Manufacturer": "Supermicro",
				"Model":        "SYS-TEST",
				"SerialNumber": "SYS123",
			},
			// Intentionally missing /redfish/v1/Systems/1/Processors collection.
			"/redfish/v1/Systems/1/Processors/CPU1": map[string]interface{}{
				"Id":           "CPU1",
				"Model":        "Xeon Gold",
				"TotalCores":   32,
				"TotalThreads": 64,
			},
			"/redfish/v1/Chassis": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"},
				},
			},
			"/redfish/v1/Chassis/1": map[string]interface{}{
				"Id": "1",
			},
			"/redfish/v1/Managers": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"},
				},
			},
			"/redfish/v1/Managers/1": map[string]interface{}{
				"Id": "1",
			},
		},
		"redfish_fetch_errors": []map[string]interface{}{
			{"path": "/redfish/v1/Systems/1/Processors", "error": "status 500"},
		},
	}

	got, err := ReplayRedfishFromRawPayloads(raw, nil)
	if err != nil {
		t.Fatalf("replay failed: %v", err)
	}
	if got.Hardware == nil {
		t.Fatalf("expected hardware")
	}
	if len(got.Hardware.CPUs) != 1 {
		t.Fatalf("expected one CPU via prefix fallback, got %d", len(got.Hardware.CPUs))
	}
	if _, ok := got.RawPayloads["redfish_fetch_errors"]; !ok {
		t.Fatalf("expected raw payloads to preserve redfish_fetch_errors")
	}
}

func TestReplayRedfishFromRawPayloads_ParsesInlineThresholdAndDiscreteSensors(t *testing.T) {
	raw := map[string]any{
		"redfish_tree": map[string]interface{}{
			"/redfish/v1": map[string]interface{}{
				"Systems":  map[string]interface{}{"@odata.id": "/redfish/v1/Systems"},
				"Chassis":  map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"},
				"Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"},
			},
			"/redfish/v1/Systems": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"},
				},
			},
			"/redfish/v1/Systems/1": map[string]interface{}{
				"Id":           "1",
				"Manufacturer": "Inspur",
				"Model":        "NF5688M7",
				"SerialNumber": "23E100051",
			},
			"/redfish/v1/Chassis": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"},
				},
			},
			"/redfish/v1/Chassis/1": map[string]interface{}{
				"Id": "1",
			},
			"/redfish/v1/Managers": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"},
				},
			},
			"/redfish/v1/Managers/1": map[string]interface{}{
				"Id": "1",
			},
			"/redfish/v1/Chassis/1/ThresholdSensors": map[string]interface{}{
				"Sensors": []interface{}{
					map[string]interface{}{
						"Name":         "Inlet_Temp",
						"Reading":      16,
						"ReadingUnits": "deg_c",
						"State":        "Enabled",
					},
				},
			},
			"/redfish/v1/Chassis/1/DiscreteSensors": map[string]interface{}{
				"Sensors": []interface{}{
					map[string]interface{}{
						"Name":  "PSU_Redundant",
						"State": "Disabled",
					},
				},
			},
		},
	}

	got, err := ReplayRedfishFromRawPayloads(raw, nil)
	if err != nil {
		t.Fatalf("replay failed: %v", err)
	}
	if len(got.Sensors) == 0 {
		t.Fatalf("expected sensors from inline ThresholdSensors")
	}
	foundSensor := false
	for _, s := range got.Sensors {
		if s.Name == "Inlet_Temp" {
			foundSensor = true
			break
		}
	}
	if !foundSensor {
		t.Fatalf("expected Inlet_Temp sensor in replay output")
	}
	foundEvent := false
	for _, ev := range got.Events {
		if ev.EventType == "Discrete Sensor Status" && ev.SensorName == "PSU_Redundant" {
			foundEvent = true
			break
		}
	}
	if !foundEvent {
		t.Fatalf("expected discrete sensor warning event from inline DiscreteSensors")
	}
}

func TestReplayRedfishFromRawPayloads_CollectsThermalAndPowerSensors(t *testing.T) {
	raw := map[string]any{
		"redfish_tree": map[string]interface{}{
			"/redfish/v1": map[string]interface{}{
				"Systems":  map[string]interface{}{"@odata.id": "/redfish/v1/Systems"},
				"Chassis":  map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"},
				"Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"},
			},
			"/redfish/v1/Systems": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"},
				},
			},
			"/redfish/v1/Systems/1": map[string]interface{}{
				"Id":           "1",
				"Manufacturer": "Inspur",
				"Model":        "NF5688M7",
				"SerialNumber": "23E100051",
			},
			"/redfish/v1/Chassis": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"},
				},
			},
			"/redfish/v1/Chassis/1": map[string]interface{}{
				"Id": "1",
			},
			"/redfish/v1/Managers": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"},
				},
			},
			"/redfish/v1/Managers/1": map[string]interface{}{
				"Id": "1",
			},
			"/redfish/v1/Chassis/1/Thermal": map[string]interface{}{
				"Fans": []interface{}{
					map[string]interface{}{
						"Name":         "FAN0_F_Speed",
						"Reading":      9279,
						"ReadingUnits": "RPM",
						"Status": map[string]interface{}{
							"Health": "OK",
							"State":  "Enabled",
						},
					},
				},
				"Temperatures": []interface{}{
					map[string]interface{}{
						"Name":           "CPU0_Temp",
						"ReadingCelsius": 44,
						"Status": map[string]interface{}{
							"Health": "OK",
							"State":  "Enabled",
						},
					},
				},
			},
			"/redfish/v1/Chassis/1/Power": map[string]interface{}{
				"Oem": map[string]interface{}{
					"Public": map[string]interface{}{
						"TotalPower":              1836,
						"CurrentCPUPowerWatts":    304,
						"CurrentMemoryPowerWatts": 75,
						"CurrentFANPowerWatts":    180,
					},
				},
				"PowerControl": []interface{}{
					map[string]interface{}{
						"Name":               "System Power Control 1",
						"PowerConsumedWatts": 1836,
						"Status": map[string]interface{}{
							"Health": "OK",
							"State":  "Enabled",
						},
					},
				},
				"PowerSupplies": []interface{}{
					map[string]interface{}{
						"Name":                 "Power Supply 1",
						"PowerInputWatts":      180,
						"LastPowerOutputWatts": 155,
						"LineInputVoltage":     223.25,
						"Status": map[string]interface{}{
							"Health": "OK",
							"State":  "Enabled",
						},
					},
				},
			},
		},
	}

	got, err := ReplayRedfishFromRawPayloads(raw, nil)
	if err != nil {
		t.Fatalf("replay failed: %v", err)
	}
	if len(got.Sensors) == 0 {
		t.Fatalf("expected non-empty sensors")
	}
	expected := map[string]bool{
		"FAN0_F_Speed":                    false,
		"CPU0_Temp":                       false,
		"Total_Power":                     false,
		"System Power Control 1_Consumed": false,
		"Power Supply 1_InputPower":       false,
	}
	for _, s := range got.Sensors {
		if _, ok := expected[s.Name]; ok {
			expected[s.Name] = true
		}
	}
	for name, found := range expected {
		if !found {
			t.Fatalf("expected sensor %q in replay output", name)
		}
	}
}

func TestEnrichNICFromPCIeFunctions(t *testing.T) {
	nic := parseNIC(map[string]interface{}{
		"Id":           "1",
		"Model":        "MCX75310AAS-NEAT",
		"Manufacturer": "Supermicro",
		"SerialNumber": "NIC-SN-1",
		"Controllers": []interface{}{
			map[string]interface{}{
				"Links": map[string]interface{}{
					"PCIeDevices": []interface{}{
						map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/NIC1"},
					},
				},
				"Location": map[string]interface{}{
					"PartLocation": map[string]interface{}{"ServiceLabel": "PCIe Slot 1 (1)"},
				},
			},
		},
	})

	pcieDoc := map[string]interface{}{
		"Id": "NIC1",
		"PCIeFunctions": map[string]interface{}{
			"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/NIC1/PCIeFunctions",
		},
	}
	functionDocs := []map[string]interface{}{
		{
			"VendorId": "0x15b3",
			"DeviceId": "0x1021",
		},
	}

	enrichNICFromPCIe(&nic, pcieDoc, functionDocs)
	if nic.VendorID != 0x15b3 || nic.DeviceID != 0x1021 {
		t.Fatalf("unexpected NIC IDs: vendor=%#x device=%#x", nic.VendorID, nic.DeviceID)
	}
	if nic.Location != "PCIe Slot 1 (1)" {
		t.Fatalf("unexpected NIC location: %q", nic.Location)
	}
}

func TestParseNIC_PortCountFromControllerCapabilities(t *testing.T) {
	nic := parseNIC(map[string]interface{}{
		"Id": "1",
		"Controllers": []interface{}{
			map[string]interface{}{
				"ControllerCapabilities": map[string]interface{}{
					"NetworkPortCount": 2,
				},
			},
		},
	})

	if nic.PortCount != 2 {
		t.Fatalf("expected port_count=2, got %d", nic.PortCount)
	}
}

func TestParseNIC_DropsUnrealisticPortCount(t *testing.T) {
	nic := parseNIC(map[string]interface{}{
		"Id": "1",
		"Controllers": []interface{}{
			map[string]interface{}{
				"ControllerCapabilities": map[string]interface{}{
					"NetworkPortCount": 825307750,
				},
			},
		},
	})

	if nic.PortCount != 0 {
		t.Fatalf("expected unrealistic port count to be dropped, got %d", nic.PortCount)
	}
}

func TestParsePCIeDevice_PrefersFunctionClassOverDeviceType(t *testing.T) {
	doc := map[string]interface{}{
		"Id":         "NIC1",
		"DeviceType": "SingleFunction",
		"Model":      "MCX75310AAS-NEAT",
		"PartNumber": "MCX75310AAS-NEAT",
	}
	functionDocs := []map[string]interface{}{
		{
			"DeviceClass": "NetworkController",
			"VendorId":    "0x15b3",
			"DeviceId":    "0x1021",
		},
	}

	got := parsePCIeDevice(doc, functionDocs)
	if got.DeviceClass == "SingleFunction" {
		t.Fatalf("device class should not keep generic redfish DeviceType")
	}
	if got.DeviceClass == "" {
		t.Fatalf("device class should be resolved")
	}
}

func TestParseComponents_UseNestedSerialNumberFallback(t *testing.T) {
	doc := map[string]interface{}{
		"Name":         "dev0",
		"Id":           "dev0",
		"Model":        "model0",
		"Manufacturer": "vendor0",
		"SerialNumber": "N/A",
		"Oem": map[string]interface{}{
			"SerialNumber": "SN-OK-001",
		},
	}

	cpus := parseCPUs([]map[string]interface{}{doc})
	if len(cpus) != 1 || cpus[0].SerialNumber != "SN-OK-001" {
		t.Fatalf("expected CPU serial fallback, got %+v", cpus)
	}

	dimms := parseMemory([]map[string]interface{}{doc})
	if len(dimms) != 1 || dimms[0].SerialNumber != "SN-OK-001" {
		t.Fatalf("expected DIMM serial fallback, got %+v", dimms)
	}

	drive := parseDrive(doc)
	if drive.SerialNumber != "SN-OK-001" {
		t.Fatalf("expected drive serial fallback, got %q", drive.SerialNumber)
	}

	nic := parseNIC(doc)
	if nic.SerialNumber != "SN-OK-001" {
		t.Fatalf("expected NIC serial fallback, got %q", nic.SerialNumber)
	}

	psu := parsePSU(doc, 1)
	if psu.SerialNumber != "SN-OK-001" {
		t.Fatalf("expected PSU serial fallback, got %q", psu.SerialNumber)
	}

	pcie := parsePCIeDevice(doc, nil)
	if pcie.SerialNumber != "SN-OK-001" {
		t.Fatalf("expected PCIe device serial fallback, got %q", pcie.SerialNumber)
	}

	pcieFn := parsePCIeFunction(doc, 1)
	if pcieFn.SerialNumber != "SN-OK-001" {
		t.Fatalf("expected PCIe function serial fallback, got %q", pcieFn.SerialNumber)
	}
}

func TestRedfishCollectionMemberRefs_IncludesOemPublicMembers(t *testing.T) {
	collection := map[string]interface{}{
		"Members": []interface{}{
			map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/Drives/OB01"},
		},
		"Oem": map[string]interface{}{
			"Public": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/Drives/FP00HDD00"},
					map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/Drives/FP00HDD02"},
				},
			},
		},
	}

	got := redfishCollectionMemberRefs(collection)
	if len(got) != 3 {
		t.Fatalf("expected 3 member refs, got %d: %v", len(got), got)
	}
}

func TestRecoverCriticalRedfishDocsPlanB_RetriesMembersFromExistingCollection(t *testing.T) {
	t.Setenv("LOGPILE_REDFISH_CRITICAL_COOLDOWN", "0s")
	t.Setenv("LOGPILE_REDFISH_CRITICAL_SLOW_GAP", "0s")
	t.Setenv("LOGPILE_REDFISH_CRITICAL_PLANB_RETRIES", "1")
	t.Setenv("LOGPILE_REDFISH_CRITICAL_RETRIES", "1")
	t.Setenv("LOGPILE_REDFISH_CRITICAL_BACKOFF", "0s")

	const memberPath = "/redfish/v1/Chassis/1/Drives/FP00HDD00"
	mux := http.NewServeMux()
	mux.HandleFunc(memberPath, func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]interface{}{
			"Id":            "FP00HDD00",
			"Name":          "FP00HDD00",
			"Model":         "HDD-TEST",
			"MediaType":     "HDD",
			"Protocol":      "SAS",
			"CapacityBytes": int64(2000398934016),
			"SerialNumber":  "HDD-SN-001",
		})
	})
	ts := httptest.NewServer(mux)
	defer ts.Close()

	rawTree := map[string]interface{}{
		"/redfish/v1/Chassis/1/Drives": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/Drives/OB01"},
			},
			"Oem": map[string]interface{}{
				"Public": map[string]interface{}{
					"Members": []interface{}{
						map[string]interface{}{"@odata.id": memberPath},
					},
				},
			},
		},
	}
	fetchErrs := map[string]string{
		memberPath: "Get \"https://example/redfish/v1/Chassis/1/Drives/FP00HDD00\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)",
	}

	c := NewRedfishConnector()
	recovered := c.recoverCriticalRedfishDocsPlanB(
		context.Background(),
		ts.Client(),
		Request{},
		ts.URL,
		[]string{"/redfish/v1/Chassis/1/Drives"},
		rawTree,
		fetchErrs,
		nil,
	)
	if recovered == 0 {
		t.Fatalf("expected plan-B to recover at least one document")
	}
	if _, ok := rawTree[memberPath]; !ok {
		t.Fatalf("expected recovered member doc for %s", memberPath)
	}
	if _, ok := fetchErrs[memberPath]; ok {
		t.Fatalf("expected fetch error for %s to be cleared after recovery", memberPath)
	}
}

func TestRecoverCriticalRedfishDocsPlanB_RetriesMembersFromSystemMemoryCollection(t *testing.T) {
	t.Setenv("LOGPILE_REDFISH_CRITICAL_COOLDOWN", "0s")
	t.Setenv("LOGPILE_REDFISH_CRITICAL_SLOW_GAP", "0s")
	t.Setenv("LOGPILE_REDFISH_CRITICAL_PLANB_RETRIES", "1")
	t.Setenv("LOGPILE_REDFISH_CRITICAL_RETRIES", "1")
	t.Setenv("LOGPILE_REDFISH_CRITICAL_BACKOFF", "0s")

	const systemPath = "/redfish/v1/Systems/1"
	const memoryPath = "/redfish/v1/Systems/1/Memory"
	const dimmPath = "/redfish/v1/Systems/1/Memory/CPU1_C1D1"

	mux := http.NewServeMux()
	mux.HandleFunc(dimmPath, func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]interface{}{
			"Id":               "CPU1_C1D1",
			"Name":             "CPU1_C1D1",
			"DeviceLocator":    "CPU1_C1D1",
			"CapacityMiB":      65536,
			"MemoryDeviceType": "DDR5",
			"Status":           map[string]interface{}{"State": "Enabled", "Health": "OK"},
			"SerialNumber":     "DIMM-SN-001",
			"PartNumber":       "DIMM-PN-001",
		})
	})
	ts := httptest.NewServer(mux)
	defer ts.Close()

	rawTree := map[string]interface{}{
		memoryPath: map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": dimmPath},
			},
		},
	}
	fetchErrs := map[string]string{
		dimmPath: `Get "https://example/redfish/v1/Systems/1/Memory/CPU1_C1D1": context deadline exceeded (Client.Timeout exceeded while awaiting headers)`,
	}

	criticalPaths := redfishCriticalEndpoints([]string{systemPath}, nil, nil)
	hasMemoryPath := false
	for _, p := range criticalPaths {
		if p == memoryPath {
			hasMemoryPath = true
			break
		}
	}
	if !hasMemoryPath {
		t.Fatalf("expected critical endpoints to include %s", memoryPath)
	}

	c := NewRedfishConnector()
	recovered := c.recoverCriticalRedfishDocsPlanB(
		context.Background(),
		ts.Client(),
		Request{},
		ts.URL,
		criticalPaths,
		rawTree,
		fetchErrs,
		nil,
	)
	if recovered == 0 {
		t.Fatalf("expected plan-B to recover at least one DIMM document")
	}
	if _, ok := rawTree[dimmPath]; !ok {
		t.Fatalf("expected recovered DIMM doc for %s", dimmPath)
	}
	if _, ok := fetchErrs[dimmPath]; ok {
		t.Fatalf("expected DIMM fetch error for %s to be cleared", dimmPath)
	}
}

func TestReplayCollectStorage_ProbesSupermicroNVMeDiskBayWhenCollectionEmpty(t *testing.T) {
	r := redfishSnapshotReader{tree: map[string]interface{}{
		"/redfish/v1/Systems": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"},
			},
		},
		"/redfish/v1/Systems/1/Storage": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Storage/NVMeSSD"},
			},
		},
		"/redfish/v1/Systems/1/Storage/NVMeSSD": map[string]interface{}{
			"Drives": map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Storage/NVMeSSD/Drives"},
		},
		"/redfish/v1/Systems/1/Storage/NVMeSSD/Drives": map[string]interface{}{
			"Members": []interface{}{},
		},
		"/redfish/v1/Chassis": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane"},
			},
		},
		"/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane": map[string]interface{}{
			"Drives": map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane/Drives"},
		},
		"/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane/Drives": map[string]interface{}{
			"Members@odata.count": 0,
			"Members":             []interface{}{},
		},
		"/redfish/v1/Chassis/NVMeSSD.0.Group.0.StorageBackplane/Drives/Disk.Bay.0": map[string]interface{}{
			"Id":            "Disk.Bay.0",
			"Name":          "Disk.Bay.0",
			"Manufacturer":  "INTEL",
			"SerialNumber":  "BTLJ035203XT1P0FGN",
			"Model":         "INTEL SSDPE2KX010T8",
			"CapacityBytes": int64(1000204886016),
			"Protocol":      "NVMe",
			"MediaType":     "SSD",
			"Status":        map[string]interface{}{"State": "Enabled", "Health": "OK"},
		},
	}}

	got := r.collectStorage("/redfish/v1/Systems/1")
	if len(got) != 1 {
		t.Fatalf("expected one drive from direct Disk.Bay probe, got %d", len(got))
	}
	if got[0].SerialNumber != "BTLJ035203XT1P0FGN" {
		t.Fatalf("unexpected serial: %q", got[0].SerialNumber)
	}
	if got[0].SizeGB == 0 {
		t.Fatalf("expected size to be parsed from CapacityBytes")
	}
}

func TestReplayCollectGPUs_DoesNotCollapseOnPlaceholderSerialAndSkipsNIC(t *testing.T) {
	r := redfishSnapshotReader{tree: map[string]interface{}{
		"/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/3"},
				map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/9"},
				map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/7"},
			},
		},
		"/redfish/v1/Chassis/1/PCIeDevices/3": map[string]interface{}{
			"Id":           "3",
			"Name":         "PCIeCard3",
			"Model":        "H200-SXM5-141G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "N/A",
			"Oem": map[string]interface{}{
				"Public": map[string]interface{}{
					"DeviceClass": "DisplayController",
				},
			},
		},
		"/redfish/v1/Chassis/1/PCIeDevices/9": map[string]interface{}{
			"Id":           "9",
			"Name":         "PCIeCard9",
			"Model":        "H200-SXM5-141G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "N/A",
			"Oem": map[string]interface{}{
				"Public": map[string]interface{}{
					"DeviceClass": "DisplayController",
				},
			},
		},
		"/redfish/v1/Chassis/1/PCIeDevices/7": map[string]interface{}{
			"Id":           "7",
			"Name":         "PCIeCard7",
			"Model":        "MCX631102AN-ADAT",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "MT2538J00CZE",
			"Oem": map[string]interface{}{
				"Public": map[string]interface{}{
					"DeviceClass": "NetworkController",
				},
			},
		},
	}}

	got := r.collectGPUs(nil, []string{"/redfish/v1/Chassis/1"})
	if len(got) != 2 {
		t.Fatalf("expected 2 GPUs (two H200 cards), got %d", len(got))
	}
	for _, gpu := range got {
		if gpu.Model == "MCX631102AN-ADAT" {
			t.Fatalf("network adapter should not be classified as GPU")
		}
	}
}

func TestParseBoardInfo_NormalizesNullPlaceholders(t *testing.T) {
	got := parseBoardInfo(map[string]interface{}{
		"Manufacturer": "NULL",
		"Model":        "NULL",
		"SerialNumber": "23E100051",
		"PartNumber":   "0 ",
		"UUID":         "fa403f6f-2ee9-11f0-bab9-346f1104085a",
	})
	if got.Manufacturer != "" {
		t.Fatalf("expected empty manufacturer, got %q", got.Manufacturer)
	}
	if got.ProductName != "" {
		t.Fatalf("expected empty product name, got %q", got.ProductName)
	}
	if got.PartNumber != "" {
		t.Fatalf("expected empty part number, got %q", got.PartNumber)
	}
	if got.SerialNumber != "23E100051" {
		t.Fatalf("unexpected serial number: %q", got.SerialNumber)
	}
}

func TestShouldCrawlPath_SkipsJsonSchemas(t *testing.T) {
	if shouldCrawlPath("/redfish/v1/JsonSchemas") {
		t.Fatalf("expected /JsonSchemas to be skipped")
	}
	if shouldCrawlPath("/redfish/v1/JsonSchemas/ComputerSystem.v1_8_0") {
		t.Fatalf("expected JsonSchemas members to be skipped")
	}
	if !shouldCrawlPath("/redfish/v1/Systems/1") {
		t.Fatalf("expected normal hardware path to be crawled")
	}
}

func TestReplayCollectGPUs_FromGraphicsControllers(t *testing.T) {
	r := redfishSnapshotReader{tree: map[string]interface{}{
		"/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU0"},
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU1"},
			},
		},
		"/redfish/v1/Systems/1/GraphicsControllers/GPU0": map[string]interface{}{
			"Id":           "GPU0",
			"Name":         "GPU0",
			"Model":        "H200-SXM5-141G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "1654225094493",
			"Status":       map[string]interface{}{"State": "Enabled", "Health": "OK"},
		},
		"/redfish/v1/Systems/1/GraphicsControllers/GPU1": map[string]interface{}{
			"Id":           "GPU1",
			"Name":         "GPU1",
			"Model":        "H200-SXM5-141G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "1654425002635",
			"Status":       map[string]interface{}{"State": "Enabled", "Health": "OK"},
		},
	}}

	got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil)
	if len(got) != 2 {
		t.Fatalf("expected 2 GPUs from GraphicsControllers, got %d", len(got))
	}
	if got[0].SerialNumber == "" || got[1].SerialNumber == "" {
		t.Fatalf("expected GPU serial numbers from GraphicsControllers")
	}
}

func TestReplayCollectGPUs_DedupUsesRedfishPathBeforeHeuristics(t *testing.T) {
	r := redfishSnapshotReader{tree: map[string]interface{}{
		"/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU0"},
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU1"},
			},
		},
		"/redfish/v1/Systems/1/GraphicsControllers/GPU0": map[string]interface{}{
			"Id":           "GPU0",
			"Name":         "H100-PCIE-80G",
			"Model":        "H100-PCIE-80G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "N/A",
		},
		"/redfish/v1/Systems/1/GraphicsControllers/GPU1": map[string]interface{}{
			"Id":           "GPU1",
			"Name":         "H100-PCIE-80G",
			"Model":        "H100-PCIE-80G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "N/A",
		},
	}}

	got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil)
	if len(got) != 2 {
		t.Fatalf("expected both GPUs to be kept by unique redfish path, got %d", len(got))
	}
}

func TestParseGPU_UsesNestedOemSerialNumber(t *testing.T) {
	doc := map[string]interface{}{
		"Id":           "GPU4",
		"Name":         "H100-PCIE-80G",
		"Model":        "H100-PCIE-80G",
		"Manufacturer": "NVIDIA",
		"SerialNumber": "N/A",
		"Oem": map[string]interface{}{
			"SerialNumber": "1794024010533",
		},
	}

	got := parseGPU(doc, nil, 1)
	if got.SerialNumber != "1794024010533" {
		t.Fatalf("expected nested OEM serial number, got %q", got.SerialNumber)
	}
}

func TestParseBoardInfoWithFallback_UsesFRU(t *testing.T) {
	system := map[string]interface{}{
		"Manufacturer": "NULL",
		"Model":        "NULL",
		"SerialNumber": "23E100051",
		"PartNumber":   "0",
	}
	chassis := map[string]interface{}{
		"Manufacturer": "NULL",
		"Model":        "NULL",
	}
	fru := map[string]interface{}{
		"FRUInfo": map[string]interface{}{
			"Board": map[string]interface{}{
				"Manufacturer": "Kaytus",
				"ProductName":  "KR4268X2",
			},
		},
	}

	got := parseBoardInfoWithFallback(system, chassis, fru)
	if got.ProductName != "KR4268X2" {
		t.Fatalf("expected product from FRU, got %q", got.ProductName)
	}
	if got.Manufacturer != "Kaytus" {
		t.Fatalf("expected manufacturer from FRU, got %q", got.Manufacturer)
	}
	if got.SerialNumber != "23E100051" {
		t.Fatalf("expected serial from system, got %q", got.SerialNumber)
	}
}

func TestReplayRedfishFromRawPayloads_AddsMissingServerModelWarning(t *testing.T) {
	raw := map[string]any{
		"redfish_tree": map[string]interface{}{
			"/redfish/v1": map[string]interface{}{
				"Systems":  map[string]interface{}{"@odata.id": "/redfish/v1/Systems"},
				"Chassis":  map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"},
				"Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"},
			},
			"/redfish/v1/Systems": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"},
				},
			},
			"/redfish/v1/Systems/1": map[string]interface{}{
				"Manufacturer": "NULL",
				"Model":        "NULL",
				"SerialNumber": "23E100051",
			},
			"/redfish/v1/Chassis": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"},
				},
			},
			"/redfish/v1/Chassis/1": map[string]interface{}{
				"Id":           "1",
				"Manufacturer": "NULL",
				"Model":        "NULL",
			},
			"/redfish/v1/Managers": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"},
				},
			},
			"/redfish/v1/Managers/1": map[string]interface{}{
				"Id": "1",
			},
		},
		"redfish_fetch_errors": []map[string]interface{}{
			{"path": "/redfish/v1/Systems/1/Oem/Public/FRU", "error": "status 500"},
		},
	}

	got, err := ReplayRedfishFromRawPayloads(raw, nil)
	if err != nil {
		t.Fatalf("replay failed: %v", err)
	}
	if got.Hardware == nil {
		t.Fatalf("expected hardware")
	}
	if got.Hardware.BoardInfo.ProductName != "" {
		t.Fatalf("expected empty model for warning test, got %q", got.Hardware.BoardInfo.ProductName)
	}
	found := false
	for _, ev := range got.Events {
		if ev.Source == "Redfish" && ev.EventType == "Collection Warning" {
			found = true
			break
		}
	}
	if !found {
		t.Fatalf("expected collection warning event about missing server model")
	}
}

func TestReplayRedfishFromRawPayloads_AddsDriveFetchWarning(t *testing.T) {
	raw := map[string]any{
		"redfish_tree": map[string]interface{}{
			"/redfish/v1": map[string]interface{}{
				"Systems":  map[string]interface{}{"@odata.id": "/redfish/v1/Systems"},
				"Chassis":  map[string]interface{}{"@odata.id": "/redfish/v1/Chassis"},
				"Managers": map[string]interface{}{"@odata.id": "/redfish/v1/Managers"},
			},
			"/redfish/v1/Systems": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1"},
				},
			},
			"/redfish/v1/Systems/1": map[string]interface{}{
				"Manufacturer": "Inspur",
				"Model":        "NF5688M7",
				"SerialNumber": "23E100051",
			},
			"/redfish/v1/Chassis": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1"},
				},
			},
			"/redfish/v1/Chassis/1": map[string]interface{}{
				"Id":           "1",
				"Manufacturer": "Inspur",
				"Model":        "NF5688M7",
			},
			"/redfish/v1/Managers": map[string]interface{}{
				"Members": []interface{}{
					map[string]interface{}{"@odata.id": "/redfish/v1/Managers/1"},
				},
			},
			"/redfish/v1/Managers/1": map[string]interface{}{
				"Id": "1",
			},
		},
		"redfish_fetch_errors": []map[string]interface{}{
			{
				"path":  "/redfish/v1/Chassis/1/Drives/FP00HDD00",
				"error": `Get "...": context deadline exceeded (Client.Timeout exceeded while awaiting headers)`,
			},
		},
	}

	got, err := ReplayRedfishFromRawPayloads(raw, nil)
	if err != nil {
		t.Fatalf("replay failed: %v", err)
	}

	found := false
	for _, ev := range got.Events {
		if ev.Source == "Redfish" &&
			ev.EventType == "Collection Warning" &&
			strings.Contains(strings.ToLower(ev.Description), "drive documents were unavailable") {
			found = true
			break
		}
	}
	if !found {
		t.Fatalf("expected collection warning event for drive fetch errors")
	}
}

func TestReplayCollectGPUs_SkipsModelOnlyDuplicateFromGraphicsControllers(t *testing.T) {
	r := redfishSnapshotReader{tree: map[string]interface{}{
		"/redfish/v1/Systems/1/PCIeDevices": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/PCIeDevices/3"},
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/PCIeDevices/9"},
			},
		},
		"/redfish/v1/Systems/1/PCIeDevices/3": map[string]interface{}{
			"Id":           "3",
			"Name":         "PCIeCard3",
			"Model":        "H200-SXM5-141G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "1654225094493",
		},
		"/redfish/v1/Systems/1/PCIeDevices/9": map[string]interface{}{
			"Id":           "9",
			"Name":         "PCIeCard9",
			"Model":        "H200-SXM5-141G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "1654425002635",
		},
		"/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU0"},
			},
		},
		"/redfish/v1/Systems/1/GraphicsControllers/GPU0": map[string]interface{}{
			"Id":           "GPU0",
			"Name":         "H200-SXM5-141G",
			"Model":        "H200-SXM5-141G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "N/A",
		},
	}}

	got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, nil)
	if len(got) != 2 {
		t.Fatalf("expected 2 GPUs without generic duplicate, got %d", len(got))
	}
	for _, gpu := range got {
		if gpu.Slot == "H200-SXM5-141G" {
			t.Fatalf("unexpected model-only duplicate GPU row")
		}
	}
}

func TestApplyBoardInfoFallbackFromDocs_SkipsComponentProductNames(t *testing.T) {
	board := models.BoardInfo{
		SerialNumber: "23E100051",
	}
	docs := []map[string]interface{}{
		{
			"Model":        "DDR5 DIMM",
			"Manufacturer": "DELTA",
			"SerialNumber": "802C1A2507D284B001",
		},
		{
			"PlatformId":   "NF5688M7",
			"Manufacturer": "Inspur",
			"PartNumber":   "YZMB-00001",
		},
	}

	applyBoardInfoFallbackFromDocs(&board, docs)
	if board.ProductName != "NF5688M7" {
		t.Fatalf("expected server model from fallback docs, got %q", board.ProductName)
	}
	if board.Manufacturer != "Inspur" {
		t.Fatalf("expected manufacturer from server fallback doc, got %q", board.Manufacturer)
	}
}

func TestDedupeStorage_IgnoresPlaceholderSerial(t *testing.T) {
	in := []models.Storage{
		{Slot: "OB01", Model: "N/A", SerialNumber: "N/A"},
		{Slot: "OB02", Model: "N/A", SerialNumber: "N/A"},
		{Slot: "OB03", Model: "N/A", SerialNumber: "N/A"},
		{Slot: "OB04", Model: "N/A", SerialNumber: "N/A"},
	}

	out := dedupeStorage(in)
	if len(out) != 4 {
		t.Fatalf("expected all placeholder-serial NVMe drives to be kept by slot key, got %d", len(out))
	}
}

func TestDedupeStorage_MergesPlaceholderSlotsWithRichDrivesByOrder(t *testing.T) {
	in := []models.Storage{
		{Slot: "PCIe8_RAID_Disk_1:0", Type: "SSD", Model: "SOLIDIGM SSDSC2K", SizeGB: 1787, SerialNumber: "S1", Present: true},
		{Slot: "PCIe8_RAID_Disk_1:1", Type: "SSD", Model: "SOLIDIGM SSDSC2K", SizeGB: 1787, SerialNumber: "S2", Present: true},
		{Slot: "PCIe8_RAID_Disk_1:2", Type: "SSD", Model: "SOLIDIGM SSDSC2K", SizeGB: 1787, SerialNumber: "S3", Present: true},
		{Slot: "OB01", Type: "NVMe", Model: "N/A", SerialNumber: "N/A", Present: true},
		{Slot: "OB02", Type: "NVMe", Model: "N/A", SerialNumber: "N/A", Present: true},
		{Slot: "OB03", Type: "NVMe", Model: "N/A", SerialNumber: "N/A", Present: true},
		{Slot: "OB04", Type: "NVMe", Model: "N/A", SerialNumber: "N/A", Present: true},
		{Slot: "FP00HDD00", Type: "NVMe", Model: "INTEL SSDPE2KE032T8", SizeGB: 2980, SerialNumber: "N1", Present: true},
		{Slot: "FP00HDD02", Type: "NVMe", Model: "INTEL SSDPE2KE032T8", SizeGB: 2980, SerialNumber: "N2", Present: true},
		{Slot: "FP00HDD04", Type: "NVMe", Model: "INTEL SSDPE2KE032T8", SizeGB: 2980, SerialNumber: "N3", Present: true},
		{Slot: "FP00HDD06", Type: "NVMe", Model: "INTEL SSDPE2KE032T8", SizeGB: 2980, SerialNumber: "N4", Present: true},
	}

	out := dedupeStorage(in)
	if len(out) != 7 {
		t.Fatalf("expected 7 rows after placeholder merge, got %d", len(out))
	}

	bySlot := make(map[string]models.Storage, len(out))
	for _, d := range out {
		bySlot[d.Slot] = d
		if strings.HasPrefix(d.Slot, "FP00HDD") {
			t.Fatalf("expected FP donor slot %q to be absorbed by placeholder slot", d.Slot)
		}
	}
	if bySlot["OB01"].SerialNumber != "N1" || bySlot["OB02"].SerialNumber != "N2" || bySlot["OB03"].SerialNumber != "N3" || bySlot["OB04"].SerialNumber != "N4" {
		t.Fatalf("expected OB slots to be enriched in order, got OB01=%q OB02=%q OB03=%q OB04=%q",
			bySlot["OB01"].SerialNumber, bySlot["OB02"].SerialNumber, bySlot["OB03"].SerialNumber, bySlot["OB04"].SerialNumber)
	}
	if bySlot["OB01"].Model != "INTEL SSDPE2KE032T8" || bySlot["OB01"].SizeGB != 2980 {
		t.Fatalf("expected OB01 to inherit rich model/size, got model=%q size=%d", bySlot["OB01"].Model, bySlot["OB01"].SizeGB)
	}
}

func TestDedupeNetworkAdapters_MergesBySlotAndKeepsRicherData(t *testing.T) {
	in := []models.NetworkAdapter{
		{
			Slot:    "NIC-A",
			Model:   "N/A",
			Vendor:  "",
			Present: true,
		},
		{
			Slot:         "NIC-A",
			Model:        "ConnectX-7",
			Vendor:       "NVIDIA",
			SerialNumber: "NICSN001",
			Firmware:     "28.41.2020",
			PortCount:    2,
			MACAddresses: []string{"00:11:22:33:44:55"},
			Present:      true,
		},
	}

	out := dedupeNetworkAdapters(in)
	if len(out) != 1 {
		t.Fatalf("expected merged single NIC row, got %d", len(out))
	}
	if out[0].SerialNumber != "NICSN001" || out[0].Model != "ConnectX-7" || out[0].Vendor != "NVIDIA" {
		t.Fatalf("expected richer NIC fields preserved, got %+v", out[0])
	}
}

func TestDedupePCIeDevices_MergesByLooseKeyAndKeepsBDF(t *testing.T) {
	in := []models.PCIeDevice{
		{
			Slot:        "PCIe Slot 3",
			DeviceClass: "Network Controller",
			PartNumber:  "MCX75310AAS-NEAT",
		},
		{
			Slot:         "PCIe Slot 3",
			DeviceClass:  "Network Controller",
			PartNumber:   "MCX75310AAS-NEAT",
			BDF:          "0000:af:00.0",
			VendorID:     0x15b3,
			DeviceID:     0x1021,
			SerialNumber: "MT000123",
		},
	}

	out := dedupePCIeDevices(in)
	if len(out) != 1 {
		t.Fatalf("expected merged single PCIe row, got %d", len(out))
	}
	if out[0].BDF != "0000:af:00.0" || out[0].SerialNumber != "MT000123" || out[0].VendorID == 0 || out[0].DeviceID == 0 {
		t.Fatalf("expected richer PCIe fields preserved, got %+v", out[0])
	}
}

func TestAppendPSU_MergesRicherDuplicate(t *testing.T) {
	var out []models.PSU
	seen := make(map[string]int)
	idx := 1
	idx = appendPSU(&out, seen, models.PSU{
		Slot:    "PSU1",
		Model:   "N/A",
		Present: true,
	}, idx)
	_ = appendPSU(&out, seen, models.PSU{
		Slot:         "PSU1",
		Model:        "DLG2700BW54C31",
		SerialNumber: "DGPLV2515025L",
		WattageW:     2700,
		Firmware:     "00.01.04",
		Present:      true,
	}, idx)

	if len(out) != 1 {
		t.Fatalf("expected PSU duplicate merge, got %d rows", len(out))
	}
	if out[0].SerialNumber != "DGPLV2515025L" || out[0].WattageW != 2700 || out[0].Model != "DLG2700BW54C31" {
		t.Fatalf("expected richer PSU fields preserved, got %+v", out[0])
	}
}

func TestReplayCollectGPUs_DropsModelOnlyPlaceholderWhenConcreteDiscoveredLater(t *testing.T) {
	r := redfishSnapshotReader{tree: map[string]interface{}{
		"/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU0"},
			},
		},
		"/redfish/v1/Systems/1/GraphicsControllers/GPU0": map[string]interface{}{
			"Id":    "GPU0",
			"Name":  "H200-SXM5-141G",
			"Model": "H200-SXM5-141G",
		},
		"/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/4"},
			},
		},
		"/redfish/v1/Chassis/1/PCIeDevices/4": map[string]interface{}{
			"Id":           "4",
			"Name":         "PCIeCard4",
			"Model":        "H200-SXM5-141G",
			"Manufacturer": "NVIDIA",
			"BDF":          "0000:0f:00.0",
		},
	}}

	got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"})
	if len(got) != 1 {
		t.Fatalf("expected generic graphics placeholder to be dropped, got %d GPUs", len(got))
	}
	if got[0].Slot != "PCIeCard4" {
		t.Fatalf("expected concrete PCIe GPU to remain, got slot=%q", got[0].Slot)
	}
}

func TestReplayCollectGPUs_MergesGraphicsSerialIntoConcretePCIeGPU(t *testing.T) {
	r := redfishSnapshotReader{tree: map[string]interface{}{
		"/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/GraphicsControllers/GPU4"},
			},
		},
		"/redfish/v1/Systems/1/GraphicsControllers/GPU4": map[string]interface{}{
			"Id":           "4",
			"Name":         "H100-PCIE-80G",
			"Model":        "H100-PCIE-80G",
			"Manufacturer": "NVIDIA",
			"Oem": map[string]interface{}{
				"SerialNumber": "1794024010533",
			},
		},
		"/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/8"},
			},
		},
		"/redfish/v1/Chassis/1/PCIeDevices/8": map[string]interface{}{
			"Id":           "8",
			"Name":         "PCIeCard8",
			"Model":        "H100-PCIE-80G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": "N/A",
			"BDF":          "0000:b1:00.0",
		},
	}}

	got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"})
	if len(got) != 1 {
		t.Fatalf("expected merged single GPU row, got %d", len(got))
	}
	if got[0].Slot != "PCIeCard8" {
		t.Fatalf("expected concrete PCIe slot, got %q", got[0].Slot)
	}
	if got[0].SerialNumber != "1794024010533" {
		t.Fatalf("expected merged serial from graphics controller, got %q", got[0].SerialNumber)
	}
}

func TestReplayCollectGPUs_MergesAmbiguousSameModelByOrder(t *testing.T) {
	tree := map[string]interface{}{
		"/redfish/v1/Systems/1/GraphicsControllers": map[string]interface{}{
			"Members": []interface{}{},
		},
		"/redfish/v1/Chassis/1/PCIeDevices": map[string]interface{}{
			"Members": []interface{}{},
		},
	}

	pcieIDs := []int{4, 8, 12, 14, 20, 23, 26, 30}
	serials := []string{
		"1654425002361",
		"1654425004310",
		"1654425004204",
		"1654225097289",
		"1654225095717",
		"1654425002114",
		"1654425002714",
		"1654425002991",
	}
	for i := 0; i < len(pcieIDs); i++ {
		gpuPath := fmt.Sprintf("/redfish/v1/Systems/1/GraphicsControllers/GPU%d", i+1)
		pciePath := fmt.Sprintf("/redfish/v1/Chassis/1/PCIeDevices/%d", pcieIDs[i])
		tree["/redfish/v1/Systems/1/GraphicsControllers"].(map[string]interface{})["Members"] =
			append(tree["/redfish/v1/Systems/1/GraphicsControllers"].(map[string]interface{})["Members"].([]interface{}), map[string]interface{}{"@odata.id": gpuPath})
		tree["/redfish/v1/Chassis/1/PCIeDevices"].(map[string]interface{})["Members"] =
			append(tree["/redfish/v1/Chassis/1/PCIeDevices"].(map[string]interface{})["Members"].([]interface{}), map[string]interface{}{"@odata.id": pciePath})

		tree[gpuPath] = map[string]interface{}{
			"Id":           fmt.Sprintf("GPU%d", i+1),
			"Name":         "H200-SXM5-141G",
			"Model":        "H200-SXM5-141G",
			"Manufacturer": "NVIDIA",
			"SerialNumber": serials[i],
		}
		tree[pciePath] = map[string]interface{}{
			"Id":           fmt.Sprintf("%d", pcieIDs[i]),
			"Name":         fmt.Sprintf("PCIeCard%d", pcieIDs[i]),
			"Model":        "H200-SXM5-141G",
			"Manufacturer": "NVIDIA",
			"BDF":          fmt.Sprintf("0000:%02x:00.0", i+1),
		}
	}

	r := redfishSnapshotReader{tree: tree}
	got := r.collectGPUs([]string{"/redfish/v1/Systems/1"}, []string{"/redfish/v1/Chassis/1"})
	if len(got) != len(pcieIDs) {
		t.Fatalf("expected %d merged GPUs, got %d", len(pcieIDs), len(got))
	}

	bySlot := make(map[string]models.GPU, len(got))
	for _, gpu := range got {
		bySlot[gpu.Slot] = gpu
		if strings.EqualFold(strings.TrimSpace(gpu.Slot), strings.TrimSpace(gpu.Model)) {
			t.Fatalf("expected model-only placeholder to be dropped, got slot=%q", gpu.Slot)
		}
	}
	for i, id := range pcieIDs {
		slot := fmt.Sprintf("PCIeCard%d", id)
		gpu, ok := bySlot[slot]
		if !ok {
			t.Fatalf("expected concrete slot %q in output", slot)
		}
		if gpu.SerialNumber != serials[i] {
			t.Fatalf("expected slot %s serial %s, got %s", slot, serials[i], gpu.SerialNumber)
		}
	}
}

func TestShouldCrawlPath_MemorySubresourcesAreSkipped(t *testing.T) {
	if !shouldCrawlPath("/redfish/v1/Systems/1/Memory/CPU0_C0D0") {
		t.Fatalf("expected direct DIMM resource to be crawlable")
	}
	if shouldCrawlPath("/redfish/v1/Systems/1/Memory/CPU0_C0D0/Assembly") {
		t.Fatalf("expected DIMM assembly subresource to be skipped")
	}
	if shouldCrawlPath("/redfish/v1/Systems/1/Memory/CPU0_C0D0/MemoryMetrics") {
		t.Fatalf("expected DIMM metrics subresource to be skipped")
	}
	if shouldCrawlPath("/redfish/v1/Chassis/1/PCIeDevices/0/PCIeFunctions/1") {
		t.Fatalf("expected noisy chassis pciefunctions branch to be skipped")
	}
}

func TestIsRedfishMemoryMemberPath(t *testing.T) {
	cases := []struct {
		path string
		want bool
	}{
		{path: "/redfish/v1/Systems/1/Memory", want: false},
		{path: "/redfish/v1/Systems/1/Memory/CPU0_C0D0", want: true},
		{path: "/redfish/v1/Systems/1/Memory/CPU0_C0D0/Assembly", want: false},
		{path: "/redfish/v1/Systems/1/Memory/CPU0_C0D0/MemoryMetrics", want: false},
		{path: "/redfish/v1/Chassis/1/Memory/CPU0_C0D0", want: false},
	}
	for _, tc := range cases {
		got := isRedfishMemoryMemberPath(tc.path)
		if got != tc.want {
			t.Fatalf("isRedfishMemoryMemberPath(%q) = %v, want %v", tc.path, got, tc.want)
		}
	}
}

func TestRedfishSnapshotBranchKey(t *testing.T) {
	cases := []struct {
		path string
		want string
	}{
		{path: "", want: ""},
		{path: "/redfish/v1", want: ""},
		{path: "/redfish/v1/Systems", want: "/redfish/v1/Systems"},
		{path: "/redfish/v1/Systems/1", want: "/redfish/v1/Systems/1"},
		{path: "/redfish/v1/Systems/1/Memory", want: "/redfish/v1/Systems/1/Memory"},
		{path: "/redfish/v1/Systems/1/Memory/CPU0_C0D0", want: "/redfish/v1/Systems/1/Memory"},
		{path: "/redfish/v1/Systems/1/PCIeDevices/GPU1", want: "/redfish/v1/Systems/1/PCIeDevices"},
		{path: "/redfish/v1/Chassis/1/Sensors/1", want: "/redfish/v1/Chassis/1/Sensors"},
		{path: "/redfish/v1/UpdateService/FirmwareInventory/BIOS", want: "/redfish/v1/UpdateService/FirmwareInventory"},
	}
	for _, tc := range cases {
		got := redfishSnapshotBranchKey(tc.path)
		if got != tc.want {
			t.Fatalf("redfishSnapshotBranchKey(%q) = %q, want %q", tc.path, got, tc.want)
		}
	}
}

func TestShouldPostProbeCollectionPath(t *testing.T) {
	if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Sensors") {
		t.Fatalf("expected sensors collection to be skipped by default")
	}
	t.Setenv("LOGPILE_REDFISH_SENSOR_POSTPROBE", "1")
	if !shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Sensors") {
		t.Fatalf("expected sensors collection to be post-probed when enabled")
	}
	if !shouldPostProbeCollectionPath("/redfish/v1/Systems/1/Storage/RAID/Drives") {
		t.Fatalf("expected drives collection to be post-probed")
	}
	if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Boards/BOARD1") {
		t.Fatalf("expected board member resource to be skipped from post-probe")
	}
	if shouldPostProbeCollectionPath("/redfish/v1/Chassis/1/Assembly/Oem/COMMONb/COMMONbAssembly/1") {
		t.Fatalf("expected assembly member resource to be skipped from post-probe")
	}
}

func TestShouldAdaptivePostProbeCollectionPath(t *testing.T) {
	withExplicitNamedMembers := map[string]interface{}{
		"Members": []interface{}{
			map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/NIC-0-0"},
			map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/NIC-0-1"},
		},
	}
	if shouldAdaptivePostProbeCollectionPath("/redfish/v1/Systems/1/EthernetInterfaces", withExplicitNamedMembers) {
		t.Fatalf("expected explicit non-numeric members to skip adaptive post-probe")
	}

	withNumericMembers := map[string]interface{}{
		"Members": []interface{}{
			map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/1"},
			map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/2"},
		},
	}
	if !shouldAdaptivePostProbeCollectionPath("/redfish/v1/Chassis/1/PCIeDevices", withNumericMembers) {
		t.Fatalf("expected numeric members to allow adaptive post-probe")
	}

	withoutMembers := map[string]interface{}{"Name": "Drives"}
	if !shouldAdaptivePostProbeCollectionPath("/redfish/v1/Chassis/1/Drives", withoutMembers) {
		t.Fatalf("expected missing members to allow adaptive post-probe")
	}
}

func TestShouldAdaptiveNVMeProbe(t *testing.T) {
	withMembers := map[string]interface{}{
		"Members": []interface{}{
			map[string]interface{}{"@odata.id": "/redfish/v1/Chassis/1/Drives/OB01"},
		},
	}
	if shouldAdaptiveNVMeProbe(withMembers) {
		t.Fatalf("expected drives collection with explicit members to skip NVMe probe")
	}

	withoutMembers := map[string]interface{}{"Name": "Drives"}
	if !shouldAdaptiveNVMeProbe(withoutMembers) {
		t.Fatalf("expected drives collection without members to allow NVMe probe")
	}
}

func TestRedfishAdaptivePrefetchTargets(t *testing.T) {
	candidates := []string{
		"/redfish/v1/Systems/1/Memory",
		"/redfish/v1/Systems/1/Processors",
		"/redfish/v1/Systems/1/Storage",
	}
	rawTree := map[string]interface{}{
		"/redfish/v1/Systems/1/Memory": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Memory/DIMM1"},
			},
		},
		"/redfish/v1/Systems/1/Storage": map[string]interface{}{
			"Members": []interface{}{
				map[string]interface{}{"@odata.id": "/redfish/v1/Systems/1/Storage/1"},
			},
		},
	}
	fetchErrs := map[string]string{
		"/redfish/v1/Systems/1/Memory/DIMM1":    "Get \"https://bmc/redfish/v1/Systems/1/Memory/DIMM1\": context deadline exceeded",
		"/redfish/v1/Systems/1/Storage/1":       "status 404 from /redfish/v1/Systems/1/Storage/1: not found",
		"/redfish/v1/Systems/1/Processors":      "Get \"https://bmc/redfish/v1/Systems/1/Processors\": context deadline exceeded",
		"/redfish/v1/Systems/1/Storage/Volumes": "status 404 from /redfish/v1/Systems/1/Storage/Volumes: not found",
	}

	got := redfishAdaptivePrefetchTargets(candidates, rawTree, fetchErrs)
	joined := strings.Join(got, "\n")
	for _, wanted := range []string{
		"/redfish/v1/Systems/1/Memory",
		"/redfish/v1/Systems/1/Processors",
	} {
		if !strings.Contains(joined, wanted) {
			t.Fatalf("expected adaptive prefetch target %q", wanted)
		}
	}
	if strings.Contains(joined, "/redfish/v1/Systems/1/Storage") {
		t.Fatalf("expected storage with only non-retryable missing members to be skipped")
	}
}

func TestRedfishSnapshotPrioritySeeds_DefaultSkipsNoisyBranches(t *testing.T) {
	seeds := redfishSnapshotPrioritySeeds(
		[]string{"/redfish/v1/Systems/1"},
		[]string{"/redfish/v1/Chassis/1"},
		[]string{"/redfish/v1/Managers/1"},
	)
	joined := strings.Join(seeds, "\n")
	for _, noisy := range []string{
		"/redfish/v1/Fabrics",
		"/redfish/v1/Chassis/1/Backplanes",
		"/redfish/v1/Chassis/1/Boards",
		"/redfish/v1/Chassis/1/Sensors",
		"/redfish/v1/Managers/1/LogServices",
	} {
		if strings.Contains(joined, noisy) {
			t.Fatalf("unexpected noisy seed %q", noisy)
		}
	}
	for _, wanted := range []string{
		"/redfish/v1/Systems/1/Memory",
		"/redfish/v1/Systems/1/PCIeDevices",
		"/redfish/v1/Chassis/1/Drives",
		"/redfish/v1/Chassis/1/NetworkAdapters",
		"/redfish/v1/Managers/1/NetworkProtocol",
	} {
		if !strings.Contains(joined, wanted) {
			t.Fatalf("expected seed %q", wanted)
		}
	}
}

func TestRedfishPrefetchTargets_FilterNoisyBranches(t *testing.T) {
	critical := []string{
		"/redfish/v1/Systems/1",
		"/redfish/v1/Systems/1/Memory",
		"/redfish/v1/Systems/1/Oem/Public/FRU",
		"/redfish/v1/Chassis/1/Drives",
		"/redfish/v1/Chassis/1/Backplanes",
		"/redfish/v1/Chassis/1/Sensors",
		"/redfish/v1/Managers/1/LogServices",
		"/redfish/v1/Managers/1/NetworkProtocol",
	}
	got := redfishPrefetchTargets(critical)
	joined := strings.Join(got, "\n")
	for _, wanted := range []string{
		"/redfish/v1/Systems/1",
		"/redfish/v1/Systems/1/Memory",
		"/redfish/v1/Systems/1/Oem/Public/FRU",
		"/redfish/v1/Chassis/1/Drives",
		"/redfish/v1/Managers/1/NetworkProtocol",
	} {
		if !strings.Contains(joined, wanted) {
			t.Fatalf("expected prefetch target %q", wanted)
		}
	}
	for _, noisy := range []string{
		"/redfish/v1/Chassis/1/Backplanes",
		"/redfish/v1/Chassis/1/Sensors",
		"/redfish/v1/Managers/1/LogServices",
	} {
		if strings.Contains(joined, noisy) {
			t.Fatalf("unexpected noisy prefetch target %q", noisy)
		}
	}
}
