package metrics

import (
	"strings"
	"testing"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/component-base/metrics/testutil"

	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
)

func TestClusterReadyMetrics(t *testing.T) {
	tests := []struct {
		name    string
		cluster *clusterv1alpha1.Cluster
		want    string
	}{
		{
			name: "cluster ready",
			cluster: &clusterv1alpha1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name: "foo",
				},
				Spec: clusterv1alpha1.ClusterSpec{
					SyncMode: clusterv1alpha1.Push,
				},
				Status: clusterv1alpha1.ClusterStatus{
					Conditions: []metav1.Condition{
						{
							Type:   clusterv1alpha1.ClusterConditionReady,
							Status: metav1.ConditionTrue,
						},
					},
				},
			},
			want: `
# HELP cluster_ready_state State of the cluster(1 if ready, 0 otherwise).
# TYPE cluster_ready_state gauge
cluster_ready_state{cluster_name="foo"} 1
`,
		},
		{
			name: "cluster not ready",
			cluster: &clusterv1alpha1.Cluster{
				ObjectMeta: metav1.ObjectMeta{
					Name: "foo",
				},
				Spec: clusterv1alpha1.ClusterSpec{
					SyncMode: clusterv1alpha1.Pull,
				},
				Status: clusterv1alpha1.ClusterStatus{
					Conditions: []metav1.Condition{
						{},
					},
				},
			},
			want: `
# HELP cluster_ready_state State of the cluster(1 if ready, 0 otherwise).
# TYPE cluster_ready_state gauge
cluster_ready_state{cluster_name="foo"} 0
`,
		},
	}
	for _, test := range tests {
		clusterReadyGauge.Reset()
		RecordClusterStatus(test.cluster)
		if err := testutil.CollectAndCompare(clusterReadyGauge, strings.NewReader(test.want), clusterReadyMetricsName); err != nil {
			t.Errorf("unexpected collecting result:\n%s", err)
		}
	}
}

func TestClusterTotalNodeNumberMetrics(t *testing.T) {
	testCluster := &clusterv1alpha1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: "foo",
		},
		Spec: clusterv1alpha1.ClusterSpec{
			SyncMode: clusterv1alpha1.Push,
		},
		Status: clusterv1alpha1.ClusterStatus{
			Conditions: []metav1.Condition{
				{
					Type:   clusterv1alpha1.ClusterConditionReady,
					Status: metav1.ConditionTrue,
				},
			},
			NodeSummary: &clusterv1alpha1.NodeSummary{
				TotalNum: 100,
			},
		},
	}
	want := `
# HELP cluster_node_number Number of nodes in the cluster.
# TYPE cluster_node_number gauge
cluster_node_number{cluster_name="foo"} 100
`
	clusterTotalNodeNumberGauge.Reset()
	RecordClusterStatus(testCluster)
	if err := testutil.CollectAndCompare(clusterTotalNodeNumberGauge, strings.NewReader(want), clusterTotalNodeNumberMetricsName); err != nil {
		t.Errorf("unexpected collecting result:\n%s", err)
	}
}

func TestClusterReadyNodeNumberMetrics(t *testing.T) {
	testCluster := &clusterv1alpha1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: "foo",
		},
		Spec: clusterv1alpha1.ClusterSpec{
			SyncMode: clusterv1alpha1.Push,
		},
		Status: clusterv1alpha1.ClusterStatus{
			Conditions: []metav1.Condition{
				{
					Type:   clusterv1alpha1.ClusterConditionReady,
					Status: metav1.ConditionTrue,
				},
			},
			NodeSummary: &clusterv1alpha1.NodeSummary{
				TotalNum: 100,
				ReadyNum: 10,
			},
		},
	}
	want := `
# HELP cluster_ready_node_number Number of ready nodes in the cluster.
# TYPE cluster_ready_node_number gauge
cluster_ready_node_number{cluster_name="foo"} 10
`
	clusterReadyNodeNumberGauge.Reset()
	RecordClusterStatus(testCluster)
	if err := testutil.CollectAndCompare(clusterReadyNodeNumberGauge, strings.NewReader(want), clusterReadyNodeNumberMetricsName); err != nil {
		t.Errorf("unexpected collecting result:\n%s", err)
	}
}

func TestClusterMemoryAllocatableMetrics(t *testing.T) {
	testCluster := &clusterv1alpha1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: "foo",
		},
		Spec: clusterv1alpha1.ClusterSpec{
			SyncMode: clusterv1alpha1.Push,
		},
		Status: clusterv1alpha1.ClusterStatus{
			Conditions: []metav1.Condition{
				{
					Type:   clusterv1alpha1.ClusterConditionReady,
					Status: metav1.ConditionTrue,
				},
			},
			ResourceSummary: &clusterv1alpha1.ResourceSummary{
				Allocatable: corev1.ResourceList{
					corev1.ResourceMemory: *resource.NewQuantity(200, resource.DecimalSI),
				},
			},
		},
	}
	want := `
# HELP cluster_memory_allocatable_bytes Allocatable cluster memory resource in bytes.
# TYPE cluster_memory_allocatable_bytes gauge
cluster_memory_allocatable_bytes{cluster_name="foo"} 200
`
	clusterMemoryAllocatableGauge.Reset()
	RecordClusterStatus(testCluster)
	if err := testutil.CollectAndCompare(clusterMemoryAllocatableGauge, strings.NewReader(want), clusterMemoryAllocatableMetricsName); err != nil {
		t.Errorf("unexpected collecting result:\n%s", err)
	}
}

func TestClusterCPUAllocatableMetrics(t *testing.T) {
	testCluster := &clusterv1alpha1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: "foo",
		},
		Spec: clusterv1alpha1.ClusterSpec{
			SyncMode: clusterv1alpha1.Push,
		},
		Status: clusterv1alpha1.ClusterStatus{
			Conditions: []metav1.Condition{
				{
					Type:   clusterv1alpha1.ClusterConditionReady,
					Status: metav1.ConditionTrue,
				},
			},
			ResourceSummary: &clusterv1alpha1.ResourceSummary{
				Allocatable: corev1.ResourceList{
					corev1.ResourceCPU: *resource.NewMilliQuantity(200, resource.DecimalSI),
				},
			},
		},
	}
	want := `
# HELP cluster_cpu_allocatable_number Number of allocatable CPU in the cluster.
# TYPE cluster_cpu_allocatable_number gauge
cluster_cpu_allocatable_number{cluster_name="foo"} 0.2
`
	clusterCPUAllocatableGauge.Reset()
	RecordClusterStatus(testCluster)
	if err := testutil.CollectAndCompare(clusterCPUAllocatableGauge, strings.NewReader(want), clusterCPUAllocatableMetricsName); err != nil {
		t.Errorf("unexpected collecting result:\n%s", err)
	}
}

func TestClusterPodAllocatableMetrics(t *testing.T) {
	testCluster := &clusterv1alpha1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: "foo",
		},
		Spec: clusterv1alpha1.ClusterSpec{
			SyncMode: clusterv1alpha1.Push,
		},
		Status: clusterv1alpha1.ClusterStatus{
			Conditions: []metav1.Condition{
				{
					Type:   clusterv1alpha1.ClusterConditionReady,
					Status: metav1.ConditionTrue,
				},
			},
			ResourceSummary: &clusterv1alpha1.ResourceSummary{
				Allocatable: corev1.ResourceList{
					corev1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
				},
			},
		},
	}
	want := `
# HELP cluster_pod_allocatable_number Number of allocatable pods in the cluster.
# TYPE cluster_pod_allocatable_number gauge
cluster_pod_allocatable_number{cluster_name="foo"} 110
`
	clusterPodAllocatableGauge.Reset()
	RecordClusterStatus(testCluster)
	if err := testutil.CollectAndCompare(clusterPodAllocatableGauge, strings.NewReader(want), clusterPodAllocatableMetricsName); err != nil {
		t.Errorf("unexpected collecting result:\n%s", err)
	}
}

func TestClusterMemoryAllocatedMetrics(t *testing.T) {
	testCluster := &clusterv1alpha1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: "foo",
		},
		Spec: clusterv1alpha1.ClusterSpec{
			SyncMode: clusterv1alpha1.Push,
		},
		Status: clusterv1alpha1.ClusterStatus{
			Conditions: []metav1.Condition{
				{
					Type:   clusterv1alpha1.ClusterConditionReady,
					Status: metav1.ConditionTrue,
				},
			},
			ResourceSummary: &clusterv1alpha1.ResourceSummary{
				Allocated: corev1.ResourceList{
					corev1.ResourceMemory: *resource.NewQuantity(200, resource.DecimalSI),
				},
			},
		},
	}
	want := `
# HELP cluster_memory_allocated_bytes Allocated cluster memory resource in bytes.
# TYPE cluster_memory_allocated_bytes gauge
cluster_memory_allocated_bytes{cluster_name="foo"} 200
`
	clusterMemoryAllocatedGauge.Reset()
	RecordClusterStatus(testCluster)
	if err := testutil.CollectAndCompare(clusterMemoryAllocatedGauge, strings.NewReader(want), clusterMemoryAllocatedMetricsName); err != nil {
		t.Errorf("unexpected collecting result:\n%s", err)
	}
}

func TestClusterCPUAllocatedMetrics(t *testing.T) {
	testCluster := &clusterv1alpha1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: "foo",
		},
		Spec: clusterv1alpha1.ClusterSpec{
			SyncMode: clusterv1alpha1.Push,
		},
		Status: clusterv1alpha1.ClusterStatus{
			Conditions: []metav1.Condition{
				{
					Type:   clusterv1alpha1.ClusterConditionReady,
					Status: metav1.ConditionTrue,
				},
			},
			ResourceSummary: &clusterv1alpha1.ResourceSummary{
				Allocated: corev1.ResourceList{
					corev1.ResourceCPU: *resource.NewMilliQuantity(200, resource.DecimalSI),
				},
			},
		},
	}
	want := `
# HELP cluster_cpu_allocated_number Number of allocated CPU in the cluster.
# TYPE cluster_cpu_allocated_number gauge
cluster_cpu_allocated_number{cluster_name="foo"} 0.2
`
	clusterCPUAllocatedGauge.Reset()
	RecordClusterStatus(testCluster)
	if err := testutil.CollectAndCompare(clusterCPUAllocatedGauge, strings.NewReader(want), clusterCPUAllocatedMetricsName); err != nil {
		t.Errorf("unexpected collecting result:\n%s", err)
	}
}

func TestClusterPodAllocatedMetrics(t *testing.T) {
	testCluster := &clusterv1alpha1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: "foo",
		},
		Spec: clusterv1alpha1.ClusterSpec{
			SyncMode: clusterv1alpha1.Push,
		},
		Status: clusterv1alpha1.ClusterStatus{
			Conditions: []metav1.Condition{
				{
					Type:   clusterv1alpha1.ClusterConditionReady,
					Status: metav1.ConditionTrue,
				},
			},
			ResourceSummary: &clusterv1alpha1.ResourceSummary{
				Allocated: corev1.ResourceList{
					corev1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
				},
			},
		},
	}
	want := `
# HELP cluster_pod_allocated_number Number of allocated pods in the cluster.
# TYPE cluster_pod_allocated_number gauge
cluster_pod_allocated_number{cluster_name="foo"} 110
`
	clusterPodAllocatedGauge.Reset()
	RecordClusterStatus(testCluster)
	if err := testutil.CollectAndCompare(clusterPodAllocatedGauge, strings.NewReader(want), clusterPodAllocatedMetricsName); err != nil {
		t.Errorf("unexpected collecting result:\n%s", err)
	}
}
