package redfishprofile

import "strings"

func CollectSignals(serviceRootDoc, systemDoc, chassisDoc, managerDoc map[string]interface{}, resourceHints []string, hintDocs ...map[string]interface{}) MatchSignals {
	resourceHints = append([]string{}, resourceHints...)
	docHints := make([]string, 0)
	for _, doc := range append([]map[string]interface{}{serviceRootDoc, systemDoc, chassisDoc, managerDoc}, hintDocs...) {
		embeddedPaths, embeddedHints := collectDocSignalHints(doc)
		resourceHints = append(resourceHints, embeddedPaths...)
		docHints = append(docHints, embeddedHints...)
	}
	signals := MatchSignals{
		ServiceRootVendor:   lookupString(serviceRootDoc, "Vendor"),
		ServiceRootProduct:  lookupString(serviceRootDoc, "Product"),
		SystemManufacturer:  lookupString(systemDoc, "Manufacturer"),
		SystemModel:         lookupString(systemDoc, "Model"),
		SystemSKU:           lookupString(systemDoc, "SKU"),
		ChassisManufacturer: lookupString(chassisDoc, "Manufacturer"),
		ChassisModel:        lookupString(chassisDoc, "Model"),
		ManagerManufacturer: lookupString(managerDoc, "Manufacturer"),
		ResourceHints:       resourceHints,
		DocHints:            docHints,
	}
	signals.OEMNamespaces = dedupeSorted(append(
		oemNamespaces(serviceRootDoc),
		append(oemNamespaces(systemDoc), append(oemNamespaces(chassisDoc), oemNamespaces(managerDoc)...)...)...,
	))
	return normalizeSignals(signals)
}

func CollectSignalsFromTree(tree map[string]interface{}) MatchSignals {
	getDoc := func(path string) map[string]interface{} {
		if v, ok := tree[path]; ok {
			if doc, ok := v.(map[string]interface{}); ok {
				return doc
			}
		}
		return nil
	}

	memberPath := func(collectionPath, fallbackPath string) string {
		collection := getDoc(collectionPath)
		if len(collection) != 0 {
			if members, ok := collection["Members"].([]interface{}); ok && len(members) > 0 {
				if ref, ok := members[0].(map[string]interface{}); ok {
					if path := lookupString(ref, "@odata.id"); path != "" {
						return path
					}
				}
			}
		}
		return fallbackPath
	}

	systemPath := memberPath("/redfish/v1/Systems", "/redfish/v1/Systems/1")
	chassisPath := memberPath("/redfish/v1/Chassis", "/redfish/v1/Chassis/1")
	managerPath := memberPath("/redfish/v1/Managers", "/redfish/v1/Managers/1")

	resourceHints := make([]string, 0, len(tree))
	hintDocs := make([]map[string]interface{}, 0, len(tree))
	for path := range tree {
		path = strings.TrimSpace(path)
		if path == "" {
			continue
		}
		resourceHints = append(resourceHints, path)
	}
	for _, v := range tree {
		doc, ok := v.(map[string]interface{})
		if !ok {
			continue
		}
		hintDocs = append(hintDocs, doc)
	}

	return CollectSignals(
		getDoc("/redfish/v1"),
		getDoc(systemPath),
		getDoc(chassisPath),
		getDoc(managerPath),
		resourceHints,
		hintDocs...,
	)
}

func collectDocSignalHints(doc map[string]interface{}) ([]string, []string) {
	if len(doc) == 0 {
		return nil, nil
	}
	paths := make([]string, 0)
	hints := make([]string, 0)
	var walk func(any)
	walk = func(v any) {
		switch x := v.(type) {
		case map[string]interface{}:
			for rawKey, child := range x {
				key := strings.TrimSpace(rawKey)
				if key != "" {
					hints = append(hints, key)
				}
				if s, ok := child.(string); ok {
					s = strings.TrimSpace(s)
					if s != "" {
						switch key {
						case "@odata.id", "target":
							paths = append(paths, s)
						case "@odata.type":
							hints = append(hints, s)
						default:
							if isInterestingSignalString(s) {
								hints = append(hints, s)
								if strings.HasPrefix(s, "/") {
									paths = append(paths, s)
								}
							}
						}
					}
				}
				walk(child)
			}
		case []interface{}:
			for _, child := range x {
				walk(child)
			}
		}
	}
	walk(doc)
	return paths, hints
}

func isInterestingSignalString(s string) bool {
	switch {
	case strings.HasPrefix(s, "/"):
		return true
	case strings.HasPrefix(s, "#"):
		return true
	case strings.Contains(s, "COMMONb"):
		return true
	case strings.Contains(s, "EnvironmentMetrcs"):
		return true
	case strings.Contains(s, "GetServerAllUSBStatus"):
		return true
	default:
		return false
	}
}

func lookupString(doc map[string]interface{}, key string) string {
	if len(doc) == 0 {
		return ""
	}
	value, _ := doc[key]
	if s, ok := value.(string); ok {
		return strings.TrimSpace(s)
	}
	return ""
}

func oemNamespaces(doc map[string]interface{}) []string {
	if len(doc) == 0 {
		return nil
	}
	oem, ok := doc["Oem"].(map[string]interface{})
	if !ok {
		return nil
	}
	out := make([]string, 0, len(oem))
	for key := range oem {
		key = strings.TrimSpace(key)
		if key == "" {
			continue
		}
		out = append(out, key)
	}
	return out
}
