// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package client_test

import (
	"testing"

	"github.com/derailed/k9s/internal/client"
	"github.com/stretchr/testify/assert"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)

func TestToPercentage(t *testing.T) {
	uu := []struct {
		v1, v2 int64
		e      int
	}{
		{0, 0, 0},
		{100, 200, 50},
		{200, 100, 200},
		{224, 4000, 5},
	}

	for _, u := range uu {
		assert.Equal(t, u.e, client.ToPercentage(u.v1, u.v2))
	}
}

func TestToMB(t *testing.T) {
	uu := []struct {
		v int64
		e int64
	}{
		{0, 0},
		{2 * client.MegaByte, 2},
		{10 * client.MegaByte, 10},
	}

	for _, u := range uu {
		assert.Equal(t, u.e, client.ToMB(u.v))
	}
}

func TestPodsMetrics(t *testing.T) {
	uu := map[string]struct {
		metrics *v1beta1.PodMetricsList
		eSize   int
		e       client.PodsMetrics
	}{
		"dud": {
			eSize: 0,
		},

		"ok": {
			metrics: &v1beta1.PodMetricsList{
				Items: []v1beta1.PodMetrics{
					*makeMxPod("p1", "1", "4Gi"),
					*makeMxPod("p2", "50m", "1Mi"),
				},
			},
			eSize: 2,
			e: client.PodsMetrics{
				"default/p1": client.PodMetrics{
					CurrentCPU: 3000,
					CurrentMEM: 12288,
				},
			},
		},
	}

	m := client.NewMetricsServer(nil)
	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			mmx := make(client.PodsMetrics)
			m.PodsMetrics(u.metrics, mmx)

			assert.Len(t, mmx, u.eSize)
			if u.eSize == 0 {
				return
			}
			mx, ok := mmx["default/p1"]
			assert.True(t, ok)
			assert.Equal(t, u.e["default/p1"], mx)
		})
	}
}

func BenchmarkPodsMetrics(b *testing.B) {
	m := client.NewMetricsServer(nil)

	metrics := v1beta1.PodMetricsList{
		Items: []v1beta1.PodMetrics{
			*makeMxPod("p1", "1", "4Gi"),
			*makeMxPod("p2", "50m", "1Mi"),
			*makeMxPod("p3", "50m", "1Mi"),
		},
	}
	mmx := make(client.PodsMetrics, 3)

	b.ResetTimer()
	b.ReportAllocs()
	for range b.N {
		m.PodsMetrics(&metrics, mmx)
	}
}

func TestNodesMetrics(t *testing.T) {
	uu := map[string]struct {
		nodes   *v1.NodeList
		metrics *v1beta1.NodeMetricsList
		eSize   int
		e       client.NodesMetrics
	}{
		"duds": {
			eSize: 0,
		},
		"no_nodes": {
			metrics: &v1beta1.NodeMetricsList{
				Items: []v1beta1.NodeMetrics{
					*makeMxNode("n1", "10", "8Gi"),
					*makeMxNode("n2", "50m", "1Mi"),
				},
			},
			eSize: 0,
		},
		"no_metrics": {
			nodes: &v1.NodeList{
				Items: []v1.Node{
					makeNode("n1", "32", "128Gi", "50m", "2Mi"),
					makeNode("n2", "8", "4Gi", "50m", "10Mi"),
				},
			},
			eSize: 0,
		},
		"ok": {
			nodes: &v1.NodeList{
				Items: []v1.Node{
					makeNode("n1", "32", "128Gi", "32", "128Gi"),
					makeNode("n2", "8", "4Gi", "8", "4Gi"),
				},
			},
			metrics: &v1beta1.NodeMetricsList{
				Items: []v1beta1.NodeMetrics{
					*makeMxNode("n1", "10", "8Gi"),
					*makeMxNode("n2", "50m", "1Mi"),
				},
			},
			eSize: 2,
			e: client.NodesMetrics{
				"n1": client.NodeMetrics{
					TotalCPU:       32000,
					TotalMEM:       131072,
					AllocatableCPU: 32000,
					AllocatableMEM: 131072,
					AvailableCPU:   22000,
					AvailableMEM:   122880,
					CurrentMetrics: client.CurrentMetrics{
						CurrentCPU: 10000,
						CurrentMEM: 8192,
					},
				},
			},
		},
	}

	m := client.NewMetricsServer(nil)
	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			mmx := make(client.NodesMetrics)
			m.NodesMetrics(u.nodes, u.metrics, mmx)

			assert.Len(t, mmx, u.eSize)
			if u.eSize == 0 {
				return
			}
			mx, ok := mmx["n1"]
			assert.True(t, ok)
			assert.Equal(t, u.e["n1"], mx)
		})
	}
}

func BenchmarkNodesMetrics(b *testing.B) {
	nodes := v1.NodeList{
		Items: []v1.Node{
			makeNode("n1", "100m", "4Mi", "100m", "2Mi"),
			makeNode("n2", "100m", "4Mi", "100m", "2Mi"),
		},
	}

	metrics := v1beta1.NodeMetricsList{
		Items: []v1beta1.NodeMetrics{
			*makeMxNode("n1", "50m", "1Mi"),
			*makeMxNode("n2", "50m", "1Mi"),
		},
	}

	m := client.NewMetricsServer(nil)
	mmx := make(client.NodesMetrics)

	b.ResetTimer()
	b.ReportAllocs()
	for range b.N {
		m.NodesMetrics(&nodes, &metrics, mmx)
	}
}

func TestClusterLoad(t *testing.T) {
	uu := map[string]struct {
		nodes   *v1.NodeList
		metrics *v1beta1.NodeMetricsList
		eSize   int
		e       client.ClusterMetrics
	}{
		"duds": {
			eSize: 0,
		},
		"no_nodes": {
			metrics: &v1beta1.NodeMetricsList{
				Items: []v1beta1.NodeMetrics{
					*makeMxNode("n1", "10", "8Gi"),
					*makeMxNode("n2", "50m", "1Mi"),
				},
			},
			eSize: 0,
		},
		"no_metrics": {
			nodes: &v1.NodeList{
				Items: []v1.Node{
					makeNode("n1", "32", "128Gi", "50m", "2Mi"),
					makeNode("n2", "8", "4Gi", "50m", "10Mi"),
				},
			},
			eSize: 0,
		},
		"ok": {
			nodes: &v1.NodeList{
				Items: []v1.Node{
					makeNode("n1", "100m", "4Mi", "50m", "2Mi"),
					makeNode("n2", "100m", "4Mi", "50m", "2Mi"),
				},
			},
			metrics: &v1beta1.NodeMetricsList{
				Items: []v1beta1.NodeMetrics{
					*makeMxNode("n1", "50m", "1Mi"),
					*makeMxNode("n2", "50m", "1Mi"),
				},
			},
			eSize: 2,
			e: client.ClusterMetrics{
				PercCPU: 100.0,
				PercMEM: 50.0,
			},
		},
	}

	m := client.NewMetricsServer(nil)
	for k := range uu {
		u := uu[k]
		t.Run(k, func(t *testing.T) {
			var cmx client.ClusterMetrics
			_ = m.ClusterLoad(u.nodes, u.metrics, &cmx)
			assert.Equal(t, u.e, cmx)
		})
	}
}

func BenchmarkClusterLoad(b *testing.B) {
	nodes := v1.NodeList{
		Items: []v1.Node{
			makeNode("n1", "100m", "4Mi", "50m", "2Mi"),
			makeNode("n2", "100m", "4Mi", "50m", "2Mi"),
		},
	}

	metrics := v1beta1.NodeMetricsList{
		Items: []v1beta1.NodeMetrics{
			*makeMxNode("n1", "50m", "1Mi"),
			*makeMxNode("n2", "50m", "1Mi"),
		},
	}

	m := client.NewMetricsServer(nil)
	var mx client.ClusterMetrics
	b.ResetTimer()
	b.ReportAllocs()
	for range b.N {
		_ = m.ClusterLoad(&nodes, &metrics, &mx)
	}
}

// ----------------------------------------------------------------------------
// Helpers...

func makeMxPod(name, cpu, mem string) *v1beta1.PodMetrics {
	return &v1beta1.PodMetrics{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: "default",
		},
		Containers: []v1beta1.ContainerMetrics{
			{Usage: makeRes(cpu, mem)},
			{Usage: makeRes(cpu, mem)},
			{Usage: makeRes(cpu, mem)},
		},
	}
}

func makeNode(name, tcpu, tmem, acpu, amem string) v1.Node {
	return v1.Node{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Status: v1.NodeStatus{
			Capacity:    makeRes(tcpu, tmem),
			Allocatable: makeRes(acpu, amem),
		},
	}
}

func makeMxNode(name, cpu, mem string) *v1beta1.NodeMetrics {
	return &v1beta1.NodeMetrics{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Usage: makeRes(cpu, mem),
	}
}

func makeRes(c, m string) v1.ResourceList {
	cpu, _ := resource.ParseQuantity(c)
	mem, _ := resource.ParseQuantity(m)

	return v1.ResourceList{
		v1.ResourceCPU:    cpu,
		v1.ResourceMemory: mem,
	}
}
