package inspur

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

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

// PCIeRESTInfo represents the RESTful PCIE Device info structure
type PCIeRESTInfo []struct {
	ID               int    `json:"id"`
	Present          int    `json:"present"`
	Enable           int    `json:"enable"`
	Status           int    `json:"status"`
	VendorID         int    `json:"vendor_id"`
	VendorName       string `json:"vendor_name"`
	DeviceID         int    `json:"device_id"`
	DeviceName       string `json:"device_name"`
	BusNum           int    `json:"bus_num"`
	DevNum           int    `json:"dev_num"`
	FuncNum          int    `json:"func_num"`
	MaxLinkWidth     int    `json:"max_link_width"`
	MaxLinkSpeed     int    `json:"max_link_speed"`
	CurrentLinkWidth int    `json:"current_link_width"`
	CurrentLinkSpeed int    `json:"current_link_speed"`
	Slot             int    `json:"slot"`
	Location         string `json:"location"`
	DeviceLocator    string `json:"DeviceLocator"`
	DevType          int    `json:"dev_type"`
	DevSubtype       int    `json:"dev_subtype"`
	PartNum          string `json:"part_num"`
	SerialNum        string `json:"serial_num"`
	FwVer            string `json:"fw_ver"`
}

// ParsePCIeSlotDeviceNames parses devicefrusdr.log and returns a map from integer PCIe slot ID
// to device name string. Used to enrich HddInfo entries in asset.json that lack model names.
func ParsePCIeSlotDeviceNames(content []byte) map[int]string {
	info, ok := parsePCIeRESTJSON(content)
	if !ok {
		return nil
	}
	result := make(map[int]string, len(info))
	for _, entry := range info {
		if entry.Slot <= 0 {
			continue
		}
		name := sanitizePCIeDeviceName(entry.DeviceName)
		if name != "" {
			result[entry.Slot] = name
		}
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

// parsePCIeRESTJSON parses the RESTful PCIE Device info JSON from devicefrusdr.log content.
func parsePCIeRESTJSON(content []byte) (PCIeRESTInfo, bool) {
	text := string(content)
	startMarker := "RESTful PCIE Device info:"
	endMarker := "BMC sdr Info:"

	startIdx := strings.Index(text, startMarker)
	if startIdx == -1 {
		return nil, false
	}
	endIdx := strings.Index(text[startIdx:], endMarker)
	if endIdx == -1 {
		endIdx = len(text) - startIdx
	}
	jsonText := strings.TrimSpace(text[startIdx+len(startMarker) : startIdx+endIdx])

	var info PCIeRESTInfo
	if err := json.Unmarshal([]byte(jsonText), &info); err != nil {
		return nil, false
	}
	return info, true
}

// ParsePCIeNVMeLocToSlot parses devicefrusdr.log and returns a map from NVMe location number
// (the numeric suffix in "#NVME0", "#NVME2", etc.) to the integer PCIe slot ID.
// This is used to correlate audit.log NVMe disk numbers with HddInfo PcieSlot values.
func ParsePCIeNVMeLocToSlot(content []byte) map[int]int {
	info, ok := parsePCIeRESTJSON(content)
	if !ok {
		return nil
	}

	nvmeLocRegex := regexp.MustCompile(`(?i)^#NVME(\d+)$`)
	result := make(map[int]int)
	for _, entry := range info {
		if entry.Slot <= 0 {
			continue
		}
		loc := strings.TrimSpace(entry.Location)
		m := nvmeLocRegex.FindStringSubmatch(loc)
		if m == nil {
			continue
		}
		locNum, err := strconv.Atoi(m[1])
		if err != nil {
			continue
		}
		result[locNum] = entry.Slot
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

// ParsePCIeDevices parses RESTful PCIE Device info from devicefrusdr.log
func ParsePCIeDevices(content []byte) []models.PCIeDevice {
	text := string(content)

	// Find RESTful PCIE Device info section
	startMarker := "RESTful PCIE Device info:"
	endMarker := "BMC sdr Info:"

	startIdx := strings.Index(text, startMarker)
	if startIdx == -1 {
		return nil
	}

	endIdx := strings.Index(text[startIdx:], endMarker)
	if endIdx == -1 {
		endIdx = len(text) - startIdx
	}

	jsonText := text[startIdx+len(startMarker) : startIdx+endIdx]
	jsonText = strings.TrimSpace(jsonText)

	var pcieInfo PCIeRESTInfo
	if err := json.Unmarshal([]byte(jsonText), &pcieInfo); err != nil {
		return nil
	}

	var devices []models.PCIeDevice
	for _, pcie := range pcieInfo {
		if pcie.Present != 1 {
			continue
		}

		// Convert PCIe speed to GEN notation
		maxSpeed := fmt.Sprintf("GEN%d", pcie.MaxLinkSpeed)
		currentSpeed := fmt.Sprintf("GEN%d", pcie.CurrentLinkSpeed)

		// Determine device class based on dev_type
		deviceClass := determineDeviceClass(pcie.DevType, pcie.DevSubtype, pcie.DeviceName)
		_, pciDeviceName := pciids.DeviceInfo(pcie.VendorID, pcie.DeviceID)

		// Build BDF string in canonical form (bb:dd.f)
		bdf := formatBDF(pcie.BusNum, pcie.DevNum, pcie.FuncNum)

		partNumber := strings.TrimSpace(pcie.PartNum)
		if partNumber == "" {
			partNumber = sanitizePCIeDeviceName(pcie.DeviceName)
		}
		if partNumber == "" {
			partNumber = normalizeModelLabel(pciDeviceName)
		}
		if isGenericPCIeClass(deviceClass) {
			if resolved := normalizeModelLabel(pciDeviceName); resolved != "" {
				deviceClass = resolved
			}
		}
		manufacturer := strings.TrimSpace(pcie.VendorName)
		if manufacturer == "" {
			manufacturer = normalizeModelLabel(pciids.VendorName(pcie.VendorID))
		}

		device := models.PCIeDevice{
			Slot:         pcie.Location,
			VendorID:     pcie.VendorID,
			DeviceID:     pcie.DeviceID,
			BDF:          bdf,
			DeviceClass:  deviceClass,
			Manufacturer: manufacturer,
			LinkWidth:    pcie.CurrentLinkWidth,
			LinkSpeed:    currentSpeed,
			MaxLinkWidth: pcie.MaxLinkWidth,
			MaxLinkSpeed: maxSpeed,
			PartNumber:   partNumber,
			SerialNumber: strings.TrimSpace(pcie.SerialNum),
		}

		devices = append(devices, device)
	}

	return devices
}

var rawHexDeviceNameRegex = regexp.MustCompile(`(?i)^0x[0-9a-f]+$`)

func sanitizePCIeDeviceName(name string) string {
	name = strings.TrimSpace(name)
	if name == "" {
		return ""
	}
	if strings.EqualFold(name, "N/A") {
		return ""
	}
	if rawHexDeviceNameRegex.MatchString(name) {
		return ""
	}
	return name
}

// MergePCIeDevices enriches base devices (from asset.json) with detailed RESTful PCIe data.
// Matching is done by BDF first, then by slot fallback.
func MergePCIeDevices(base []models.PCIeDevice, rest []models.PCIeDevice) []models.PCIeDevice {
	if len(rest) == 0 {
		return base
	}
	if len(base) == 0 {
		return append([]models.PCIeDevice(nil), rest...)
	}

	type ref struct {
		index int
	}
	byBDF := make(map[string]ref, len(base))
	bySlot := make(map[string]ref, len(base))

	for i := range base {
		bdf := normalizePCIeBDF(base[i].BDF)
		if bdf != "" {
			byBDF[bdf] = ref{index: i}
		}
		slot := strings.ToLower(strings.TrimSpace(base[i].Slot))
		if slot != "" {
			bySlot[slot] = ref{index: i}
		}
	}

	for _, detailed := range rest {
		idx := -1
		if bdf := normalizePCIeBDF(detailed.BDF); bdf != "" {
			if found, ok := byBDF[bdf]; ok {
				idx = found.index
			}
		}
		if idx == -1 {
			slot := strings.ToLower(strings.TrimSpace(detailed.Slot))
			if slot != "" {
				if found, ok := bySlot[slot]; ok {
					idx = found.index
				}
			}
		}

		if idx == -1 {
			base = append(base, detailed)
			newIdx := len(base) - 1
			if bdf := normalizePCIeBDF(detailed.BDF); bdf != "" {
				byBDF[bdf] = ref{index: newIdx}
			}
			if slot := strings.ToLower(strings.TrimSpace(detailed.Slot)); slot != "" {
				bySlot[slot] = ref{index: newIdx}
			}
			continue
		}

		enrichPCIeDevice(&base[idx], detailed)
	}

	return base
}

func enrichPCIeDevice(dst *models.PCIeDevice, src models.PCIeDevice) {
	if dst == nil {
		return
	}
	if strings.TrimSpace(dst.Slot) == "" {
		dst.Slot = src.Slot
	}
	if strings.TrimSpace(dst.BDF) == "" {
		dst.BDF = src.BDF
	}
	if dst.VendorID == 0 {
		dst.VendorID = src.VendorID
	}
	if dst.DeviceID == 0 {
		dst.DeviceID = src.DeviceID
	}
	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.LinkSpeed) == "" || strings.EqualFold(strings.TrimSpace(dst.LinkSpeed), "unknown") {
		dst.LinkSpeed = src.LinkSpeed
	}
	if strings.TrimSpace(dst.MaxLinkSpeed) == "" || strings.EqualFold(strings.TrimSpace(dst.MaxLinkSpeed), "unknown") {
		dst.MaxLinkSpeed = src.MaxLinkSpeed
	}
	if dst.LinkWidth == 0 {
		dst.LinkWidth = src.LinkWidth
	}
	if dst.MaxLinkWidth == 0 {
		dst.MaxLinkWidth = src.MaxLinkWidth
	}
	if isGenericPCIeClass(dst.DeviceClass) && !isGenericPCIeClass(src.DeviceClass) {
		dst.DeviceClass = src.DeviceClass
	}
}

func normalizePCIeBDF(bdf string) string {
	bdf = strings.TrimSpace(strings.ToLower(bdf))
	if bdf == "" {
		return ""
	}

	if strings.Contains(bdf, "/") {
		parts := strings.Split(bdf, "/")
		if len(parts) == 4 {
			return fmt.Sprintf("%s:%s.%s", parts[1], parts[2], parts[3])
		}
	}
	return bdf
}

func isGenericPCIeClass(class string) bool {
	switch strings.ToLower(strings.TrimSpace(class)) {
	case "", "unknown", "other", "bridge", "network", "storage", "sas", "sata", "display", "vga", "3d controller", "serial bus":
		return true
	default:
		return false
	}
}

// determineDeviceClass maps device type to human-readable class
func determineDeviceClass(devType, devSubtype int, deviceName string) string {
	// dev_type mapping:
	// 1 = Mass Storage Controller
	// 2 = Network Controller
	// 3 = Display Controller (GPU)
	// 4 = Multimedia Controller

	switch devType {
	case 1:
		if devSubtype == 4 {
			return "RAID Controller"
		}
		return "Storage Controller"
	case 2:
		return "Network Controller"
	case 3:
		// GPU
		if strings.Contains(strings.ToUpper(deviceName), "H100") {
			return "GPU (H100)"
		}
		if strings.Contains(strings.ToUpper(deviceName), "A100") {
			return "GPU (A100)"
		}
		if strings.Contains(strings.ToUpper(deviceName), "NVIDIA") {
			return "GPU"
		}
		return "Display Controller"
	case 4:
		return "Multimedia Controller"
	default:
		return "Unknown"
	}
}

// ParseGPUs extracts GPU data from PCIe devices and sensors
func ParseGPUs(pcieDevices []models.PCIeDevice, sensors []models.SensorReading) []models.GPU {
	var gpus []models.GPU

	// Find GPU devices
	for _, pcie := range pcieDevices {
		if !strings.Contains(strings.ToLower(pcie.DeviceClass), "gpu") &&
			!strings.Contains(strings.ToLower(pcie.DeviceClass), "display") {
			continue
		}

		// Skip integrated graphics (ASPEED, etc.)
		if strings.Contains(pcie.Manufacturer, "ASPEED") {
			continue
		}

		gpu := models.GPU{
			Slot:             pcie.Slot,
			Location:         pcie.Slot,
			Model:            pcie.DeviceClass,
			Manufacturer:     pcie.Manufacturer,
			SerialNumber:     pcie.SerialNumber,
			MaxLinkWidth:     pcie.MaxLinkWidth,
			MaxLinkSpeed:     pcie.MaxLinkSpeed,
			CurrentLinkWidth: pcie.LinkWidth,
			CurrentLinkSpeed: pcie.LinkSpeed,
			Status:           "OK",
		}

		// Extract GPU number from slot name (e.g., "PCIE7" -> 7)
		slotNum := extractSlotNumber(pcie.Slot)

		// Find temperature sensors for this GPU
		for _, sensor := range sensors {
			sensorName := strings.ToUpper(sensor.Name)

			// Match GPU temperature sensor (e.g., "GPU7_Temp")
			if strings.Contains(sensorName, fmt.Sprintf("GPU%d_TEMP", slotNum)) {
				if sensor.RawValue != "" {
					fmt.Sscanf(sensor.RawValue, "%d", &gpu.Temperature)
				}
			}

			// Match GPU memory temperature (e.g., "GPU7_Mem_Temp")
			if strings.Contains(sensorName, fmt.Sprintf("GPU%d_MEM_TEMP", slotNum)) {
				if sensor.RawValue != "" {
					fmt.Sscanf(sensor.RawValue, "%d", &gpu.MemTemperature)
				}
			}

			// Match PCIe slot temperature (e.g., "PCIE7_GPU_TLM_T")
			if strings.Contains(sensorName, fmt.Sprintf("PCIE%d_GPU_TLM_T", slotNum)) {
				if sensor.RawValue != "" && gpu.Temperature == 0 {
					fmt.Sscanf(sensor.RawValue, "%d", &gpu.Temperature)
				}
			}
		}

		gpus = append(gpus, gpu)
	}

	return gpus
}

// extractSlotNumber extracts slot number from location string
// e.g., "CPU0_PE3_AC_PCIE7" -> 7
func extractSlotNumber(location string) int {
	parts := strings.Split(location, "_")
	for _, part := range parts {
		if strings.HasPrefix(part, "PCIE") || strings.HasPrefix(part, "#CPU") {
			var num int
			fmt.Sscanf(part, "PCIE%d", &num)
			if num > 0 {
				return num
			}
		}
	}
	return 0
}
