package inspur

import (
	"regexp"
	"sort"
	"strings"

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

var bpHDDSerialTokenRegex = regexp.MustCompile(`[A-Za-z0-9]{8,32}`)

func enrichStorageFromSerialFallbackFiles(files []parser.ExtractedFile, hw *models.HardwareConfig) {
	if hw == nil {
		return
	}
	f := parser.FindFileByName(files, "BpHDDSerialNumber.info")
	if f == nil {
		return
	}
	serials := extractBPHDDSerials(f.Content)
	if len(serials) == 0 {
		return
	}
	applyStorageSerialFallback(hw, serials)
}

func extractBPHDDSerials(content []byte) []string {
	if len(content) == 0 {
		return nil
	}
	matches := bpHDDSerialTokenRegex.FindAllString(string(content), -1)
	if len(matches) == 0 {
		return nil
	}

	out := make([]string, 0, len(matches))
	seen := make(map[string]struct{}, len(matches))
	for _, m := range matches {
		v := normalizeRedisValue(m)
		if !looksLikeStorageSerial(v) {
			continue
		}
		key := strings.ToLower(v)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, v)
	}
	return out
}

func looksLikeStorageSerial(v string) bool {
	if len(v) < 8 {
		return false
	}
	hasLetter := false
	hasDigit := false
	for _, r := range v {
		switch {
		case r >= 'A' && r <= 'Z':
			hasLetter = true
		case r >= 'a' && r <= 'z':
			hasLetter = true
		case r >= '0' && r <= '9':
			hasDigit = true
		default:
			return false
		}
	}
	return hasLetter && hasDigit
}

// applyRAIDSlotSerials updates storage serial numbers using the slot→serial map
// derived from audit.log RAID SN change events. Overwrites existing serials since
// audit.log represents the authoritative current state after all disk replacements.
func applyRAIDSlotSerials(hw *models.HardwareConfig, serials map[string]string) {
	if hw == nil || len(serials) == 0 {
		return
	}
	for i := range hw.Storage {
		slot := strings.TrimSpace(hw.Storage[i].Slot)
		if slot == "" {
			continue
		}
		if sn, ok := serials[slot]; ok && sn != "" {
			hw.Storage[i].SerialNumber = sn
		}
	}
}

func applyStorageSerialFallback(hw *models.HardwareConfig, serials []string) {
	if hw == nil || len(hw.Storage) == 0 || len(serials) == 0 {
		return
	}

	existing := make(map[string]struct{}, len(hw.Storage))
	for _, dev := range hw.Storage {
		if sn := normalizeRedisValue(dev.SerialNumber); sn != "" {
			existing[strings.ToLower(sn)] = struct{}{}
		}
	}

	filtered := make([]string, 0, len(serials))
	for _, sn := range serials {
		key := strings.ToLower(sn)
		if _, ok := existing[key]; ok {
			continue
		}
		filtered = append(filtered, sn)
	}
	if len(filtered) == 0 {
		return
	}

	type target struct {
		index int
		rank  int
		slot  string
	}
	targets := make([]target, 0, len(hw.Storage))
	for i := range hw.Storage {
		dev := hw.Storage[i]
		if normalizeRedisValue(dev.SerialNumber) != "" {
			continue
		}
		if !dev.Present && strings.TrimSpace(dev.Slot) == "" {
			continue
		}
		rank := 0
		if !dev.Present {
			rank += 10
		}
		if strings.EqualFold(strings.TrimSpace(dev.Type), "NVMe") {
			rank += 5
		}
		if strings.TrimSpace(dev.Slot) == "" {
			rank += 4
		}
		targets = append(targets, target{
			index: i,
			rank:  rank,
			slot:  strings.ToLower(strings.TrimSpace(dev.Slot)),
		})
	}
	if len(targets) == 0 {
		return
	}

	sort.Slice(targets, func(i, j int) bool {
		if targets[i].rank != targets[j].rank {
			return targets[i].rank < targets[j].rank
		}
		return targets[i].slot < targets[j].slot
	})

	for i := 0; i < len(targets) && i < len(filtered); i++ {
		dev := &hw.Storage[targets[i].index]
		dev.SerialNumber = filtered[i]
		if !dev.Present {
			dev.Present = true
		}
	}
}
