package inspur

import (
	"regexp"
	"sort"
	"strconv"
	"strings"

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

// solSmartdDeviceRe matches smartd device info lines from SOLHostCapture.log.
// Example:
//
//	Device: /dev/sda [SAT], Micron_5400_MTFDDAK480TGA, S/N:2310400DC7E3, WWN:..., FW:D4CM003, 480 GB
var solSmartdDeviceRe = regexp.MustCompile(
	`Device: /dev/\S+ \[SAT\], (.+?), S/N:(\S+),.*?FW:(\S+), ([\d.]+) (GB|TB)`,
)

type solSmartdDevice struct {
	Model    string
	Serial   string
	Firmware string
	SizeGB   int
}

// parseSOLSmartdDevices extracts unique disk entries from SOLHostCapture.log content.
// Deduplicates by serial number (case-insensitive); preserves first-seen order.
func parseSOLSmartdDevices(content []byte) []solSmartdDevice {
	seen := make(map[string]struct{})
	var out []solSmartdDevice

	for _, line := range strings.Split(string(content), "\n") {
		m := solSmartdDeviceRe.FindStringSubmatch(line)
		if m == nil {
			continue
		}
		serial := strings.TrimSpace(m[2])
		if serial == "" {
			continue
		}
		key := strings.ToLower(serial)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}

		sizeGB := parseSolSizeGB(m[4], m[5])
		out = append(out, solSmartdDevice{
			Model:    strings.TrimSpace(m[1]),
			Serial:   serial,
			Firmware: strings.TrimSpace(m[3]),
			SizeGB:   sizeGB,
		})
	}
	return out
}

// parseSolSizeGB converts smartd size string ("480", "3.84") + unit ("GB", "TB") to integer GB.
// Uses decimal TB (1 TB = 1000 GB) matching disk manufacturer conventions.
func parseSolSizeGB(value, unit string) int {
	f, err := strconv.ParseFloat(value, 64)
	if err != nil || f <= 0 {
		return 0
	}
	if strings.EqualFold(unit, "TB") {
		f *= 1000
	}
	return int(f + 0.5)
}

// enrichStorageFromSOLSmartd enriches the storage inventory using disk info from
// SOLHostCapture.log (smartd startup messages). Both the log/ and runningdata/ copies
// are processed; serials are deduplicated across both files.
//
// Enrichment priority:
//  1. Exact model match to existing entries that are missing a serial.
//  2. Positional assignment to present placeholder slots (no model, no serial).
//  3. New entries added for any remaining devices.
func enrichStorageFromSOLSmartd(files []parser.ExtractedFile, hw *models.HardwareConfig) {
	if hw == nil {
		return
	}

	solFiles := parser.FindFileByPattern(files, "SOLHostCapture.log")
	if len(solFiles) == 0 {
		return
	}

	// Collect unique devices from all SOL log copies.
	seenSerial := make(map[string]struct{})
	var devices []solSmartdDevice
	for _, f := range solFiles {
		for _, d := range parseSOLSmartdDevices(f.Content) {
			key := strings.ToLower(d.Serial)
			if _, ok := seenSerial[key]; ok {
				continue
			}
			seenSerial[key] = struct{}{}
			devices = append(devices, d)
		}
	}
	if len(devices) == 0 {
		return
	}

	// Skip devices whose serial already appears in the storage inventory.
	existingSerials := make(map[string]struct{}, len(hw.Storage))
	for _, dev := range hw.Storage {
		sn := strings.ToLower(strings.TrimSpace(dev.SerialNumber))
		if sn != "" {
			existingSerials[sn] = struct{}{}
		}
	}
	var newDevices []solSmartdDevice
	for _, d := range devices {
		if _, ok := existingSerials[strings.ToLower(d.Serial)]; !ok {
			newDevices = append(newDevices, d)
		}
	}
	if len(newDevices) == 0 {
		return
	}

	// Pass 1: enrich existing entries that match by model (first-match wins per device).
	remaining := solEnrichByModel(hw, newDevices)
	if len(remaining) == 0 {
		return
	}

	// Pass 2: assign to present placeholder slots (present=true, no model, no serial).
	remaining = solEnrichByPlaceholder(hw, remaining)
	if len(remaining) == 0 {
		return
	}

	// Pass 3: add as new storage entries without a slot assignment.
	for _, d := range remaining {
		hw.Storage = append(hw.Storage, solMakeStorage(d))
	}
}

// solEnrichByModel fills SerialNumber (and optionally Firmware/SizeGB) on existing storage
// entries whose model matches the smartd model exactly. Returns unmatched devices.
func solEnrichByModel(hw *models.HardwareConfig, devices []solSmartdDevice) []solSmartdDevice {
	var unmatched []solSmartdDevice
	for _, d := range devices {
		matched := false
		for i := range hw.Storage {
			if strings.TrimSpace(hw.Storage[i].SerialNumber) != "" {
				continue
			}
			if !strings.EqualFold(strings.TrimSpace(hw.Storage[i].Model), d.Model) {
				continue
			}
			hw.Storage[i].SerialNumber = d.Serial
			if strings.TrimSpace(hw.Storage[i].Firmware) == "" {
				hw.Storage[i].Firmware = d.Firmware
			}
			if hw.Storage[i].SizeGB == 0 {
				hw.Storage[i].SizeGB = d.SizeGB
			}
			matched = true
			break
		}
		if !matched {
			unmatched = append(unmatched, d)
		}
	}
	return unmatched
}

// solEnrichByPlaceholder assigns smartd devices to present storage entries that have
// neither a model nor a serial number, sorted by slot name. Returns unmatched devices.
func solEnrichByPlaceholder(hw *models.HardwareConfig, devices []solSmartdDevice) []solSmartdDevice {
	type slot struct {
		index int
		name  string
	}
	var placeholders []slot
	for i := range hw.Storage {
		if !hw.Storage[i].Present {
			continue
		}
		if strings.TrimSpace(hw.Storage[i].SerialNumber) != "" {
			continue
		}
		if strings.TrimSpace(hw.Storage[i].Model) != "" {
			continue
		}
		placeholders = append(placeholders, slot{index: i, name: hw.Storage[i].Slot})
	}
	sort.Slice(placeholders, func(i, j int) bool {
		return placeholders[i].name < placeholders[j].name
	})

	pi := 0
	var unmatched []solSmartdDevice
	for _, d := range devices {
		if pi >= len(placeholders) {
			unmatched = append(unmatched, d)
			continue
		}
		idx := placeholders[pi].index
		pi++
		hw.Storage[idx].SerialNumber = d.Serial
		hw.Storage[idx].Model = d.Model
		hw.Storage[idx].Firmware = d.Firmware
		if hw.Storage[idx].SizeGB == 0 {
			hw.Storage[idx].SizeGB = d.SizeGB
		}
		hw.Storage[idx].Type = solStorageType(d.Model)
		if hw.Storage[idx].Manufacturer == "" {
			hw.Storage[idx].Manufacturer = extractStorageManufacturer(d.Model)
		}
		if hw.Storage[idx].Interface == "" {
			hw.Storage[idx].Interface = "SATA"
		}
	}
	return unmatched
}

func solMakeStorage(d solSmartdDevice) models.Storage {
	return models.Storage{
		Model:        d.Model,
		SerialNumber: d.Serial,
		Firmware:     d.Firmware,
		SizeGB:       d.SizeGB,
		Type:         solStorageType(d.Model),
		Manufacturer: extractStorageManufacturer(d.Model),
		Interface:    "SATA",
		Present:      true,
	}
}

// solStorageType infers SSD vs HDD from the model string.
// Micron SSD models start with "MTFDD"; Intel SSDs contain "SSD".
func solStorageType(model string) string {
	upper := strings.ToUpper(model)
	if strings.Contains(upper, "SSD") ||
		strings.HasPrefix(upper, "MTFDD") ||
		strings.HasPrefix(upper, "MICRON_5") {
		return "SSD"
	}
	return "HDD"
}
