package helper

import (
	"context"
	"reflect"
	"testing"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"

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

var (
	unreachableTaintTemplate = &corev1.Taint{
		Key:    clusterv1alpha1.TaintClusterUnreachable,
		Effect: corev1.TaintEffectNoExecute,
	}

	notReadyTaintTemplate = &corev1.Taint{
		Key:    clusterv1alpha1.TaintClusterNotReady,
		Effect: corev1.TaintEffectNoExecute,
	}
)

func TestUpdateClusterControllerTaint(t *testing.T) {
	type args struct {
		taints         []corev1.Taint
		taintsToAdd    []*corev1.Taint
		taintsToRemove []*corev1.Taint
	}
	tests := []struct {
		name       string
		args       args
		wantTaints []corev1.Taint
		wantErr    bool
	}{
		{
			name: "ready condition from true to false",
			args: args{
				taints:         nil,
				taintsToAdd:    []*corev1.Taint{notReadyTaintTemplate.DeepCopy()},
				taintsToRemove: []*corev1.Taint{unreachableTaintTemplate.DeepCopy()},
			},
			wantTaints: []corev1.Taint{*notReadyTaintTemplate},
			wantErr:    false,
		},
		{
			name: "ready condition from true to unknown",
			args: args{
				taints:         nil,
				taintsToAdd:    []*corev1.Taint{unreachableTaintTemplate.DeepCopy()},
				taintsToRemove: []*corev1.Taint{notReadyTaintTemplate.DeepCopy()},
			},
			wantTaints: []corev1.Taint{*unreachableTaintTemplate},
			wantErr:    false,
		},
		{
			name: "ready condition from false to unknown",
			args: args{
				taints:         []corev1.Taint{*notReadyTaintTemplate},
				taintsToAdd:    []*corev1.Taint{unreachableTaintTemplate.DeepCopy()},
				taintsToRemove: []*corev1.Taint{notReadyTaintTemplate.DeepCopy()},
			},
			wantTaints: []corev1.Taint{*unreachableTaintTemplate},
			wantErr:    false,
		},
		{
			name: "ready condition from false to true",
			args: args{
				taints:         []corev1.Taint{*notReadyTaintTemplate},
				taintsToAdd:    []*corev1.Taint{},
				taintsToRemove: []*corev1.Taint{notReadyTaintTemplate.DeepCopy(), unreachableTaintTemplate.DeepCopy()},
			},
			wantTaints: nil,
			wantErr:    false,
		},
		{
			name: "ready condition from unknown to true",
			args: args{
				taints:         []corev1.Taint{*unreachableTaintTemplate},
				taintsToAdd:    []*corev1.Taint{},
				taintsToRemove: []*corev1.Taint{notReadyTaintTemplate.DeepCopy(), unreachableTaintTemplate.DeepCopy()},
			},
			wantTaints: nil,
			wantErr:    false,
		},
		{
			name: "ready condition from unknown to false",
			args: args{
				taints:         []corev1.Taint{*unreachableTaintTemplate},
				taintsToAdd:    []*corev1.Taint{notReadyTaintTemplate.DeepCopy()},
				taintsToRemove: []*corev1.Taint{unreachableTaintTemplate.DeepCopy()},
			},
			wantTaints: []corev1.Taint{*notReadyTaintTemplate},
			wantErr:    false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx, cancel := context.WithCancel(context.Background())
			defer cancel()

			cluster := &clusterv1alpha1.Cluster{
				ObjectMeta: metav1.ObjectMeta{Name: "member"},
				Spec: clusterv1alpha1.ClusterSpec{
					Taints: tt.args.taints,
				},
			}
			c := fakeclient.NewClientBuilder().WithScheme(gclient.NewSchema()).WithObjects(cluster).Build()

			if err := UpdateClusterControllerTaint(ctx, c, tt.args.taintsToAdd, tt.args.taintsToRemove, cluster); (err != nil) != tt.wantErr {
				t.Errorf("UpdateClusterControllerTaint() error = %v, wantErr %v", err, tt.wantErr)
			}

			if err := c.Get(ctx, client.ObjectKey{Name: cluster.Name}, cluster); err != nil {
				t.Fatalf("Failed to get cluster %s: %v", cluster.Name, err)
			}

			if len(cluster.Spec.Taints) != len(tt.wantTaints) {
				t.Errorf("Cluster gotTaints = %v, want %v", cluster.Spec.Taints, tt.wantTaints)
			}
			for i := range cluster.Spec.Taints {
				if cluster.Spec.Taints[i].Key != tt.wantTaints[i].Key ||
					cluster.Spec.Taints[i].Value != tt.wantTaints[i].Value ||
					cluster.Spec.Taints[i].Effect != tt.wantTaints[i].Effect {
					t.Errorf("Cluster gotTaints = %v, want %v", cluster.Spec.Taints, tt.wantTaints)
				}
			}
		})
	}
}

func TestTaintExists(t *testing.T) {
	type args struct {
		taints      []corev1.Taint
		taintToFind *corev1.Taint
	}
	tests := []struct {
		name string
		args args
		want bool
	}{
		{
			name: "exist",
			args: args{
				taints: []corev1.Taint{
					{
						Key:    clusterv1alpha1.TaintClusterUnreachable,
						Effect: corev1.TaintEffectNoExecute,
					},
					{
						Key:    clusterv1alpha1.TaintClusterNotReady,
						Effect: corev1.TaintEffectNoExecute,
					},
				},
				taintToFind: &corev1.Taint{
					Key:    clusterv1alpha1.TaintClusterUnreachable,
					Effect: corev1.TaintEffectNoExecute,
				},
			},
			want: true,
		},
		{
			name: "not exist",
			args: args{
				taints: []corev1.Taint{
					{
						Key:    clusterv1alpha1.TaintClusterUnreachable,
						Effect: corev1.TaintEffectNoExecute,
					},
					{
						Key:    clusterv1alpha1.TaintClusterNotReady,
						Effect: corev1.TaintEffectNoExecute,
					},
				},
				taintToFind: &corev1.Taint{
					Key:    clusterv1alpha1.TaintClusterNotReady,
					Effect: corev1.TaintEffectNoSchedule,
				},
			},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := TaintExists(tt.args.taints, tt.args.taintToFind); got != tt.want {
				t.Errorf("TaintExists() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestTolerationExists(t *testing.T) {
	type args struct {
		tolerations      []corev1.Toleration
		tolerationToFind *corev1.Toleration
	}
	tests := []struct {
		name string
		args args
		want bool
	}{
		{
			name: "not exist",
			args: args{
				tolerations: []corev1.Toleration{
					{
						Key:      clusterv1alpha1.TaintClusterUnreachable,
						Effect:   corev1.TaintEffectNoExecute,
						Operator: corev1.TolerationOpEqual,
						Value:    "foo",
					},
					{
						Key:      clusterv1alpha1.TaintClusterNotReady,
						Effect:   corev1.TaintEffectNoExecute,
						Operator: corev1.TolerationOpEqual,
						Value:    "foo",
					},
				},
				tolerationToFind: &corev1.Toleration{
					Key:      clusterv1alpha1.TaintClusterNotReady,
					Effect:   corev1.TaintEffectNoSchedule,
					Operator: corev1.TolerationOpEqual,
					Value:    "foo",
				},
			},
			want: false,
		},
		{
			name: "exist",
			args: args{
				tolerations: []corev1.Toleration{
					{
						Key:      clusterv1alpha1.TaintClusterUnreachable,
						Effect:   corev1.TaintEffectNoExecute,
						Operator: corev1.TolerationOpEqual,
						Value:    "foo",
					},
					{
						Key:      clusterv1alpha1.TaintClusterNotReady,
						Effect:   corev1.TaintEffectNoExecute,
						Operator: corev1.TolerationOpEqual,
						Value:    "foo",
					},
				},
				tolerationToFind: &corev1.Toleration{
					Key:      clusterv1alpha1.TaintClusterNotReady,
					Effect:   corev1.TaintEffectNoExecute,
					Operator: corev1.TolerationOpEqual,
					Value:    "foo",
				},
			},
			want: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := TolerationExists(tt.args.tolerations, tt.args.tolerationToFind); got != tt.want {
				t.Errorf("TolerationExists() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestHasNoExecuteTaints(t *testing.T) {
	tests := []struct {
		name   string
		taints []corev1.Taint
		want   bool
	}{
		{
			name: "has NoExecute taints",
			taints: []corev1.Taint{
				{
					Key:    clusterv1alpha1.TaintClusterUnreachable,
					Effect: corev1.TaintEffectNoExecute,
				},
				{
					Key:    clusterv1alpha1.TaintClusterNotReady,
					Effect: corev1.TaintEffectNoSchedule,
				},
			},
			want: true,
		},
		{
			name: "no NoExecute taints",
			taints: []corev1.Taint{
				{
					Key:    clusterv1alpha1.TaintClusterUnreachable,
					Effect: corev1.TaintEffectPreferNoSchedule,
				},
				{
					Key:    clusterv1alpha1.TaintClusterNotReady,
					Effect: corev1.TaintEffectNoSchedule,
				},
			},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := HasNoExecuteTaints(tt.taints); got != tt.want {
				t.Errorf("HasNoExecuteTaints() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestGetNoExecuteTaints(t *testing.T) {
	tests := []struct {
		name   string
		taints []corev1.Taint
		want   []corev1.Taint
	}{
		{
			name: "has NoExecute taints",
			taints: []corev1.Taint{
				{
					Key:    clusterv1alpha1.TaintClusterUnreachable,
					Effect: corev1.TaintEffectNoExecute,
				},
				{
					Key:    clusterv1alpha1.TaintClusterNotReady,
					Effect: corev1.TaintEffectNoSchedule,
				},
			},
			want: []corev1.Taint{
				{
					Key:    clusterv1alpha1.TaintClusterUnreachable,
					Effect: corev1.TaintEffectNoExecute,
				},
			},
		},
		{
			name: "no NoExecute taints",
			taints: []corev1.Taint{
				{
					Key:    clusterv1alpha1.TaintClusterUnreachable,
					Effect: corev1.TaintEffectPreferNoSchedule,
				},
				{
					Key:    clusterv1alpha1.TaintClusterNotReady,
					Effect: corev1.TaintEffectNoSchedule,
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := GetNoExecuteTaints(tt.taints); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("GetNoExecuteTaints() = %v, want %v", got, tt.want)
			}
		})
	}
}
