package inspur

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

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

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

// AssetJSON represents the structure of Inspur asset.json file
type AssetJSON struct {
	VersionInfo []struct {
		DeviceID       int    `json:"DeviceId"`
		DeviceName     string `json:"DeviceName"`
		DeviceRevision string `json:"DeviceRevision"`
		BuildTime      string `json:"BuildTime"`
	} `json:"VersionInfo"`

	CpuInfo []struct {
		ProcessorName       string `json:"ProcessorName"`
		ProcessorID         string `json:"ProcessorId"`
		MicroCodeVer        string `json:"MicroCodeVer"`
		CurrentSpeed        int    `json:"CurrentSpeed"`
		Core                int    `json:"Core"`
		ThreadCount         int    `json:"ThreadCount"`
		L1Cache             int    `json:"L1Cache"`
		L2Cache             int    `json:"L2Cache"`
		L3Cache             int    `json:"L3Cache"`
		CpuTdp              int    `json:"CpuTdp"`
		PPIN                string `json:"PPIN"`
		TurboEnableMaxSpeed int    `json:"TurboEnableMaxSpeed"`
		TurboCloseMaxSpeed  int    `json:"TurboCloseMaxSpeed"`
		UPIBandwidth        string `json:"UPIBandwidth"`
	} `json:"CpuInfo"`

	MemInfo struct {
		MemCommonInfo []struct {
			Manufacturer      string `json:"Manufacturer"`
			MaxSpeed          int    `json:"MaxSpeed"`
			CurrentSpeed      int    `json:"CurrentSpeed"`
			MemoryType        int    `json:"MemoryType"`
			Rank              int    `json:"Rank"`
			DataWidth         int    `json:"DataWidth"`
			ConfiguredVoltage int    `json:"ConfiguredVoltage"`
			PhysicalSize      int    `json:"PhysicalSize"`
		} `json:"MemCommonInfo"`

		DimmInfo []struct {
			SerialNumber string `json:"SerialNumber"`
			PartNumber   string `json:"PartNumber"`
			AssetTag     string `json:"AssetTag"`
		} `json:"DimmInfo"`
	} `json:"MemInfo"`

	HddInfo []struct {
		PresentBitmap      []int  `json:"PresentBitmap"`
		SerialNumber       string `json:"SerialNumber"`
		Manufacturer       string `json:"Manufacturer"`
		ModelName          string `json:"ModelName"`
		FirmwareVersion    string `json:"FirmwareVersion"`
		Capacity           int    `json:"Capacity"`
		Location           int    `json:"Location"`
		DiskInterfaceType  int    `json:"DiskInterfaceType"`
		MediaType          int    `json:"MediaType"`
		LocationString     string `json:"LocationString"`
		BlockSizeBytes     int    `json:"BlockSizeBytes"`
		CapableSpeedGbs    string `json:"CapableSpeedGbs"`
		NegotiatedSpeedGbs string `json:"NegotiatedSpeedGbs"`
		PcieSlot           int    `json:"PcieSlot"`
	} `json:"HddInfo"`

	PcieInfo []struct {
		VendorId            int      `json:"VendorId"`
		DeviceId            int      `json:"DeviceId"`
		BusNumber           int      `json:"BusNumber"`
		DeviceNumber        int      `json:"DeviceNumber"`
		FunctionNumber      int      `json:"FunctionNumber"`
		MaxLinkWidth        int      `json:"MaxLinkWidth"`
		MaxLinkSpeed        int      `json:"MaxLinkSpeed"`
		NegotiatedLinkWidth int      `json:"NegotiatedLinkWidth"`
		CurrentLinkSpeed    int      `json:"CurrentLinkSpeed"`
		ClassCode           int      `json:"ClassCode"`
		SubClassCode        int      `json:"SubClassCode"`
		PcieSlot            int      `json:"PcieSlot"`
		LocString           string   `json:"LocString"`
		PartNumber          *string  `json:"PartNumber"`
		SerialNumber        *string  `json:"SerialNumber"`
		Mac                 []string `json:"Mac"`
	} `json:"PcieInfo"`
}

// ParseAssetJSON parses Inspur asset.json content.
//   - pcieSlotDeviceNames: optional map from integer PCIe slot ID to device name string,
//     sourced from devicefrusdr.log PCIe REST section. Fills missing NVMe model names.
//   - pcieSlotSerials: optional map from integer PCIe slot ID to serial number string,
//     sourced from audit.log SN-changed events. Fills missing NVMe serial numbers.
func ParseAssetJSON(content []byte, pcieSlotDeviceNames map[int]string, pcieSlotSerials map[int]string) (*models.HardwareConfig, error) {
	var asset AssetJSON
	if err := json.Unmarshal(content, &asset); err != nil {
		return nil, err
	}

	config := &models.HardwareConfig{}

	// Parse version info
	for _, v := range asset.VersionInfo {
		config.Firmware = append(config.Firmware, models.FirmwareInfo{
			DeviceName: v.DeviceName,
			Version:    v.DeviceRevision,
			BuildTime:  v.BuildTime,
		})
	}

	// Parse CPU info
	seenMicrocode := make(map[string]bool)
	for i, cpu := range asset.CpuInfo {
		config.CPUs = append(config.CPUs, models.CPU{
			Socket:       i,
			Model:        strings.TrimSpace(cpu.ProcessorName),
			Cores:        cpu.Core,
			Threads:      cpu.ThreadCount,
			FrequencyMHz: cpu.CurrentSpeed,
			MaxFreqMHz:   cpu.TurboEnableMaxSpeed,
			L1CacheKB:    cpu.L1Cache,
			L2CacheKB:    cpu.L2Cache,
			L3CacheKB:    cpu.L3Cache,
			TDP:          cpu.CpuTdp,
			PPIN:         cpu.PPIN,
		})

		// Add CPU microcode to firmware list (deduplicated)
		if cpu.MicroCodeVer != "" && !seenMicrocode[cpu.MicroCodeVer] {
			config.Firmware = append(config.Firmware, models.FirmwareInfo{
				DeviceName: fmt.Sprintf("CPU%d Microcode", i),
				Version:    cpu.MicroCodeVer,
			})
			seenMicrocode[cpu.MicroCodeVer] = true
		}
	}

	// Memory info is parsed from component.log (RESTful Memory info) which has more details
	// Only use asset.json memory data as fallback if component.log is not available
	if len(asset.MemInfo.MemCommonInfo) > 0 {
		common := asset.MemInfo.MemCommonInfo[0]
		for i, dimm := range asset.MemInfo.DimmInfo {
			slot := fmt.Sprintf("DIMM%d", i)
			config.Memory = append(config.Memory, models.MemoryDIMM{
				Slot:            slot,
				Location:        slot,
				Present:         true,
				SizeMB:          common.PhysicalSize * 1024,
				Type:            memoryTypeToString(common.MemoryType),
				MaxSpeedMHz:     common.MaxSpeed,
				CurrentSpeedMHz: common.CurrentSpeed,
				Manufacturer:    common.Manufacturer,
				SerialNumber:    dimm.SerialNumber,
				PartNumber:      strings.TrimSpace(dimm.PartNumber),
				Ranks:           common.Rank,
			})
		}
	}

	// Parse storage info
	for _, hdd := range asset.HddInfo {
		slot := normalizeAssetHDDSlot(hdd.LocationString, hdd.Location, hdd.DiskInterfaceType)
		modelName := strings.TrimSpace(hdd.ModelName)
		serial := normalizeRedisValue(hdd.SerialNumber)
		present := bitmapHasAnyValue(hdd.PresentBitmap)
		if !present && (slot != "" || modelName != "" || serial != "" || hdd.Capacity > 0) {
			present = true
		}

		if !present && slot == "" && modelName == "" && serial == "" && hdd.Capacity == 0 {
			continue
		}

		// Enrich model name from PCIe device name (supplied from devicefrusdr.log).
		// BMC does not populate HddInfo.ModelName for NVMe drives, but the PCIe REST
		// section in devicefrusdr.log carries the drive model as device_name.
		if modelName == "" && hdd.PcieSlot > 0 && len(pcieSlotDeviceNames) > 0 {
			if devName, ok := pcieSlotDeviceNames[hdd.PcieSlot]; ok && devName != "" {
				modelName = devName
			}
		}

		// Enrich serial number from audit.log SN-changed events (supplied via pcieSlotSerials).
		// BMC asset.json does not carry NVMe serial numbers; audit.log logs every SN change.
		if serial == "" && hdd.PcieSlot > 0 && len(pcieSlotSerials) > 0 {
			if sn, ok := pcieSlotSerials[hdd.PcieSlot]; ok && sn != "" {
				serial = sn
			}
		}

		storageType := "HDD"
		if hdd.DiskInterfaceType == 5 {
			storageType = "NVMe"
		} else if hdd.MediaType == 1 {
			storageType = "SSD"
		}

		// Resolve manufacturer: try vendor ID first, then model name extraction
		manufacturer := resolveManufacturer(hdd.Manufacturer, modelName)

		config.Storage = append(config.Storage, models.Storage{
			Slot:         slot,
			Type:         storageType,
			Model:        modelName,
			SizeGB:       hdd.Capacity,
			SerialNumber: serial,
			Manufacturer: manufacturer,
			Firmware:     hdd.FirmwareVersion,
			Interface:    diskInterfaceToString(hdd.DiskInterfaceType),
			Present:      present,
		})

		// Disk firmware is already stored in Storage.Firmware — do not duplicate in Hardware.Firmware.
	}

	// Parse PCIe info
	for _, pcie := range asset.PcieInfo {
		vendor, deviceName := pciids.DeviceInfo(pcie.VendorId, pcie.DeviceId)
		device := models.PCIeDevice{
			Slot:         pcie.LocString,
			VendorID:     pcie.VendorId,
			DeviceID:     pcie.DeviceId,
			BDF:          formatBDF(pcie.BusNumber, pcie.DeviceNumber, pcie.FunctionNumber),
			LinkWidth:    pcie.NegotiatedLinkWidth,
			LinkSpeed:    pcieLinkSpeedToString(pcie.CurrentLinkSpeed),
			MaxLinkWidth: pcie.MaxLinkWidth,
			MaxLinkSpeed: pcieLinkSpeedToString(pcie.MaxLinkSpeed),
			DeviceClass:  pcieClassToString(pcie.ClassCode, pcie.SubClassCode),
			Manufacturer: vendor,
		}
		if pcie.PartNumber != nil {
			device.PartNumber = strings.TrimSpace(*pcie.PartNumber)
		}
		if pcie.SerialNumber != nil {
			device.SerialNumber = strings.TrimSpace(*pcie.SerialNumber)
		}
		if len(pcie.Mac) > 0 {
			device.MACAddresses = pcie.Mac
		}
		// Use device name from PCI IDs database if available
		if deviceName != "" {
			device.DeviceClass = normalizeModelLabel(deviceName)
		}
		config.PCIeDevices = append(config.PCIeDevices, device)

		// Extract GPUs (class 3 = display controller)
		if pcie.ClassCode == 3 {
			gpuModel := normalizeGPUModel(pcie.VendorId, pcie.DeviceId, deviceName, pcie.ClassCode, pcie.SubClassCode)
			gpu := models.GPU{
				Slot:             pcie.LocString,
				Model:            gpuModel,
				Manufacturer:     vendor,
				VendorID:         pcie.VendorId,
				DeviceID:         pcie.DeviceId,
				BDF:              formatBDF(pcie.BusNumber, pcie.DeviceNumber, pcie.FunctionNumber),
				CurrentLinkWidth: pcie.NegotiatedLinkWidth,
				CurrentLinkSpeed: pcieLinkSpeedToString(pcie.CurrentLinkSpeed),
				MaxLinkWidth:     pcie.MaxLinkWidth,
				MaxLinkSpeed:     pcieLinkSpeedToString(pcie.MaxLinkSpeed),
			}
			if pcie.PartNumber != nil {
				gpu.PartNumber = strings.TrimSpace(*pcie.PartNumber)
			}
			if pcie.SerialNumber != nil {
				gpu.SerialNumber = strings.TrimSpace(*pcie.SerialNumber)
			}
			config.GPUs = append(config.GPUs, gpu)
		}
	}

	return config, nil
}

func normalizeModelLabel(v string) string {
	v = strings.TrimSpace(v)
	if v == "" {
		return ""
	}
	return strings.Join(strings.Fields(v), " ")
}

func normalizeGPUModel(vendorID, deviceID int, model string, classCode, subClass int) string {
	model = normalizeModelLabel(model)

	if model == "" || rawHexPCIDeviceRegex.MatchString(model) || isGenericGPUModelLabel(model) {
		if pciModel := normalizeModelLabel(pciids.DeviceName(vendorID, deviceID)); pciModel != "" {
			model = pciModel
		}
	}

	if model == "" || isGenericGPUModelLabel(model) {
		model = pcieClassToString(classCode, subClass)
	}

	// Last fallback for unknown NVIDIA display devices: expose PCI DeviceID
	// instead of generic "3D Controller".
	if (model == "" || strings.EqualFold(model, "3D Controller")) && vendorID == 0x10de && deviceID > 0 {
		return fmt.Sprintf("0x%04X", deviceID)
	}

	return model
}

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

func memoryTypeToString(memType int) string {
	switch memType {
	case 26:
		return "DDR4"
	case 34:
		return "DDR5"
	default:
		return "Unknown"
	}
}

func diskInterfaceToString(ifType int) string {
	switch ifType {
	case 4:
		return "SATA"
	case 5:
		return "NVMe"
	case 6:
		return "SAS"
	default:
		return "Unknown"
	}
}

func normalizeAssetHDDSlot(locationString string, location int, diskInterfaceType int) string {
	slot := strings.TrimSpace(locationString)
	if slot != "" {
		return slot
	}
	if location < 0 {
		return ""
	}
	if diskInterfaceType == 5 {
		return fmt.Sprintf("OB%02d", location+1)
	}
	return fmt.Sprintf("%d", location)
}

func bitmapHasAnyValue(values []int) bool {
	for _, v := range values {
		if v != 0 {
			return true
		}
	}
	return false
}

func pcieLinkSpeedToString(speed int) string {
	switch speed {
	case 1:
		return "2.5 GT/s"
	case 2:
		return "5.0 GT/s"
	case 3:
		return "8.0 GT/s"
	case 4:
		return "16.0 GT/s"
	case 5:
		return "32.0 GT/s"
	default:
		return "Unknown"
	}
}

func pcieClassToString(classCode, subClass int) string {
	switch classCode {
	case 1:
		switch subClass {
		case 0:
			return "SCSI"
		case 1:
			return "IDE"
		case 4:
			return "RAID"
		case 6:
			return "SATA"
		case 7:
			return "SAS"
		case 8:
			return "NVMe"
		default:
			return "Storage"
		}
	case 2:
		return "Network"
	case 3:
		switch subClass {
		case 0:
			return "VGA"
		case 2:
			return "3D Controller"
		default:
			return "Display"
		}
	case 4:
		return "Multimedia"
	case 6:
		return "Bridge"
	case 12:
		return "Serial Bus"
	default:
		return "Other"
	}
}

func formatBDF(bus, dev, fun int) string {
	return fmt.Sprintf("%02x:%02x.%x", bus, dev, fun)
}

// resolveManufacturer resolves manufacturer name from various sources
func resolveManufacturer(rawManufacturer, modelName string) string {
	raw := strings.TrimSpace(rawManufacturer)

	// If it looks like a vendor ID (hex), try to resolve it
	if raw != "" {
		if name := pciids.VendorNameFromString(raw); name != "" {
			return name
		}
		// If not a vendor ID but looks like a real name (has letters), use it
		hasLetter := false
		for _, c := range raw {
			if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') {
				hasLetter = true
				break
			}
		}
		if hasLetter && len(raw) > 2 {
			return raw
		}
	}

	// Try to extract from model name
	return extractStorageManufacturer(modelName)
}

// extractStorageManufacturer tries to extract manufacturer from model name
func extractStorageManufacturer(model string) string {
	modelUpper := strings.ToUpper(model)

	knownVendors := []struct {
		prefix string
		name   string
	}{
		{"SAMSUNG", "Samsung"},
		{"KIOXIA", "KIOXIA"},
		{"TOSHIBA", "Toshiba"},
		{"WDC", "Western Digital"},
		{"WD", "Western Digital"},
		{"SEAGATE", "Seagate"},
		{"HGST", "HGST"},
		{"INTEL", "Intel"},
		{"MICRON", "Micron"},
		{"KINGSTON", "Kingston"},
		{"CRUCIAL", "Crucial"},
		{"SK HYNIX", "SK Hynix"},
		{"SKHYNIX", "SK Hynix"},
		{"SANDISK", "SanDisk"},
		{"LITEON", "Lite-On"},
		{"PLEXTOR", "Plextor"},
		{"ADATA", "ADATA"},
		{"TRANSCEND", "Transcend"},
		{"CORSAIR", "Corsair"},
		{"SOLIDIGM", "Solidigm"},
	}

	for _, v := range knownVendors {
		if strings.HasPrefix(modelUpper, v.prefix) {
			return v.name
		}
	}

	return ""
}
