package util

import (
	"reflect"
	"testing"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"

	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
	testhelper "github.com/karmada-io/karmada/test/helper"
)

const (
	ClusterMember1 = "member1"
	ClusterMember2 = "member2"
)

func TestGetBindingClusterNames(t *testing.T) {
	tests := []struct {
		name     string
		binding  *workv1alpha2.ResourceBinding
		expected []string
	}{
		{
			name: "nil",
			binding: &workv1alpha2.ResourceBinding{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "demo-name",
					Namespace: "demo-ns",
				},
				Spec: workv1alpha2.ResourceBindingSpec{
					Clusters: []workv1alpha2.TargetCluster{},
				},
				Status: workv1alpha2.ResourceBindingStatus{},
			},
			expected: nil,
		},
		{
			name: "not nil",
			binding: &workv1alpha2.ResourceBinding{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "demo-name",
					Namespace: "demo-ns",
				},
				Spec: workv1alpha2.ResourceBindingSpec{
					Clusters: []workv1alpha2.TargetCluster{
						{
							Name: ClusterMember1,
						},
						{
							Name: ClusterMember2,
						},
					},
				},
				Status: workv1alpha2.ResourceBindingStatus{},
			},
			expected: []string{ClusterMember1, ClusterMember2},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := GetBindingClusterNames(&tt.binding.Spec)
			if !reflect.DeepEqual(got, tt.expected) {
				t.Errorf("GetBindingClusterNames() = %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestIsBindingReplicasChanged(t *testing.T) {
	tests := []struct {
		name        string
		bindingSpec *workv1alpha2.ResourceBindingSpec
		strategy    *policyv1alpha1.ReplicaSchedulingStrategy
		expected    bool
	}{
		{
			name:        "nil strategy",
			bindingSpec: &workv1alpha2.ResourceBindingSpec{},
			strategy:    nil,
			expected:    false,
		},
		{
			name:        "empty strategy",
			bindingSpec: &workv1alpha2.ResourceBindingSpec{},
			strategy:    &policyv1alpha1.ReplicaSchedulingStrategy{},
			expected:    false,
		},
		{
			name: "Duplicated strategy and replicas not changed",
			bindingSpec: &workv1alpha2.ResourceBindingSpec{
				Replicas: 5,
				Clusters: []workv1alpha2.TargetCluster{
					{
						Name:     ClusterMember1,
						Replicas: 5,
					},
					{
						Name:     ClusterMember2,
						Replicas: 5,
					},
				}},
			strategy: &policyv1alpha1.ReplicaSchedulingStrategy{ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated},
			expected: false,
		},
		{
			name: "Duplicated strategy and replicas changed",
			bindingSpec: &workv1alpha2.ResourceBindingSpec{
				Replicas: 5,
				Clusters: []workv1alpha2.TargetCluster{
					{
						Name:     ClusterMember1,
						Replicas: 3,
					},
					{
						Name:     ClusterMember2,
						Replicas: 5,
					},
				}},
			strategy: &policyv1alpha1.ReplicaSchedulingStrategy{ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDuplicated},
			expected: true,
		},
		{
			name: "Divided strategy and replicas not changed",
			bindingSpec: &workv1alpha2.ResourceBindingSpec{
				Replicas: 5,
				Clusters: []workv1alpha2.TargetCluster{
					{
						Name:     ClusterMember1,
						Replicas: 2,
					},
					{
						Name:     ClusterMember2,
						Replicas: 3,
					},
				},
			},
			strategy: &policyv1alpha1.ReplicaSchedulingStrategy{ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDivided},
			expected: false,
		},
		{
			name: "Divided strategy and replicas changed",
			bindingSpec: &workv1alpha2.ResourceBindingSpec{
				Replicas: 5,
				Clusters: []workv1alpha2.TargetCluster{
					{
						Name:     ClusterMember1,
						Replicas: 3,
					},
					{
						Name:     ClusterMember2,
						Replicas: 3,
					},
				},
			},
			strategy: &policyv1alpha1.ReplicaSchedulingStrategy{ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDivided},
			expected: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.bindingSpec == nil {
				t.FailNow()
			}
			got := IsBindingReplicasChanged(tt.bindingSpec, tt.strategy)
			if got != tt.expected {
				t.Errorf("IsBindingReplicasChanged() = %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestGetSumOfReplicas(t *testing.T) {
	tests := []struct {
		name     string
		clusters []workv1alpha2.TargetCluster
		expected int32
	}{
		{
			name:     "empty",
			clusters: []workv1alpha2.TargetCluster{},
			expected: 0,
		},
		{
			name: "not empty",
			clusters: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember1,
					Replicas: 2,
				},
				{
					Name:     ClusterMember2,
					Replicas: 3,
				},
			},
			expected: 5,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := GetSumOfReplicas(tt.clusters)
			if got != tt.expected {
				t.Errorf("GetSumOfReplicas() = %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestConvertToClusterNames(t *testing.T) {
	tests := []struct {
		name     string
		clusters []workv1alpha2.TargetCluster
		expected sets.Set[string]
	}{
		{
			name:     "empty",
			clusters: []workv1alpha2.TargetCluster{},
			expected: sets.New[string](),
		},
		{
			name: "not empty",
			clusters: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember1,
					Replicas: 2,
				},
				{
					Name:     ClusterMember2,
					Replicas: 3,
				},
			},
			expected: sets.New(ClusterMember1, ClusterMember2),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := ConvertToClusterNames(tt.clusters)
			if !reflect.DeepEqual(got, tt.expected) {
				t.Errorf("ConvertToClusterNames() = %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestMergeTargetClusters(t *testing.T) {
	tests := []struct {
		name     string
		old      []workv1alpha2.TargetCluster
		new      []workv1alpha2.TargetCluster
		expected []workv1alpha2.TargetCluster
	}{
		{
			name:     "empty",
			old:      []workv1alpha2.TargetCluster{},
			new:      []workv1alpha2.TargetCluster{},
			expected: []workv1alpha2.TargetCluster{},
		},
		{
			name: "old clusters are empty",
			old:  []workv1alpha2.TargetCluster{},
			new: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember2,
					Replicas: 3,
				},
			},
			expected: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember2,
					Replicas: 3,
				},
			},
		},
		{
			name: "new clusters are empty",
			old: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember1,
					Replicas: 3,
				},
			},
			new: []workv1alpha2.TargetCluster{},
			expected: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember1,
					Replicas: 3,
				},
			},
		},
		{
			name: "no cluster with the same name",
			old: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember1,
					Replicas: 2,
				},
			},
			new: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember2,
					Replicas: 3,
				},
			},
			expected: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember1,
					Replicas: 2,
				},
				{
					Name:     ClusterMember2,
					Replicas: 3,
				},
			},
		},
		{
			name: "some clusters have the same name in the old and new clusters",
			old: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember1,
					Replicas: 2,
				},
			},
			new: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember1,
					Replicas: 4,
				},
				{
					Name:     ClusterMember2,
					Replicas: 3,
				},
			},
			expected: []workv1alpha2.TargetCluster{
				{
					Name:     ClusterMember1,
					Replicas: 6,
				},
				{
					Name:     ClusterMember2,
					Replicas: 3,
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := MergeTargetClusters(tt.old, tt.new)
			if !testhelper.IsScheduleResultEqual(got, tt.expected) {
				t.Errorf("MergeTargetClusters() = %v, want %v", got, tt.expected)
			}
		})
	}
}
