package inspur

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"
	"time"

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

// ParseComponentLog parses component.log file and extracts detailed hardware info
func ParseComponentLog(content []byte, hw *models.HardwareConfig) {
	if hw == nil {
		return
	}

	text := string(content)

	// Parse RESTful Memory info (detailed memory data)
	parseMemoryInfo(text, hw)

	// Parse RESTful PSU info
	parsePSUInfo(text, hw)

	// Parse RESTful HDD info
	parseHDDInfo(text, hw)

	// Parse RESTful diskbackplane info
	parseDiskBackplaneInfo(text, hw)

	// Parse RESTful Network Adapter info
	parseNetworkAdapterInfo(text, hw)

	// Extract firmware from all components
	extractComponentFirmware(text, hw)
}

// ParseComponentLogEvents extracts events from component.log (memory errors, etc.)
func ParseComponentLogEvents(content []byte) []models.Event {
	var events []models.Event
	text := string(content)

	// Parse RESTful Memory info for Warning/Error status
	memEvents := parseMemoryEvents(text)
	events = append(events, memEvents...)
	events = append(events, parseFanEvents(text)...)

	return events
}

// ParseComponentLogSensors extracts sensor readings from component.log JSON sections.
func ParseComponentLogSensors(content []byte) []models.SensorReading {
	text := string(content)
	var out []models.SensorReading
	out = append(out, parseFanSensors(text)...)
	out = append(out, parseDiskBackplaneSensors(text)...)
	out = append(out, parsePSUSummarySensors(text)...)
	return out
}

// MemoryRESTInfo represents the RESTful Memory info structure
type MemoryRESTInfo struct {
	MemModules []struct {
		MemModID          int    `json:"mem_mod_id"`
		ConfigStatus      int    `json:"config_status"`
		MemModSlot        string `json:"mem_mod_slot"`
		MemModStatus      int    `json:"mem_mod_status"`
		MemModSize        int    `json:"mem_mod_size"`
		MemModType        string `json:"mem_mod_type"`
		MemModTechnology  string `json:"mem_mod_technology"`
		MemModFrequency   int    `json:"mem_mod_frequency"`
		MemModCurrentFreq int    `json:"mem_mod_current_frequency"`
		MemModVendor      string `json:"mem_mod_vendor"`
		MemModPartNum     string `json:"mem_mod_part_num"`
		MemModSerial      string `json:"mem_mod_serial_num"`
		MemModRanks       int    `json:"mem_mod_ranks"`
		Status            string `json:"status"`
	} `json:"mem_modules"`
	TotalMemoryCount   int `json:"total_memory_count"`
	PresentMemoryCount int `json:"present_memory_count"`
	MemTotalMemSize    int `json:"mem_total_mem_size"`
}

func parseMemoryInfo(text string, hw *models.HardwareConfig) {
	// Find RESTful Memory info section
	re := regexp.MustCompile(`RESTful Memory info:\s*(\{[\s\S]*?\})\s*RESTful HDD`)
	match := re.FindStringSubmatch(text)
	if match == nil {
		return
	}

	jsonStr := match[1]
	jsonStr = strings.ReplaceAll(jsonStr, "\n", "")

	var memInfo MemoryRESTInfo
	if err := json.Unmarshal([]byte(jsonStr), &memInfo); err != nil {
		return
	}

	var merged []models.MemoryDIMM
	seen := make(map[string]int)
	for _, existing := range hw.Memory {
		key := inspurMemoryKey(existing)
		if key == "" {
			continue
		}
		seen[key] = len(merged)
		merged = append(merged, existing)
	}
	for _, mem := range memInfo.MemModules {
		item := models.MemoryDIMM{
			Slot:            mem.MemModSlot,
			Location:        mem.MemModSlot,
			Present:         mem.MemModStatus == 1 && mem.MemModSize > 0,
			SizeMB:          mem.MemModSize * 1024, // Convert GB to MB
			Type:            mem.MemModType,
			Technology:      strings.TrimSpace(mem.MemModTechnology),
			MaxSpeedMHz:     mem.MemModFrequency,
			CurrentSpeedMHz: mem.MemModCurrentFreq,
			Manufacturer:    mem.MemModVendor,
			SerialNumber:    mem.MemModSerial,
			PartNumber:      strings.TrimSpace(mem.MemModPartNum),
			Status:          mem.Status,
			Ranks:           mem.MemModRanks,
		}
		key := inspurMemoryKey(item)
		if idx, ok := seen[key]; ok {
			mergeInspurMemoryDIMM(&merged[idx], item)
			continue
		}
		if key != "" {
			seen[key] = len(merged)
		}
		merged = append(merged, item)
	}
	hw.Memory = merged
}

// PSURESTInfo represents the RESTful PSU info structure
type PSURESTInfo struct {
	PowerSupplies []struct {
		ID         int     `json:"id"`
		Present    int     `json:"present"`
		VendorID   string  `json:"vendor_id"`
		Model      string  `json:"model"`
		SerialNum  string  `json:"serial_num"`
		PartNum    string  `json:"part_num"`
		FwVer      string  `json:"fw_ver"`
		InputType  string  `json:"input_type"`
		Status     string  `json:"status"`
		RatedPower int     `json:"rated_power"`
		PSInPower  int     `json:"ps_in_power"`
		PSOutPower int     `json:"ps_out_power"`
		PSInVolt   float64 `json:"ps_in_volt"`
		PSOutVolt  float64 `json:"ps_out_volt"`
		PSUMaxTemp int     `json:"psu_max_temperature"`
	} `json:"power_supplies"`
	PresentPowerReading int `json:"present_power_reading"`
}

func parsePSUInfo(text string, hw *models.HardwareConfig) {
	// Find RESTful PSU info section
	re := regexp.MustCompile(`RESTful PSU info:\s*(\{[\s\S]*?\})\s*RESTful Network`)
	match := re.FindStringSubmatch(text)
	if match == nil {
		return
	}

	jsonStr := match[1]
	jsonStr = strings.ReplaceAll(jsonStr, "\n", "")

	var psuInfo PSURESTInfo
	if err := json.Unmarshal([]byte(jsonStr), &psuInfo); err != nil {
		return
	}

	var merged []models.PSU
	seen := make(map[string]int)
	for _, existing := range hw.PowerSupply {
		key := inspurPSUKey(existing)
		if key == "" {
			continue
		}
		seen[key] = len(merged)
		merged = append(merged, existing)
	}
	for _, psu := range psuInfo.PowerSupplies {
		item := models.PSU{
			Slot:          fmt.Sprintf("PSU%d", psu.ID),
			Present:       psu.Present == 1,
			Model:         strings.TrimSpace(psu.Model),
			Vendor:        strings.TrimSpace(psu.VendorID),
			WattageW:      psu.RatedPower,
			SerialNumber:  strings.TrimSpace(psu.SerialNum),
			PartNumber:    strings.TrimSpace(psu.PartNum),
			Firmware:      psu.FwVer,
			Status:        psu.Status,
			InputType:     psu.InputType,
			InputPowerW:   psu.PSInPower,
			OutputPowerW:  psu.PSOutPower,
			InputVoltage:  psu.PSInVolt,
			OutputVoltage: psu.PSOutVolt,
			TemperatureC:  psu.PSUMaxTemp,
		}
		key := inspurPSUKey(item)
		if idx, ok := seen[key]; ok {
			mergeInspurPSU(&merged[idx], item)
			continue
		}
		if key != "" {
			seen[key] = len(merged)
		}
		merged = append(merged, item)
	}
	hw.PowerSupply = merged
}

// HDDRESTInfo represents the RESTful HDD info structure
type HDDRESTInfo []struct {
	ID             int    `json:"id"`
	Present        int    `json:"present"`
	Enable         int    `json:"enable"`
	SN             string `json:"SN"`
	Model          string `json:"model"`
	Capacity       int    `json:"capacity"`
	Manufacture    string `json:"manufacture"`
	Firmware       string `json:"firmware"`
	LocationString string `json:"locationstring"`
	CapableSpeed   int    `json:"capablespeed"`
}

func parseHDDInfo(text string, hw *models.HardwareConfig) {
	// Find RESTful HDD info section
	re := regexp.MustCompile(`RESTful HDD info:\s*(\[[\s\S]*?\])\s*RESTful PSU`)
	match := re.FindStringSubmatch(text)
	if match == nil {
		return
	}

	jsonStr := match[1]
	jsonStr = strings.ReplaceAll(jsonStr, "\n", "")

	var hddInfo HDDRESTInfo
	if err := json.Unmarshal([]byte(jsonStr), &hddInfo); err != nil {
		return
	}

	// Update storage with detailed info (merge with existing data from asset.json)
	hddMap := make(map[string]struct {
		SN       string
		Model    string
		Firmware string
		Mfr      string
	})
	for _, hdd := range hddInfo {
		if hdd.Present == 1 {
			slot := strings.TrimSpace(hdd.LocationString)
			if slot == "" {
				slot = fmt.Sprintf("HDD%d", hdd.ID)
			}
			hddMap[slot] = struct {
				SN       string
				Model    string
				Firmware string
				Mfr      string
			}{
				SN:       normalizeRedisValue(hdd.SN),
				Model:    strings.TrimSpace(hdd.Model),
				Firmware: normalizeRedisValue(hdd.Firmware),
				Mfr:      strings.TrimSpace(hdd.Manufacture),
			}
		}
	}

	// Merge into existing inventory first (asset/other sections).
	for i := range hw.Storage {
		slot := strings.TrimSpace(hw.Storage[i].Slot)
		if slot == "" {
			continue
		}
		detail, ok := hddMap[slot]
		if !ok {
			continue
		}
		if normalizeRedisValue(hw.Storage[i].SerialNumber) == "" {
			hw.Storage[i].SerialNumber = detail.SN
		}
		if hw.Storage[i].Model == "" {
			hw.Storage[i].Model = detail.Model
		}
		if normalizeRedisValue(hw.Storage[i].Firmware) == "" {
			hw.Storage[i].Firmware = detail.Firmware
		}
		if hw.Storage[i].Manufacturer == "" {
			hw.Storage[i].Manufacturer = detail.Mfr
		}
		hw.Storage[i].Present = true
	}

	// If storage is empty, populate from HDD info
	if len(hw.Storage) == 0 {
		for _, hdd := range hddInfo {
			if hdd.Present != 1 {
				continue
			}
			storType := "HDD"
			model := strings.TrimSpace(hdd.Model)
			if strings.Contains(strings.ToUpper(model), "SSD") || strings.Contains(model, "MZ7") {
				storType = "SSD"
			}

			iface := "SATA"
			if hdd.CapableSpeed == 12 {
				iface = "SAS"
			}
			slot := strings.TrimSpace(hdd.LocationString)
			if slot == "" {
				slot = fmt.Sprintf("HDD%d", hdd.ID)
			}

			hw.Storage = append(hw.Storage, models.Storage{
				Slot:         slot,
				Type:         storType,
				Model:        model,
				SizeGB:       hdd.Capacity,
				SerialNumber: normalizeRedisValue(hdd.SN),
				Manufacturer: extractStorageManufacturer(model),
				Firmware:     normalizeRedisValue(hdd.Firmware),
				Interface:    iface,
				Present:      true,
			})
		}
	}
}

// FanRESTInfo represents the RESTful fan info structure.
type FanRESTInfo struct {
	Fans []struct {
		ID           int    `json:"id"`
		FanName      string `json:"fan_name"`
		Present      string `json:"present"`
		Status       string `json:"status"`
		StatusStr    string `json:"status_str"`
		SpeedRPM     int    `json:"speed_rpm"`
		SpeedPercent int    `json:"speed_percent"`
		MaxSpeedRPM  int    `json:"max_speed_rpm"`
		FanModel     string `json:"fan_model"`
	} `json:"fans"`
	FansPower int `json:"fans_power"`
}

// NetworkAdapterRESTInfo represents the RESTful Network Adapter info structure
type NetworkAdapterRESTInfo struct {
	SysAdapters []struct {
		ID       int    `json:"id"`
		Name     string `json:"name"`
		Location string `json:"Location"`
		Present  int    `json:"present"`
		Slot     int    `json:"slot"`
		VendorID int    `json:"vendor_id"`
		DeviceID int    `json:"device_id"`
		Vendor   string `json:"vendor"`
		Model    string `json:"model"`
		FwVer    string `json:"fw_ver"`
		Status   string `json:"status"`
		SN       string `json:"sn"`
		PN       string `json:"pn"`
		PortNum  int    `json:"port_num"`
		PortType string `json:"port_type"`
		Ports    []struct {
			ID      int    `json:"id"`
			MacAddr string `json:"mac_addr"`
		} `json:"ports"`
	} `json:"sys_adapters"`
}

func parseNetworkAdapterInfo(text string, hw *models.HardwareConfig) {
	// Find RESTful Network Adapter info section
	re := regexp.MustCompile(`RESTful Network Adapter info:\s*(\{[\s\S]*?\})\s*RESTful fan`)
	match := re.FindStringSubmatch(text)
	if match == nil {
		return
	}

	jsonStr := match[1]
	jsonStr = strings.ReplaceAll(jsonStr, "\n", "")

	var netInfo NetworkAdapterRESTInfo
	if err := json.Unmarshal([]byte(jsonStr), &netInfo); err != nil {
		return
	}

	var merged []models.NetworkAdapter
	seen := make(map[string]int)
	for _, existing := range hw.NetworkAdapters {
		key := inspurNICKey(existing)
		if key == "" {
			continue
		}
		seen[key] = len(merged)
		merged = append(merged, existing)
	}
	for _, adapter := range netInfo.SysAdapters {
		var macs []string
		for _, port := range adapter.Ports {
			if port.MacAddr != "" {
				macs = append(macs, port.MacAddr)
			}
		}

		model := normalizeModelLabel(adapter.Model)
		if model == "" || looksLikeRawDeviceID(model) {
			if resolved := normalizeModelLabel(pciids.DeviceName(adapter.VendorID, adapter.DeviceID)); resolved != "" {
				model = resolved
			}
		}
		vendor := normalizeModelLabel(adapter.Vendor)
		if vendor == "" {
			vendor = normalizeModelLabel(pciids.VendorName(adapter.VendorID))
		}

		item := models.NetworkAdapter{
			Slot:         fmt.Sprintf("Slot %d", adapter.Slot),
			Location:     adapter.Location,
			Present:      adapter.Present == 1,
			Model:        model,
			Vendor:       vendor,
			VendorID:     adapter.VendorID,
			DeviceID:     adapter.DeviceID,
			SerialNumber: normalizeRedisValue(adapter.SN),
			PartNumber:   normalizeRedisValue(adapter.PN),
			Firmware:     normalizeRedisValue(adapter.FwVer),
			PortCount:    adapter.PortNum,
			PortType:     adapter.PortType,
			MACAddresses: macs,
			Status:       adapter.Status,
		}
		key := inspurNICKey(item)
		if idx, ok := seen[key]; ok {
			mergeInspurNIC(&merged[idx], item)
			continue
		}
		if slotIdx := inspurFindNICBySlot(merged, item.Slot); slotIdx >= 0 {
			mergeInspurNIC(&merged[slotIdx], item)
			if key != "" {
				seen[key] = slotIdx
			}
			continue
		}
		if key != "" {
			seen[key] = len(merged)
		}
		merged = append(merged, item)
	}
	hw.NetworkAdapters = merged
}

func inspurMemoryKey(item models.MemoryDIMM) string {
	return strings.ToLower(strings.TrimSpace(inspurFirstNonEmpty(item.SerialNumber, item.Slot, item.Location)))
}

func mergeInspurMemoryDIMM(dst *models.MemoryDIMM, src models.MemoryDIMM) {
	if dst == nil {
		return
	}
	if strings.TrimSpace(dst.Slot) == "" {
		dst.Slot = src.Slot
	}
	if strings.TrimSpace(dst.Location) == "" {
		dst.Location = src.Location
	}
	dst.Present = dst.Present || src.Present
	if dst.SizeMB == 0 {
		dst.SizeMB = src.SizeMB
	}
	if strings.TrimSpace(dst.Type) == "" {
		dst.Type = src.Type
	}
	if strings.TrimSpace(dst.Technology) == "" {
		dst.Technology = src.Technology
	}
	if dst.MaxSpeedMHz == 0 {
		dst.MaxSpeedMHz = src.MaxSpeedMHz
	}
	if dst.CurrentSpeedMHz == 0 {
		dst.CurrentSpeedMHz = src.CurrentSpeedMHz
	}
	if strings.TrimSpace(dst.Manufacturer) == "" {
		dst.Manufacturer = src.Manufacturer
	}
	if strings.TrimSpace(dst.SerialNumber) == "" {
		dst.SerialNumber = src.SerialNumber
	}
	if strings.TrimSpace(dst.PartNumber) == "" {
		dst.PartNumber = src.PartNumber
	}
	if strings.TrimSpace(dst.Status) == "" {
		dst.Status = src.Status
	}
	if dst.Ranks == 0 {
		dst.Ranks = src.Ranks
	}
}

func inspurPSUKey(item models.PSU) string {
	return strings.ToLower(strings.TrimSpace(inspurFirstNonEmpty(item.SerialNumber, item.Slot, item.Model)))
}

func mergeInspurPSU(dst *models.PSU, src models.PSU) {
	if dst == nil {
		return
	}
	if strings.TrimSpace(dst.Slot) == "" {
		dst.Slot = src.Slot
	}
	dst.Present = dst.Present || src.Present
	if strings.TrimSpace(dst.Model) == "" {
		dst.Model = src.Model
	}
	if strings.TrimSpace(dst.Vendor) == "" {
		dst.Vendor = src.Vendor
	}
	if dst.WattageW == 0 {
		dst.WattageW = src.WattageW
	}
	if strings.TrimSpace(dst.SerialNumber) == "" {
		dst.SerialNumber = src.SerialNumber
	}
	if strings.TrimSpace(dst.PartNumber) == "" {
		dst.PartNumber = src.PartNumber
	}
	if strings.TrimSpace(dst.Firmware) == "" {
		dst.Firmware = src.Firmware
	}
	if strings.TrimSpace(dst.Status) == "" {
		dst.Status = src.Status
	}
	if strings.TrimSpace(dst.InputType) == "" {
		dst.InputType = src.InputType
	}
	if dst.InputPowerW == 0 {
		dst.InputPowerW = src.InputPowerW
	}
	if dst.OutputPowerW == 0 {
		dst.OutputPowerW = src.OutputPowerW
	}
	if dst.InputVoltage == 0 {
		dst.InputVoltage = src.InputVoltage
	}
	if dst.OutputVoltage == 0 {
		dst.OutputVoltage = src.OutputVoltage
	}
	if dst.TemperatureC == 0 {
		dst.TemperatureC = src.TemperatureC
	}
}

func inspurNICKey(item models.NetworkAdapter) string {
	return strings.ToLower(strings.TrimSpace(inspurFirstNonEmpty(item.SerialNumber, strings.Join(item.MACAddresses, ","), item.Slot, item.Location)))
}

func mergeInspurNIC(dst *models.NetworkAdapter, src models.NetworkAdapter) {
	if dst == nil {
		return
	}
	if strings.TrimSpace(dst.Slot) == "" {
		dst.Slot = src.Slot
	}
	if strings.TrimSpace(dst.Location) == "" {
		dst.Location = src.Location
	}
	dst.Present = dst.Present || src.Present
	if strings.TrimSpace(dst.BDF) == "" {
		dst.BDF = src.BDF
	}
	if strings.TrimSpace(dst.Model) == "" {
		dst.Model = src.Model
	}
	if strings.TrimSpace(dst.Description) == "" {
		dst.Description = src.Description
	}
	if strings.TrimSpace(dst.Vendor) == "" {
		dst.Vendor = src.Vendor
	}
	if dst.VendorID == 0 {
		dst.VendorID = src.VendorID
	}
	if dst.DeviceID == 0 {
		dst.DeviceID = src.DeviceID
	}
	if strings.TrimSpace(dst.SerialNumber) == "" {
		dst.SerialNumber = src.SerialNumber
	}
	if strings.TrimSpace(dst.PartNumber) == "" {
		dst.PartNumber = src.PartNumber
	}
	if strings.TrimSpace(dst.Firmware) == "" {
		dst.Firmware = src.Firmware
	}
	if dst.PortCount == 0 {
		dst.PortCount = src.PortCount
	}
	if strings.TrimSpace(dst.PortType) == "" {
		dst.PortType = src.PortType
	}
	if dst.LinkWidth == 0 {
		dst.LinkWidth = src.LinkWidth
	}
	if strings.TrimSpace(dst.LinkSpeed) == "" {
		dst.LinkSpeed = src.LinkSpeed
	}
	if dst.MaxLinkWidth == 0 {
		dst.MaxLinkWidth = src.MaxLinkWidth
	}
	if strings.TrimSpace(dst.MaxLinkSpeed) == "" {
		dst.MaxLinkSpeed = src.MaxLinkSpeed
	}
	if dst.NUMANode == 0 {
		dst.NUMANode = src.NUMANode
	}
	if strings.TrimSpace(dst.Status) == "" {
		dst.Status = src.Status
	}
	for _, mac := range src.MACAddresses {
		mac = strings.TrimSpace(mac)
		if mac == "" {
			continue
		}
		found := false
		for _, existing := range dst.MACAddresses {
			if strings.EqualFold(strings.TrimSpace(existing), mac) {
				found = true
				break
			}
		}
		if !found {
			dst.MACAddresses = append(dst.MACAddresses, mac)
		}
	}
}

func inspurFindNICBySlot(items []models.NetworkAdapter, slot string) int {
	slot = strings.ToLower(strings.TrimSpace(slot))
	if slot == "" {
		return -1
	}
	for i := range items {
		if strings.ToLower(strings.TrimSpace(items[i].Slot)) == slot {
			return i
		}
	}
	return -1
}

func inspurFirstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}

func parseFanSensors(text string) []models.SensorReading {
	re := regexp.MustCompile(`RESTful fan info:\s*(\{[\s\S]*?\})\s*RESTful diskbackplane`)
	match := re.FindStringSubmatch(text)
	if match == nil {
		return nil
	}

	jsonStr := strings.ReplaceAll(match[1], "\n", "")
	var fanInfo FanRESTInfo
	if err := json.Unmarshal([]byte(jsonStr), &fanInfo); err != nil {
		return nil
	}

	out := make([]models.SensorReading, 0, len(fanInfo.Fans)+1)
	for _, fan := range fanInfo.Fans {
		name := strings.TrimSpace(fan.FanName)
		if name == "" {
			name = fmt.Sprintf("FAN%d", fan.ID)
		}
		status := normalizeComponentStatus(fan.StatusStr, fan.Status, fan.Present)
		raw := fmt.Sprintf("rpm=%d pct=%d model=%s max_rpm=%d", fan.SpeedRPM, fan.SpeedPercent, fan.FanModel, fan.MaxSpeedRPM)
		out = append(out, models.SensorReading{
			Name:     name,
			Type:     "fan_speed",
			Value:    float64(fan.SpeedRPM),
			Unit:     "RPM",
			RawValue: raw,
			Status:   status,
		})
	}

	if fanInfo.FansPower > 0 {
		out = append(out, models.SensorReading{
			Name:     "Fans_Power",
			Type:     "power",
			Value:    float64(fanInfo.FansPower),
			Unit:     "W",
			RawValue: fmt.Sprintf("%d", fanInfo.FansPower),
			Status:   "OK",
		})
	}

	return out
}

func parseFanEvents(text string) []models.Event {
	re := regexp.MustCompile(`RESTful fan info:\s*(\{[\s\S]*?\})\s*RESTful diskbackplane`)
	match := re.FindStringSubmatch(text)
	if match == nil {
		return nil
	}

	jsonStr := strings.ReplaceAll(match[1], "\n", "")
	var fanInfo FanRESTInfo
	if err := json.Unmarshal([]byte(jsonStr), &fanInfo); err != nil {
		return nil
	}

	var events []models.Event
	for _, fan := range fanInfo.Fans {
		status := normalizeComponentStatus(fan.StatusStr, fan.Status, fan.Present)
		if isHealthyComponentStatus(status) {
			continue
		}

		name := strings.TrimSpace(fan.FanName)
		if name == "" {
			name = fmt.Sprintf("FAN%d", fan.ID)
		}

		severity := models.SeverityWarning
		lowStatus := strings.ToLower(status)
		if strings.Contains(lowStatus, "critical") || strings.Contains(lowStatus, "fail") || strings.Contains(lowStatus, "error") {
			severity = models.SeverityCritical
		}

		events = append(events, models.Event{
			ID:          fmt.Sprintf("fan_%d_status", fan.ID),
			Timestamp:   time.Now(),
			Source:      "Fan",
			SensorType:  "fan",
			SensorName:  name,
			EventType:   "Fan Status",
			Severity:    severity,
			Description: fmt.Sprintf("%s reports %s", name, status),
			RawData:     fmt.Sprintf("rpm=%d pct=%d model=%s", fan.SpeedRPM, fan.SpeedPercent, fan.FanModel),
		})
	}

	return events
}

func parseDiskBackplaneSensors(text string) []models.SensorReading {
	re := regexp.MustCompile(`RESTful diskbackplane info:\s*(\[[\s\S]*?\])\s*BMC`)
	match := re.FindStringSubmatch(text)
	if match == nil {
		return nil
	}

	jsonStr := strings.ReplaceAll(match[1], "\n", "")
	var backplaneInfo DiskBackplaneRESTInfo
	if err := json.Unmarshal([]byte(jsonStr), &backplaneInfo); err != nil {
		return nil
	}

	out := make([]models.SensorReading, 0, len(backplaneInfo))
	for _, bp := range backplaneInfo {
		if bp.Present != 1 {
			continue
		}
		name := fmt.Sprintf("Backplane%d_Temp", bp.BackplaneIndex)
		status := "OK"
		if bp.Temperature <= 0 {
			status = "unknown"
		}
		raw := fmt.Sprintf("front=%d ports=%d drives=%d cpld=%s", bp.Front, bp.PortCount, bp.DriverCount, bp.CPLDVersion)
		out = append(out, models.SensorReading{
			Name:     name,
			Type:     "temperature",
			Value:    float64(bp.Temperature),
			Unit:     "C",
			RawValue: raw,
			Status:   status,
		})
	}
	return out
}

func parsePSUSummarySensors(text string) []models.SensorReading {
	re := regexp.MustCompile(`RESTful PSU info:\s*(\{[\s\S]*?\})\s*RESTful Network`)
	match := re.FindStringSubmatch(text)
	if match == nil {
		return nil
	}

	jsonStr := strings.ReplaceAll(match[1], "\n", "")
	var psuInfo PSURESTInfo
	if err := json.Unmarshal([]byte(jsonStr), &psuInfo); err != nil {
		return nil
	}

	out := make([]models.SensorReading, 0, len(psuInfo.PowerSupplies)*3+1)
	if psuInfo.PresentPowerReading > 0 {
		out = append(out, models.SensorReading{
			Name:     "PSU_Present_Power_Reading",
			Type:     "power",
			Value:    float64(psuInfo.PresentPowerReading),
			Unit:     "W",
			RawValue: fmt.Sprintf("%d", psuInfo.PresentPowerReading),
			Status:   "OK",
		})
	}

	for _, psu := range psuInfo.PowerSupplies {
		if psu.Present != 1 {
			continue
		}
		status := normalizeComponentStatus(psu.Status)
		out = append(out, models.SensorReading{
			Name:     fmt.Sprintf("PSU%d_InputPower", psu.ID),
			Type:     "power",
			Value:    float64(psu.PSInPower),
			Unit:     "W",
			RawValue: fmt.Sprintf("%d", psu.PSInPower),
			Status:   status,
		})
		out = append(out, models.SensorReading{
			Name:     fmt.Sprintf("PSU%d_OutputPower", psu.ID),
			Type:     "power",
			Value:    float64(psu.PSOutPower),
			Unit:     "W",
			RawValue: fmt.Sprintf("%d", psu.PSOutPower),
			Status:   status,
		})
		out = append(out, models.SensorReading{
			Name:     fmt.Sprintf("PSU%d_Temp", psu.ID),
			Type:     "temperature",
			Value:    float64(psu.PSUMaxTemp),
			Unit:     "C",
			RawValue: fmt.Sprintf("%d", psu.PSUMaxTemp),
			Status:   status,
		})
	}

	return out
}

func normalizeComponentStatus(values ...string) string {
	for _, v := range values {
		s := strings.TrimSpace(v)
		if s == "" {
			continue
		}
		return s
	}
	return "unknown"
}

func isHealthyComponentStatus(status string) bool {
	switch strings.ToLower(strings.TrimSpace(status)) {
	case "", "ok", "normal", "present", "enabled":
		return true
	default:
		return false
	}
}

var rawDeviceIDLikeRegex = regexp.MustCompile(`(?i)^(?:0x)?[0-9a-f]{3,4}$`)

func looksLikeRawDeviceID(v string) bool {
	v = strings.TrimSpace(v)
	if v == "" {
		return true
	}
	return rawDeviceIDLikeRegex.MatchString(v)
}

func parseMemoryEvents(text string) []models.Event {
	var events []models.Event

	// Find RESTful Memory info section
	re := regexp.MustCompile(`RESTful Memory info:\s*(\{[\s\S]*?\})\s*RESTful HDD`)
	match := re.FindStringSubmatch(text)
	if match == nil {
		return events
	}

	jsonStr := match[1]
	jsonStr = strings.ReplaceAll(jsonStr, "\n", "")

	var memInfo MemoryRESTInfo
	if err := json.Unmarshal([]byte(jsonStr), &memInfo); err != nil {
		return events
	}

	// Generate events for memory modules with Warning or Error status
	for _, mem := range memInfo.MemModules {
		if mem.Status == "Warning" || mem.Status == "Error" || mem.Status == "Critical" {
			severity := models.SeverityWarning
			if mem.Status == "Error" || mem.Status == "Critical" {
				severity = models.SeverityCritical
			}

			description := fmt.Sprintf("Memory module %s: %s", mem.MemModSlot, mem.Status)
			if mem.MemModSize == 0 {
				description = fmt.Sprintf("Memory module %s not detected (capacity 0GB)", mem.MemModSlot)
			}

			events = append(events, models.Event{
				ID:          fmt.Sprintf("mem_%d", mem.MemModID),
				Timestamp:   time.Now(),
				Source:      "Memory",
				SensorType:  "memory",
				SensorName:  mem.MemModSlot,
				EventType:   "Memory Status",
				Severity:    severity,
				Description: description,
				RawData:     fmt.Sprintf("Slot: %s, Vendor: %s, P/N: %s, S/N: %s", mem.MemModSlot, mem.MemModVendor, mem.MemModPartNum, mem.MemModSerial),
			})
		}
	}

	return events
}

// extractComponentFirmware extracts firmware versions from all component data
func extractComponentFirmware(text string, hw *models.HardwareConfig) {
	// Create a map to track existing firmware entries (avoid duplicates)
	existingFW := make(map[string]bool)
	for _, fw := range hw.Firmware {
		existingFW[fw.DeviceName] = true
	}

	// HDD firmware is already extracted from asset.json with better names
	// Skip extracting from component.log to avoid duplicates

	// Extract PSU firmware from RESTful PSU info
	rePSU := regexp.MustCompile(`RESTful PSU info:\s*(\{[\s\S]*?\})\s*RESTful Network`)
	if match := rePSU.FindStringSubmatch(text); match != nil {
		jsonStr := strings.ReplaceAll(match[1], "\n", "")
		var psuInfo PSURESTInfo
		if err := json.Unmarshal([]byte(jsonStr), &psuInfo); err == nil {
			for _, psu := range psuInfo.PowerSupplies {
				if psu.Present == 1 && psu.FwVer != "" {
					fwName := fmt.Sprintf("PSU%d (%s)", psu.ID, psu.Model)
					if !existingFW[fwName] {
						hw.Firmware = append(hw.Firmware, models.FirmwareInfo{
							DeviceName: fwName,
							Version:    psu.FwVer,
						})
						existingFW[fwName] = true
					}
				}
			}
		}
	}

	// Extract Network Adapter firmware from RESTful Network Adapter info
	reNet := regexp.MustCompile(`RESTful Network Adapter info:\s*(\{[\s\S]*?\})\s*RESTful fan`)
	if match := reNet.FindStringSubmatch(text); match != nil {
		jsonStr := strings.ReplaceAll(match[1], "\n", "")
		var netInfo NetworkAdapterRESTInfo
		if err := json.Unmarshal([]byte(jsonStr), &netInfo); err == nil {
			for _, adapter := range netInfo.SysAdapters {
				if adapter.Present == 1 && adapter.FwVer != "" && adapter.FwVer != "NA" {
					fwName := fmt.Sprintf("NIC %s (%s)", adapter.Location, adapter.Model)
					if !existingFW[fwName] {
						hw.Firmware = append(hw.Firmware, models.FirmwareInfo{
							DeviceName: fwName,
							Version:    adapter.FwVer,
						})
						existingFW[fwName] = true
					}
				}
			}
		}
	}

	// Extract BMC, CPLD and VR firmware from RESTful version info section.
	// The JSON is a flat array: [{"id":N,"dev_name":"...","dev_version":"..."}, ...]
	reVer := regexp.MustCompile(`RESTful version info:\s*(\[[\s\S]*?\])\s*RESTful`)
	if match := reVer.FindStringSubmatch(text); match != nil {
		type verEntry struct {
			DevName    string `json:"dev_name"`
			DevVersion string `json:"dev_version"`
		}
		var entries []verEntry
		if err := json.Unmarshal([]byte(match[1]), &entries); err == nil {
			for _, e := range entries {
				name := normalizeVersionInfoName(e.DevName)
				if name == "" {
					continue
				}
				version := strings.TrimSpace(e.DevVersion)
				if version == "" {
					continue
				}
				if existingFW[name] {
					continue
				}
				hw.Firmware = append(hw.Firmware, models.FirmwareInfo{
					DeviceName: name,
					Version:    version,
				})
				existingFW[name] = true
			}
		}
	}
}

// normalizeVersionInfoName converts RESTful version info dev_name to a clean label.
// Returns "" for entries that should be skipped (inactive BMC, PSU slots).
func normalizeVersionInfoName(name string) string {
	name = strings.TrimSpace(name)
	if name == "" {
		return ""
	}
	// Skip PSU_N entries — firmware already extracted from PSU info section.
	if regexp.MustCompile(`(?i)^PSU_\d+$`).MatchString(name) {
		return ""
	}
	// Skip the inactive BMC partition.
	if strings.HasPrefix(strings.ToLower(name), "inactivate(") {
		return ""
	}
	// Active BMC: "Activate(BMC1)" → "BMC"
	if strings.HasPrefix(strings.ToLower(name), "activate(") {
		return "BMC"
	}
	// Strip trailing "Version" suffix (case-insensitive), e.g. "MainBoard0CPLDVersion" → "MainBoard0CPLD"
	if strings.HasSuffix(strings.ToLower(name), "version") {
		name = name[:len(name)-len("version")]
	}
	return strings.TrimSpace(name)
}

// DiskBackplaneRESTInfo represents the RESTful diskbackplane info structure
type DiskBackplaneRESTInfo []struct {
	PortCount      int    `json:"port_count"`
	DriverCount    int    `json:"driver_count"`
	Front          int    `json:"front"`
	BackplaneIndex int    `json:"backplane_index"`
	Present        int    `json:"present"`
	CPLDVersion    string `json:"cpld_version"`
	Temperature    int    `json:"temperature"`
}

func parseDiskBackplaneInfo(text string, hw *models.HardwareConfig) {
	// Find RESTful diskbackplane info section
	re := regexp.MustCompile(`RESTful diskbackplane info:\s*(\[[\s\S]*?\])\s*BMC`)
	match := re.FindStringSubmatch(text)
	if match == nil {
		return
	}

	jsonStr := match[1]
	jsonStr = strings.ReplaceAll(jsonStr, "\n", "")

	var backplaneInfo DiskBackplaneRESTInfo
	if err := json.Unmarshal([]byte(jsonStr), &backplaneInfo); err != nil {
		return
	}

	presentByBackplane := make(map[int]int)
	totalPresent := 0
	for _, bp := range backplaneInfo {
		if bp.Present != 1 {
			continue
		}
		if bp.DriverCount <= 0 {
			continue
		}
		limit := bp.DriverCount
		if bp.PortCount > 0 && limit > bp.PortCount {
			limit = bp.PortCount
		}
		presentByBackplane[bp.BackplaneIndex] = limit
		totalPresent += limit
	}

	if totalPresent == 0 {
		return
	}

	existingPresent := countPresentStorage(hw.Storage)
	remaining := totalPresent - existingPresent
	if remaining <= 0 {
		return
	}

	for _, bp := range backplaneInfo {
		if bp.Present != 1 || remaining <= 0 {
			continue
		}
		driveCount := presentByBackplane[bp.BackplaneIndex]
		if driveCount <= 0 {
			continue
		}

		location := "Rear"
		if bp.Front == 1 {
			location = "Front"
		}

		for i := 0; i < driveCount && remaining > 0; i++ {
			slot := fmt.Sprintf("BP%d:%d", bp.BackplaneIndex, i)
			if hasStorageSlot(hw.Storage, slot) {
				continue
			}

			hw.Storage = append(hw.Storage, models.Storage{
				Slot:        slot,
				Present:     true,
				Location:    location,
				BackplaneID: bp.BackplaneIndex,
				Type:        "HDD",
			})
			remaining--
		}
	}
}

func countPresentStorage(storage []models.Storage) int {
	count := 0
	for _, dev := range storage {
		if dev.Present {
			count++
			continue
		}
		if strings.TrimSpace(dev.Slot) != "" && (normalizeRedisValue(dev.Model) != "" || normalizeRedisValue(dev.SerialNumber) != "" || dev.SizeGB > 0) {
			count++
		}
	}
	return count
}

func hasStorageSlot(storage []models.Storage, slot string) bool {
	slot = strings.ToLower(strings.TrimSpace(slot))
	if slot == "" {
		return false
	}
	for _, dev := range storage {
		if strings.ToLower(strings.TrimSpace(dev.Slot)) == slot {
			return true
		}
	}
	return false
}
