package inspur

import (
	"encoding/hex"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"unicode"

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

var (
	reRedisGPUKey     = regexp.MustCompile(`GPUInfo:REDIS_GPUINFO_T([0-9]+):([A-Za-z0-9_]+)`)
	reRedisNICKey     = regexp.MustCompile(`RedisNicInfo:redis_nic_info_t:stNicDeviceInfo([0-9]+):([A-Za-z0-9_]+)`)
	reRedisRAIDSerial = regexp.MustCompile(`RAIDMSCCInfo:redis_pcie_mscc_raid_info_t([0-9]+):RAIDInfo:SerialNum`)
	reRedisPCIESNPN   = regexp.MustCompile(`AssetInfoPCIE:SNPN([0-9]+):(SN|PN)`)
)

type redisGPUSnapshot struct {
	ByIndex map[int]map[string]string
}

type redisNICSnapshot struct {
	ByIndex map[int]map[string]string
}

type redisPCIESerialSnapshot struct {
	ByPart map[string]string
}

func enrichFromRedisDump(content []byte, hw *models.HardwareConfig) {
	if hw == nil || len(content) == 0 {
		return
	}

	gpuSnap := parseRedisGPUSnapshot(content)
	nicSnap := parseRedisNICSnapshot(content)
	raidSerials := parseRedisRAIDSerials(content)
	pcieSnap := parseRedisPCIESerialSnapshot(content)

	applyRedisGPUEnrichment(hw, gpuSnap)
	applyRedisNICEnrichment(hw, nicSnap)
	applyRedisPCIESNPNEnrichment(hw, pcieSnap)
	applyRedisPCIeEnrichment(hw, raidSerials)
}

func parseRedisRAIDSerials(content []byte) []string {
	matches := reRedisRAIDSerial.FindAllSubmatchIndex(content, -1)
	if len(matches) == 0 {
		return nil
	}

	seen := make(map[string]bool, len(matches))
	serials := make([]string, 0, len(matches))
	for _, m := range matches {
		if len(m) < 4 {
			continue
		}
		value := normalizeRedisValue(extractRedisCandidateValue(content, m[1]))
		if value == "" || seen[value] {
			continue
		}
		seen[value] = true
		serials = append(serials, value)
	}
	return serials
}

func parseRedisPCIESerialSnapshot(content []byte) redisPCIESerialSnapshot {
	type rec struct {
		PN string
		SN string
	}
	tmp := make(map[int]rec)

	matches := reRedisPCIESNPN.FindAllSubmatchIndex(content, -1)
	for _, m := range matches {
		if len(m) < 6 {
			continue
		}
		idxStr := string(content[m[2]:m[3]])
		field := string(content[m[4]:m[5]])
		idx, err := strconv.Atoi(idxStr)
		if err != nil {
			continue
		}

		value := normalizeRedisValue(extractRedisCandidateValue(content, m[1]))
		if value == "" {
			continue
		}

		r := tmp[idx]
		if field == "PN" {
			r.PN = value
		} else if field == "SN" {
			r.SN = value
		}
		tmp[idx] = r
	}

	out := redisPCIESerialSnapshot{ByPart: make(map[string]string)}
	for _, r := range tmp {
		pn := normalizeRedisValue(r.PN)
		sn := normalizeRedisValue(r.SN)
		if pn == "" || sn == "" {
			continue
		}
		out.ByPart[strings.ToLower(strings.TrimSpace(pn))] = sn
	}
	return out
}

func parseRedisGPUSnapshot(content []byte) redisGPUSnapshot {
	snap := redisGPUSnapshot{ByIndex: make(map[int]map[string]string)}
	matches := reRedisGPUKey.FindAllSubmatchIndex(content, -1)
	for _, m := range matches {
		if len(m) < 6 {
			continue
		}

		idxStr := string(content[m[2]:m[3]])
		field := string(content[m[4]:m[5]])
		idx, err := strconv.Atoi(idxStr)
		if err != nil {
			continue
		}

		value := extractRedisInlineValue(content, m[1])
		if value == "" {
			continue
		}

		byField, ok := snap.ByIndex[idx]
		if !ok {
			byField = make(map[string]string)
			snap.ByIndex[idx] = byField
		}
		byField[field] = value
	}
	return snap
}

func parseRedisNICSnapshot(content []byte) redisNICSnapshot {
	snap := redisNICSnapshot{ByIndex: make(map[int]map[string]string)}
	matches := reRedisNICKey.FindAllSubmatchIndex(content, -1)
	for _, m := range matches {
		if len(m) < 6 {
			continue
		}

		idxStr := string(content[m[2]:m[3]])
		field := string(content[m[4]:m[5]])
		idx, err := strconv.Atoi(idxStr)
		if err != nil {
			continue
		}

		value := extractRedisInlineValue(content, m[1])
		if value == "" {
			continue
		}

		byField, ok := snap.ByIndex[idx]
		if !ok {
			byField = make(map[string]string)
			snap.ByIndex[idx] = byField
		}
		byField[field] = value
	}
	return snap
}

func extractRedisInlineValue(content []byte, start int) string {
	if start < 0 || start >= len(content) {
		return ""
	}

	i := start
	for i < len(content) && content[i] <= 0x20 {
		i++
	}
	if i >= len(content) {
		return ""
	}

	j := i
	for j < len(content) {
		c := content[j]
		if c == 0 || c < 0x20 || c > 0x7e {
			break
		}
		j++
	}

	if j <= i {
		return ""
	}

	raw := strings.TrimSpace(string(content[i:j]))
	if raw == "" {
		return ""
	}

	decoded := maybeDecodeHexString(raw)
	if decoded != "" {
		return decoded
	}
	return raw
}

func extractRedisCandidateValue(content []byte, start int) string {
	// Fast-path for simple inline string values.
	if v := extractRedisInlineValue(content, start); normalizeRedisValue(v) != "" {
		return v
	}

	if start < 0 || start >= len(content) {
		return ""
	}

	end := start + 256
	if end > len(content) {
		end = len(content)
	}
	window := content[start:end]

	for _, token := range splitAlphaNumTokens(window) {
		if len(token) < 6 {
			continue
		}
		lower := strings.ToLower(token)
		if strings.Contains(lower, "redis") || strings.Contains(lower, "sensor") || strings.Contains(lower, "fullsdr") {
			continue
		}
		if decoded := maybeDecodeHexString(token); normalizeRedisValue(decoded) != "" {
			return decoded
		}
		if normalizeRedisValue(token) != "" {
			return token
		}
	}
	return ""
}

func splitAlphaNumTokens(b []byte) []string {
	var out []string
	start := -1
	for i := 0; i < len(b); i++ {
		c := rune(b[i])
		if unicode.IsLetter(c) || unicode.IsDigit(c) {
			if start == -1 {
				start = i
			}
			continue
		}
		if start != -1 {
			out = append(out, string(b[start:i]))
			start = -1
		}
	}
	if start != -1 {
		out = append(out, string(b[start:]))
	}
	return out
}

func maybeDecodeHexString(s string) string {
	if len(s) < 8 || len(s)%2 != 0 {
		return ""
	}

	for _, c := range s {
		if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') {
			return ""
		}
	}

	b, err := hex.DecodeString(s)
	if err != nil {
		return ""
	}
	decoded := strings.TrimSpace(strings.TrimRight(string(b), "\x00"))
	if decoded == "" {
		return ""
	}
	for _, c := range decoded {
		if c < 0x20 || c > 0x7e {
			return ""
		}
	}
	return decoded
}

func applyRedisGPUEnrichment(hw *models.HardwareConfig, snap redisGPUSnapshot) {
	if len(hw.GPUs) == 0 || len(snap.ByIndex) == 0 {
		return
	}

	type redisGPU struct {
		Index int
		Data  map[string]string
	}
	redisGPUs := make([]redisGPU, 0, len(snap.ByIndex))
	for idx, data := range snap.ByIndex {
		if data == nil {
			continue
		}
		if data["NV_GPU_SerialNumber"] == "" && data["NV_GPU_FWVersion"] == "" && data["NV_GPU_UUID"] == "" {
			continue
		}
		redisGPUs = append(redisGPUs, redisGPU{Index: idx, Data: data})
	}
	if len(redisGPUs) == 0 {
		return
	}
	sort.Slice(redisGPUs, func(i, j int) bool { return redisGPUs[i].Index < redisGPUs[j].Index })

	target := make([]*models.GPU, 0, len(hw.GPUs))
	for i := range hw.GPUs {
		gpu := &hw.GPUs[i]
		if isNVIDIAGPU(gpu) {
			target = append(target, gpu)
		}
	}
	if len(target) == 0 || len(target) != len(redisGPUs) {
		return
	}
	sort.Slice(target, func(i, j int) bool {
		left := strings.TrimSpace(target[i].BDF)
		right := strings.TrimSpace(target[j].BDF)
		if left != "" && right != "" {
			return left < right
		}
		return strings.TrimSpace(target[i].Slot) < strings.TrimSpace(target[j].Slot)
	})

	for i := range target {
		applyRedisGPUFields(target[i], redisGPUs[i].Data)
	}
}

func isNVIDIAGPU(gpu *models.GPU) bool {
	if gpu == nil {
		return false
	}
	if gpu.VendorID == 0x10de {
		return true
	}
	man := strings.ToLower(strings.TrimSpace(gpu.Manufacturer))
	return strings.Contains(man, "nvidia")
}

func applyRedisGPUFields(gpu *models.GPU, fields map[string]string) {
	if gpu == nil || fields == nil {
		return
	}

	if serial := normalizeRedisValue(fields["NV_GPU_SerialNumber"]); serial != "" && isMissingGPUField(gpu.SerialNumber) {
		gpu.SerialNumber = serial
	}
	if fw := normalizeRedisValue(fields["NV_GPU_FWVersion"]); fw != "" && isMissingGPUField(gpu.Firmware) {
		gpu.Firmware = fw
	}
	if uuid := normalizeRedisValue(fields["NV_GPU_UUID"]); uuid != "" && isMissingGPUField(gpu.UUID) {
		gpu.UUID = uuid
	}

	if part := normalizeRedisValue(fields["NVGPUPartNumber"]); part != "" && isMissingGPUField(gpu.PartNumber) {
		gpu.PartNumber = part
	}
	if model := normalizeRedisValue(fields["NVGPUMarketingName"]); model != "" && isGenericGPUModel(gpu.Model) {
		gpu.Model = model
	}

	if gpu.ClockSpeed == 0 {
		if mhz, ok := parseIntField(fields["OperatingSpeedMHz"]); ok {
			gpu.ClockSpeed = mhz
		}
	}
	if gpu.Power == 0 {
		if pwr, ok := parseIntField(fields["GPUTotalPower"]); ok {
			gpu.Power = pwr
		}
	}
	if gpu.Temperature == 0 {
		if temp, ok := parseIntField(fields["Temp"]); ok {
			gpu.Temperature = temp
		}
	}
	if gpu.MemTemperature == 0 {
		if temp, ok := parseIntField(fields["MemTemp"]); ok {
			gpu.MemTemperature = temp
		}
	}
}

func parseIntField(v string) (int, bool) {
	v = normalizeRedisValue(v)
	if v == "" {
		return 0, false
	}
	n, err := strconv.Atoi(v)
	if err != nil {
		return 0, false
	}
	return n, true
}

func normalizeRedisValue(v string) string {
	v = strings.TrimSpace(v)
	if v == "" {
		return ""
	}
	l := strings.ToLower(v)
	if l == "n/a" || l == "na" || l == "null" || l == "unknown" {
		return ""
	}
	return v
}

func isMissingGPUField(v string) bool {
	return normalizeRedisValue(v) == ""
}

func isGenericGPUModel(model string) bool {
	m := strings.ToLower(strings.TrimSpace(model))
	switch m {
	case "", "unknown", "display", "display controller", "3d controller", "vga", "gpu":
		return true
	default:
		return false
	}
}

func applyRedisNICEnrichment(hw *models.HardwareConfig, snap redisNICSnapshot) {
	if len(hw.NetworkAdapters) == 0 || len(snap.ByIndex) == 0 {
		return
	}

	type redisNIC struct {
		Index int
		Data  map[string]string
	}
	redisNICs := make([]redisNIC, 0, len(snap.ByIndex))
	for idx, data := range snap.ByIndex {
		if data == nil {
			continue
		}
		if normalizeRedisValue(data["FWVersion"]) == "" {
			continue
		}
		redisNICs = append(redisNICs, redisNIC{Index: idx, Data: data})
	}
	if len(redisNICs) == 0 {
		return
	}
	sort.Slice(redisNICs, func(i, j int) bool { return redisNICs[i].Index < redisNICs[j].Index })

	target := make([]*models.NetworkAdapter, 0, len(hw.NetworkAdapters))
	for i := range hw.NetworkAdapters {
		nic := &hw.NetworkAdapters[i]
		if nic.Present {
			target = append(target, nic)
		}
	}
	if len(target) == 0 {
		return
	}
	sort.Slice(target, func(i, j int) bool {
		left := strings.TrimSpace(target[i].Location)
		right := strings.TrimSpace(target[j].Location)
		if left != "" && right != "" {
			return left < right
		}
		return strings.TrimSpace(target[i].Slot) < strings.TrimSpace(target[j].Slot)
	})

	limit := len(target)
	if len(redisNICs) < limit {
		limit = len(redisNICs)
	}
	for i := 0; i < limit; i++ {
		nic := target[i]
		data := redisNICs[i].Data

		if fw := normalizeRedisValue(data["FWVersion"]); fw != "" && normalizeRedisValue(nic.Firmware) == "" {
			nic.Firmware = fw
		}
		if serial := normalizeRedisValue(data["SerialNum"]); serial != "" && normalizeRedisValue(nic.SerialNumber) == "" {
			nic.SerialNumber = serial
		}
		if part := normalizeRedisValue(data["PartNum"]); part != "" && normalizeRedisValue(nic.PartNumber) == "" {
			nic.PartNumber = part
		}
	}
}

func applyRedisPCIeEnrichment(hw *models.HardwareConfig, raidSerials []string) {
	if hw == nil || len(hw.PCIeDevices) == 0 || len(raidSerials) == 0 {
		return
	}

	target := make([]*models.PCIeDevice, 0, len(hw.PCIeDevices))
	for i := range hw.PCIeDevices {
		dev := &hw.PCIeDevices[i]
		if normalizeRedisValue(dev.SerialNumber) != "" {
			continue
		}
		class := strings.ToLower(strings.TrimSpace(dev.DeviceClass))
		part := strings.ToLower(strings.TrimSpace(dev.PartNumber))
		if strings.Contains(class, "raid") || strings.Contains(class, "sas") || strings.Contains(class, "storage") ||
			strings.Contains(part, "raid") || strings.Contains(part, "sas") || strings.Contains(part, "hba") {
			target = append(target, dev)
		}
	}
	if len(target) == 0 {
		return
	}

	sort.Slice(target, func(i, j int) bool {
		left := strings.TrimSpace(target[i].BDF)
		right := strings.TrimSpace(target[j].BDF)
		if left != "" && right != "" {
			return left < right
		}
		return strings.TrimSpace(target[i].Slot) < strings.TrimSpace(target[j].Slot)
	})

	limit := len(target)
	if len(raidSerials) < limit {
		limit = len(raidSerials)
	}
	for i := 0; i < limit; i++ {
		target[i].SerialNumber = raidSerials[i]
	}
}

func applyRedisPCIESNPNEnrichment(hw *models.HardwareConfig, snap redisPCIESerialSnapshot) {
	if hw == nil || len(hw.PCIeDevices) == 0 || len(snap.ByPart) == 0 {
		return
	}

	for i := range hw.PCIeDevices {
		dev := &hw.PCIeDevices[i]
		if normalizeRedisValue(dev.SerialNumber) != "" {
			continue
		}
		part := strings.ToLower(strings.TrimSpace(dev.PartNumber))
		if part == "" {
			continue
		}
		if serial := normalizeRedisValue(snap.ByPart[part]); serial != "" {
			dev.SerialNumber = serial
		}
	}
}
