package resource

import (
	"encoding/json"

	commM "github.com/pingcap/test-infra/common/model"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/klog/v2"
)

type Testbed struct {
	// The testbed object from user.
	inner *commM.TestBed
}

func toITestbed(testbed *commM.TestBed) Testbed {
	return Testbed{
		inner: testbed,
	}
}

func toITestbeds(beds ...*commM.TestBed) []Testbed {
	ret := make([]Testbed, 0, len(beds))
	for _, bed := range beds {
		ret = append(ret, toITestbed(bed))
	}
	return ret
}

func (t *Testbed) FullName() string {
	return t.inner.Name
}

func (t *Testbed) Request() PhysicalResource {
	return PhysicalResource(t.inner.PhysicalResourceRequest)
}

func (t *Testbed) SetResourcePool(pool ResourcePoolInterface) {
	t.inner.ResourcePool = pool.Name()
	t.inner.RmsUrl = pool.RMS().Url()
}

func (t *Testbed) ResourcePoolAlias() string {
	return t.inner.ResourcePool
}

func (t *Testbed) AttachEstimatedRequest() {
	if t.inner.PhysicalResourceRequest.CPU.Core == 0 && t.inner.PhysicalResourceRequest.Memory.Byte == 0 {
		t.inner.PhysicalResourceRequest = commM.PhysicalResource(EstimateRequest(*t.inner))
	}
}

func EstimateRequest(testbed commM.TestBed) PhysicalResource {
	t := PhysicalResource{}
	for _, item := range testbed.Items {
		var obj interface{}
		if err := json.Unmarshal(item.Spec, &obj); err != nil {
			klog.ErrorS(err, "decode testbed item")
			continue
		}
		_t := ParseTestbedItemRequest(obj)
		t = t.Add(_t)
	}
	return t
}

func ParseTestbedItemRequest(obj interface{}) PhysicalResource {
	return PhysicalResource{
		CPU:    commM.CPUResource{Core: inferRequiredCPU("", obj)},
		Memory: commM.MemoryResource{Byte: int(inferRequiredMemory("", obj))},
	}
}

func inferRequiredCPU(name string, x interface{}) float64 {
	quota := .0
	switch a := x.(type) {
	case []interface{}:
		for _, y := range a {
			quota += inferRequiredCPU(name, y)
		}
	case map[string]interface{}:
		quota = extractRequestedCPUFromComponentLike(a, name)
		if quota > 0 {
			return quota
		}
		quota = extractRequestedFromContainerLike(a, "cpu")
		if quota > 0 {
			return quota
		}
		for k, y := range a {
			quota += inferRequiredCPU(k, y)
		}
	}
	return quota
}

func inferRequiredMemory(name string, x interface{}) float64 {
	quota := .0
	switch a := x.(type) {
	case []interface{}:
		for _, y := range a {
			quota += inferRequiredMemory(name, y)
		}
	case map[string]interface{}:
		quota = extractRequestedMemFromComponentLike(a, name)
		if quota > 0 {
			return quota
		}
		quota = extractRequestedFromContainerLike(a, "memory")
		if quota > 0 {
			return quota
		}
		for k, y := range a {
			quota += inferRequiredMemory(k, y)
		}
	}
	return quota
}

func extractRequestedCPUFromComponentLike(obj map[string]interface{}, name string) float64 {
	if _, ok := obj["image"]; !ok {
		if _, ok := obj["baseImage"]; !ok {
			return 0
		}
	}
	replicas, ok, err := unstructured.NestedFloat64(obj, "maxReplicas")
	if !ok || err != nil {
		replicas, ok, err = unstructured.NestedFloat64(obj, "replicas")
		if !ok || err != nil {
			return 0
		}
	}
	cpu, ok, err := unstructured.NestedString(obj, "requests", "cpu")
	if !ok || err != nil {
		cpu = map[string]string{
			"pd":      "250m",
			"tikv":    "500m",
			"tidb":    "500m",
			"ticdc":   "250m",
			"tiflash": "500m",
		}[name]
		if len(cpu) == 0 {
			return 0
		}
	}
	val, err := resource.ParseQuantity(cpu)
	if err != nil {
		return 0
	}
	return replicas * float64(val.ScaledValue(resource.Milli)) / 1000
}

func extractRequestedMemFromComponentLike(obj map[string]interface{}, name string) float64 {
	if _, ok := obj["image"]; !ok {
		if _, ok := obj["baseImage"]; !ok {
			return 0
		}
	}
	replicas, ok, err := unstructured.NestedFloat64(obj, "maxReplicas")
	if !ok || err != nil {
		replicas, ok, err = unstructured.NestedFloat64(obj, "replicas")
		if !ok || err != nil {
			return 0
		}
	}
	mem, ok, err := unstructured.NestedString(obj, "requests", "memory")
	if !ok || err != nil {
		mem = map[string]string{
			"pd":      "256Mi",
			"tikv":    "1Gi",
			"tidb":    "512Mi",
			"ticdc":   "8Gi",
			"tiflash": "1Gi",
		}[name]
		if len(mem) == 0 {
			return 0
		}
	}
	val, err := resource.ParseQuantity(mem)
	if err != nil {
		return 0
	}
	return replicas * float64(val.ScaledValue(resource.Milli)) / 1000
}

func extractRequestedFromContainerLike(obj map[string]interface{}, src string) float64 {
	if _, ok := obj["image"]; !ok {
		return 0
	}
	cpu, ok, err := unstructured.NestedString(obj, "resources", "requests", src)
	if err != nil {
		return 0
	}
	if !ok && src == "cpu" {
		return 1
	}
	val, err := resource.ParseQuantity(cpu)
	if err != nil {
		return 0
	}
	return float64(val.ScaledValue(resource.Milli)) / 1000
}
