package utils

import (
	"reflect"
	"strings"
)

// strKeyMap tries to convert `map[any]any` to `map[string]any`
func strKeyMap(val any) any {
	m, ok := val.(map[any]any)
	if ok {
		ret := map[string]any{}
		for k, v := range m {
			kk, ok := k.(string)
			if !ok {
				return val
			}
			ret[kk] = strKeyMap(v)
		}
		return ret
	}

	rv := reflect.ValueOf(val)
	if rv.Kind() == reflect.Slice {
		var ret []any
		for i := 0; i < rv.Len(); i++ {
			ret = append(ret, strKeyMap(rv.Index(i).Interface()))
		}
		return ret
	}

	return val
}

func foldKey(key string, val any) (string, any) {
	parts := strings.SplitN(key, ".", 2)
	if len(parts) == 1 {
		return key, strKeyMap(val)
	}
	subKey, subVal := foldKey(parts[1], val)
	return parts[0], map[string]any{
		subKey: strKeyMap(subVal),
	}
}

func patch(origin map[string]any, key string, val any) {
	origVal, found := origin[key]
	if !found {
		origin[key] = strKeyMap(val)
		return
	}
	origMap, lhsOk := origVal.(map[string]any)
	valMap, rhsOk := val.(map[string]any)
	if lhsOk && rhsOk {
		for k, v := range valMap {
			patch(origMap, k, v)
		}
	} else {
		// overwrite
		origin[key] = strKeyMap(val)
	}
}

// FoldMap convert single layer map to multi-layer
func FoldMap(ms map[string]any) map[string]any {
	// we flatten map first to deal with the case like:
	// a.b:
	//   c.d: xxx
	ms = FlattenMap(ms)
	result := map[string]any{}
	for k, v := range ms {
		key, val := foldKey(k, v)
		patch(result, key, val)
	}
	return result
}

// FlattenMap convert mutil-layer map to single layer
func FlattenMap(ms map[string]any) map[string]any {
	result := map[string]any{}
	for k, v := range ms {
		var sub map[string]any

		if m, ok := v.(map[string]any); ok {
			sub = FlattenMap(m)
		} else if m, ok := v.(map[any]any); ok {
			fixM := map[string]any{}
			for k, v := range m {
				if sk, ok := k.(string); ok {
					fixM[sk] = v
				}
			}
			sub = FlattenMap(fixM)
		} else {
			result[k] = v
			continue
		}

		for sk, sv := range sub {
			result[k+"."+sk] = sv
		}
	}
	return result
}

// MergeConfig merge two or more config into one and unflat them
// config1:
//
//	a.b.a: 1
//	a.b.b: 2
//
// config2:
//
//	a.b.a: 3
//	a.b.c: 4
//
// config3:
//
//	b.c = 5
//
// After MergeConfig(config1, config2, config3):
//
//	a:
//	  b:
//	    a: 3
//	    b: 2
//	    c: 4
//	b:
//	  c: 5
func MergeConfig(orig map[string]any, overwrites ...map[string]any) map[string]any {
	lhs := FoldMap(orig)
	for _, overwrite := range overwrites {
		rhs := FoldMap(overwrite)
		for k, v := range rhs {
			patch(lhs, k, v)
		}
	}
	return lhs
}

func MergeRawConfig(orig map[string]any, overwrites ...map[string]any) map[string]any {
	lhs := deepCopy(orig)
	for _, rhs := range overwrites {
		for k, v := range rhs {
			patch(lhs, k, v)
		}
	}
	return lhs
}

func Convert2Int(c map[string]any) map[string]any {
	for k, v := range c {
		// workaround when migrate from gitops, need to delete after migrate
		if k == "capacity-mb" {
			continue
		}
		c[k] = convert2Int(v)
	}

	return c
}

func convert2Int(v any) any {
	switch v := v.(type) {
	case float32:
		iv := int64(v)
		if float32(iv) == v {
			return iv
		}
	case float64:
		iv := int64(v)
		if float64(iv) == v {
			return iv
		}
	case map[string]any:
		return Convert2Int(v)
	case []any:
		for i, item := range v {
			v[i] = convert2Int(item)
		}
		return v
	}

	return v
}

func deepCopy(src map[string]any) map[string]any {
	dst := make(map[string]any)
	for k, v := range src {
		switch v := v.(type) {
		case map[string]any:
			dst[k] = deepCopy(v)
		default:
			dst[k] = v
		}
	}
	return dst
}
