package collector

import (
	"strings"

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

func (r redfishSnapshotReader) collectBoardFallbackDocs(systemPaths, chassisPaths []string) []map[string]interface{} {
	out := make([]map[string]interface{}, 0)
	for _, chassisPath := range chassisPaths {
		for _, suffix := range []string{"/Boards", "/Backplanes"} {
			path := joinPath(chassisPath, suffix)
			if docs, err := r.getCollectionMembers(path); err == nil && len(docs) > 0 {
				out = append(out, docs...)
				continue
			}
			if doc, err := r.getJSON(path); err == nil && len(doc) > 0 {
				out = append(out, doc)
			}
		}
	}
	for _, path := range append(append([]string{}, systemPaths...), chassisPaths...) {
		for _, suffix := range []string{"/Oem/Public", "/Oem/Public/ThermalConfig", "/ThermalConfig"} {
			docPath := joinPath(path, suffix)
			if doc, err := r.getJSON(docPath); err == nil && len(doc) > 0 {
				out = append(out, doc)
			}
		}
	}
	return out
}

func applyBoardInfoFallbackFromDocs(board *models.BoardInfo, docs []map[string]interface{}) {
	if board == nil || len(docs) == 0 {
		return
	}
	for _, doc := range docs {
		candidate := parseBoardInfoFromFRUDoc(doc)
		if !isLikelyServerProductName(candidate.ProductName) {
			continue
		}
		if board.Manufacturer == "" {
			board.Manufacturer = candidate.Manufacturer
		}
		if board.ProductName == "" {
			board.ProductName = candidate.ProductName
		}
		if board.SerialNumber == "" {
			board.SerialNumber = candidate.SerialNumber
		}
		if board.PartNumber == "" {
			board.PartNumber = candidate.PartNumber
		}
		if board.Manufacturer != "" && board.ProductName != "" && board.SerialNumber != "" && board.PartNumber != "" {
			return
		}
	}
}

func isLikelyServerProductName(v string) bool {
	v = strings.TrimSpace(v)
	if v == "" {
		return false
	}
	n := strings.ToUpper(v)
	if strings.Contains(n, "NULL") {
		return false
	}
	componentTokens := []string{
		"DIMM", "DDR", "NVME", "SSD", "HDD", "GPU", "NIC", "RAID",
		"PSU", "FAN", "BACKPLANE", "FRU",
	}
	for _, token := range componentTokens {
		if strings.Contains(n, strings.ToUpper(token)) {
			return false
		}
	}
	return true
}

// collectAssemblyFRU reads Chassis/*/Assembly documents and returns FRU entries
// for subcomponents (backplanes, PSUs, DIMMs, etc.) that carry meaningful
// serial or part numbers. Entries already present in dedicated collections
// (PSUs, DIMMs) are included here as well so that all FRU data is available
// in one place; deduplication by serial is performed.
func (r redfishSnapshotReader) collectAssemblyFRU(chassisPaths []string) []models.FRUInfo {
	seen := make(map[string]struct{})
	var out []models.FRUInfo

	add := func(fru models.FRUInfo) {
		key := strings.ToUpper(strings.TrimSpace(fru.SerialNumber))
		if key == "" {
			key = strings.ToUpper(strings.TrimSpace(fru.Description + "|" + fru.PartNumber))
		}
		if key == "" || key == "|" {
			return
		}
		if _, ok := seen[key]; ok {
			return
		}
		seen[key] = struct{}{}
		out = append(out, fru)
	}

	for _, chassisPath := range chassisPaths {
		doc, err := r.getJSON(joinPath(chassisPath, "/Assembly"))
		if err != nil || len(doc) == 0 {
			continue
		}
		assemblies, _ := doc["Assemblies"].([]interface{})
		for _, aAny := range assemblies {
			a, ok := aAny.(map[string]interface{})
			if !ok {
				continue
			}
			name := strings.TrimSpace(firstNonEmpty(asString(a["Name"]), asString(a["Description"])))
			model := strings.TrimSpace(asString(a["Model"]))
			partNumber := strings.TrimSpace(asString(a["PartNumber"]))
			serial := extractAssemblySerial(a)

			if serial == "" && partNumber == "" {
				continue
			}
			add(models.FRUInfo{
				Description:  name,
				ProductName:  model,
				SerialNumber: serial,
				PartNumber:   partNumber,
			})
		}
	}
	return out
}

// extractAssemblySerial tries to find a serial number in an Assembly entry.
// Standard Redfish Assembly has no top-level SerialNumber; vendors put it in Oem.
func extractAssemblySerial(a map[string]interface{}) string {
	if s := strings.TrimSpace(asString(a["SerialNumber"])); s != "" {
		return s
	}
	oem, _ := a["Oem"].(map[string]interface{})
	for _, v := range oem {
		subtree, ok := v.(map[string]interface{})
		if !ok {
			continue
		}
		for _, v2 := range subtree {
			node, ok := v2.(map[string]interface{})
			if !ok {
				continue
			}
			if s := strings.TrimSpace(asString(node["SerialNumber"])); s != "" {
				return s
			}
		}
	}
	return ""
}
