package inspur

import (
	"testing"

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

func TestExtractRedisInlineValue_DecodesHexEncodedString(t *testing.T) {
	data := []byte("RedisNicInfo:redis_nic_info_t:stNicDeviceInfo0:FWVersion 32362e34332e32353636000000000000\x00tail")
	key := []byte("RedisNicInfo:redis_nic_info_t:stNicDeviceInfo0:FWVersion")
	pos := indexBytes(data, key)
	if pos < 0 {
		t.Fatal("key not found")
	}

	got := extractRedisInlineValue(data, pos+len(key))
	if got != "26.43.2566" {
		t.Fatalf("expected decoded fw 26.43.2566, got %q", got)
	}
}

func TestApplyRedisGPUEnrichment_FillsSerialFirmwareUUID(t *testing.T) {
	hw := &models.HardwareConfig{
		GPUs: []models.GPU{
			{Slot: "#CPU0_PCIE2", BDF: "0c:00.0", VendorID: 0x10de, Model: "3D Controller"},
			{Slot: "#CPU0_PCIE1", BDF: "58:00.0", VendorID: 0x10de, Model: "3D Controller"},
		},
	}

	snap := redisGPUSnapshot{
		ByIndex: map[int]map[string]string{
			1: {
				"NV_GPU_SerialNumber": "1321125009572",
				"NV_GPU_FWVersion":    "96.00.B7.00.02",
				"NV_GPU_UUID":         "GPU-AAA",
			},
			2: {
				"NV_GPU_SerialNumber": "1321125010420",
				"NV_GPU_FWVersion":    "96.00.B7.00.02",
				"NV_GPU_UUID":         "GPU-BBB",
			},
		},
	}

	applyRedisGPUEnrichment(hw, snap)

	if hw.GPUs[0].SerialNumber != "1321125009572" || hw.GPUs[0].Firmware != "96.00.B7.00.02" || hw.GPUs[0].UUID != "GPU-AAA" {
		t.Fatalf("unexpected gpu0 enrichment: %+v", hw.GPUs[0])
	}
	if hw.GPUs[1].SerialNumber != "1321125010420" || hw.GPUs[1].Firmware != "96.00.B7.00.02" || hw.GPUs[1].UUID != "GPU-BBB" {
		t.Fatalf("unexpected gpu1 enrichment: %+v", hw.GPUs[1])
	}
}

func TestApplyRedisGPUEnrichment_SkipsOnCountMismatch(t *testing.T) {
	hw := &models.HardwareConfig{
		GPUs: []models.GPU{
			{Slot: "#CPU0_PCIE2", BDF: "0c:00.0", VendorID: 0x10de, Model: "3D Controller"},
		},
	}
	snap := redisGPUSnapshot{
		ByIndex: map[int]map[string]string{
			1: {"NV_GPU_SerialNumber": "1321125009572"},
			2: {"NV_GPU_SerialNumber": "1321125010420"},
		},
	}

	applyRedisGPUEnrichment(hw, snap)
	if hw.GPUs[0].SerialNumber != "" {
		t.Fatalf("expected no enrichment on count mismatch, got %q", hw.GPUs[0].SerialNumber)
	}
}

func TestParseRedisRAIDSerials_DecodesHexSerial(t *testing.T) {
	raw := []byte("RAIDMSCCInfo:redis_pcie_mscc_raid_info_t0:RAIDInfo:SerialNum\x80%@`5341523531314532 \x00tail")
	got := parseRedisRAIDSerials(raw)
	if len(got) != 1 {
		t.Fatalf("expected 1 raid serial, got %d", len(got))
	}
	if got[0] != "SAR511E2" {
		t.Fatalf("expected decoded serial SAR511E2, got %q", got[0])
	}
}

func TestApplyRedisPCIeEnrichment_FillsStorageControllerSerial(t *testing.T) {
	hw := &models.HardwareConfig{
		PCIeDevices: []models.PCIeDevice{
			{Slot: "#CPU1_PCIE9", BDF: "98:00.0", DeviceClass: "Smart Storage PQI SAS", PartNumber: "PM8222-SHBA"},
			{Slot: "#CPU0_PCIE3", BDF: "32:00.0", DeviceClass: "Fibre Channel", PartNumber: "LPE32002"},
		},
	}

	applyRedisPCIeEnrichment(hw, []string{"SAR511E2"})

	if hw.PCIeDevices[0].SerialNumber != "SAR511E2" {
		t.Fatalf("expected PM8222 serial SAR511E2, got %q", hw.PCIeDevices[0].SerialNumber)
	}
	if hw.PCIeDevices[1].SerialNumber != "" {
		t.Fatalf("expected non-storage device serial untouched, got %q", hw.PCIeDevices[1].SerialNumber)
	}
}

func TestParseRedisPCIESerialSnapshot_MapsPNToSN(t *testing.T) {
	raw := []byte("" +
		"AssetInfoPCIE:SNPN9:PN PM8222-SHBA\x00" +
		"AssetInfoPCIE:SNPN9:SN SAR511E2\x00")

	snap := parseRedisPCIESerialSnapshot(raw)
	got := snap.ByPart["pm8222-shba"]
	if got != "SAR511E2" {
		t.Fatalf("expected SN SAR511E2 for PM8222-SHBA, got %q", got)
	}
}

func TestApplyRedisPCIESNPNEnrichment_FillsByPartNumber(t *testing.T) {
	hw := &models.HardwareConfig{
		PCIeDevices: []models.PCIeDevice{
			{Slot: "#CPU1_PCIE9", PartNumber: "PM8222-SHBA"},
		},
	}
	snap := redisPCIESerialSnapshot{ByPart: map[string]string{"pm8222-shba": "SAR511E2"}}

	applyRedisPCIESNPNEnrichment(hw, snap)
	if hw.PCIeDevices[0].SerialNumber != "SAR511E2" {
		t.Fatalf("expected serial SAR511E2, got %q", hw.PCIeDevices[0].SerialNumber)
	}
}

func indexBytes(haystack, needle []byte) int {
	for i := 0; i+len(needle) <= len(haystack); i++ {
		match := true
		for j := 0; j < len(needle); j++ {
			if haystack[i+j] != needle[j] {
				match = false
				break
			}
		}
		if match {
			return i
		}
	}
	return -1
}
