package mutatefuncs

import (
	"testing"

	gkev1beta1 "github.com/crossplane-contrib/provider-gcp/apis/container/v1beta1"
	"github.com/stretchr/testify/require"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/tidbcloud/infra-provider/pkg/consts"
	nodepoolutil "github.com/tidbcloud/infra-provider/pkg/nodepool"
)

func TestMutateGKENodePoolDropsCurrentComputeClassMetadataWhenExpectedHasNoIntent(t *testing.T) {
	current := &gkev1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				consts.AnnoKeyGKENodePoolComputeClass:        "shared-priority",
				consts.AnnoKeyGKENodePoolComputeClassApplied: "shared-priority",
			},
		},
		Spec: gkev1beta1.NodePoolSpec{
			ForProvider: gkev1beta1.NodePoolParameters{
				Config: &gkev1beta1.NodeConfig{
					Labels: map[string]string{
						consts.GKEComputeClassLabelKey: "shared-priority",
						consts.LabelKeyFor3LayerArch:   "true",
						consts.K8sVersionLabelKey:      "old-version",
						"preserve":                     "label",
					},
				},
			},
		},
	}
	expected := &gkev1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				"expected": "annotation",
			},
		},
		Spec: gkev1beta1.NodePoolSpec{
			ForProvider: gkev1beta1.NodePoolParameters{
				Config: &gkev1beta1.NodeConfig{
					Labels: map[string]string{
						consts.K8sVersionLabelKey: "1.35",
					},
				},
			},
		},
	}

	require.NoError(t, MutateGKENodePool(current, expected))

	require.Equal(t, "annotation", current.Annotations["expected"])
	require.NotContains(t, current.Annotations, consts.AnnoKeyGKENodePoolComputeClass)
	require.NotContains(t, current.Annotations, consts.AnnoKeyGKENodePoolComputeClassApplied)
	require.Empty(t, nodepoolutil.GKEComputeClassNameForNodePool(current))
	require.NotContains(t, current.Spec.ForProvider.Config.Labels, consts.GKEComputeClassLabelKey)
	require.NotContains(t, current.Spec.ForProvider.Config.Labels, consts.LabelKeyFor3LayerArch)
	require.NotContains(t, current.Spec.ForProvider.Config.Labels, "preserve")
	require.Equal(t, "1.35", current.Spec.ForProvider.Config.Labels[consts.K8sVersionLabelKey])
}

func TestMutateGKENodePoolAppliesExpectedComputeClassIntentWithoutProjectingSpecLabel(t *testing.T) {
	current := &gkev1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				consts.AnnoKeyGKENodePoolComputeClass:        "old-priority",
				consts.AnnoKeyGKENodePoolComputeClassApplied: "old-priority",
			},
		},
		Spec: gkev1beta1.NodePoolSpec{
			ForProvider: gkev1beta1.NodePoolParameters{
				Config: &gkev1beta1.NodeConfig{
					Labels: map[string]string{
						consts.GKEComputeClassLabelKey: "old-priority",
						"preserve":                     "label",
					},
					Taints: []*gkev1beta1.NodeTaint{
						{Key: consts.GKEComputeClassLabelKey, Value: "old-priority", Effect: "NO_SCHEDULE"},
						{Key: "current-only", Value: "drop", Effect: "NO_SCHEDULE"},
					},
				},
			},
		},
	}
	expected := &gkev1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				"expected": "annotation",
			},
		},
		Spec: gkev1beta1.NodePoolSpec{
			ForProvider: gkev1beta1.NodePoolParameters{
				Config: &gkev1beta1.NodeConfig{
					Labels: map[string]string{
						consts.GKEComputeClassLabelKey: "ignored-spec-label",
						consts.K8sVersionLabelKey:      "1.35",
					},
					Taints: []*gkev1beta1.NodeTaint{
						{Key: "dedicated", Value: "tidb-shared", Effect: "NO_SCHEDULE"},
					},
				},
			},
		},
	}
	nodepoolutil.SetGKEComputeClassForNodePool(expected, "expected-priority")

	require.NoError(t, MutateGKENodePool(current, expected))

	require.Equal(t, "annotation", current.Annotations["expected"])
	require.Equal(t, "expected-priority", current.Annotations[consts.AnnoKeyGKENodePoolComputeClass])
	require.NotContains(t, current.Annotations, consts.AnnoKeyGKENodePoolComputeClassApplied)
	require.Equal(t, "expected-priority", nodepoolutil.GKEComputeClassNameForNodePool(current))
	require.Equal(t, "old-priority", current.Spec.ForProvider.Config.Labels[consts.GKEComputeClassLabelKey])
	require.Equal(t, "old-priority", nodepoolutil.GKEComputeClassSpecTaintForNodePool(current))
	require.Equal(t, []*gkev1beta1.NodeTaint{
		{Key: "dedicated", Value: "tidb-shared", Effect: "NO_SCHEDULE"},
		{Key: consts.GKEComputeClassLabelKey, Value: "old-priority", Effect: "NO_SCHEDULE"},
	}, current.Spec.ForProvider.Config.Taints)
	require.Equal(t, "label", current.Spec.ForProvider.Config.Labels["preserve"])
	require.Equal(t, "1.35", current.Spec.ForProvider.Config.Labels[consts.K8sVersionLabelKey])
}

func TestMutateGKENodePoolDoesNotSynthesizeComputeClassSpecLabelForPendingIntent(t *testing.T) {
	current := &gkev1beta1.NodePool{}
	expected := &gkev1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				"expected": "annotation",
			},
		},
		Spec: gkev1beta1.NodePoolSpec{
			ForProvider: gkev1beta1.NodePoolParameters{
				Config: &gkev1beta1.NodeConfig{
					Labels: map[string]string{
						consts.GKEComputeClassLabelKey: "ignored-spec-label",
						consts.K8sVersionLabelKey:      "1.35",
					},
				},
			},
		},
	}
	nodepoolutil.SetGKEComputeClassForNodePool(expected, "expected-priority")

	require.NoError(t, MutateGKENodePool(current, expected))

	require.Equal(t, "expected-priority", current.Annotations[consts.AnnoKeyGKENodePoolComputeClass])
	require.NotContains(t, current.Annotations, consts.AnnoKeyGKENodePoolComputeClassApplied)
	require.NotContains(t, current.Spec.ForProvider.Config.Labels, consts.GKEComputeClassLabelKey)
	require.Empty(t, nodepoolutil.GKEComputeClassSpecTaintForNodePool(current))
	require.Equal(t, "1.35", current.Spec.ForProvider.Config.Labels[consts.K8sVersionLabelKey])
}

func TestMutateGKENodePoolPreservesAppliedComputeClassWhenDesiredMatches(t *testing.T) {
	current := &gkev1beta1.NodePool{}
	nodepoolutil.SetGKEComputeClassForNodePool(current, "expected-priority")
	nodepoolutil.SetGKEAppliedComputeClassForNodePool(current, "expected-priority")
	expected := &gkev1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				"expected": "annotation",
			},
		},
	}
	nodepoolutil.SetGKEComputeClassForNodePool(expected, "expected-priority")

	require.NoError(t, MutateGKENodePool(current, expected))

	require.Equal(t, "expected-priority", current.Annotations[consts.AnnoKeyGKENodePoolComputeClass])
	require.Equal(t, "expected-priority", current.Annotations[consts.AnnoKeyGKENodePoolComputeClassApplied])
	require.Equal(t, "expected-priority", current.Spec.ForProvider.Config.Labels[consts.GKEComputeClassLabelKey])
	require.Equal(t, "expected-priority", nodepoolutil.GKEComputeClassSpecTaintForNodePool(current))
}

func TestMutateGKENodePoolDoesNotMutateExpectedAnnotations(t *testing.T) {
	current := &gkev1beta1.NodePool{}
	nodepoolutil.SetGKEComputeClassForNodePool(current, "expected-priority")
	nodepoolutil.SetGKEAppliedComputeClassForNodePool(current, "expected-priority")
	expected := &gkev1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				"expected": "annotation",
			},
		},
	}
	nodepoolutil.SetGKEComputeClassForNodePool(expected, "expected-priority")

	require.NoError(t, MutateGKENodePool(current, expected))

	require.Equal(t, "expected-priority", current.Annotations[consts.AnnoKeyGKENodePoolComputeClassApplied])
	require.NotContains(t, expected.Annotations, consts.AnnoKeyGKENodePoolComputeClassApplied)
}

func TestMutateGKENodePoolDropsAppliedComputeClassWhenExpectedOnlyHasSpecLabel(t *testing.T) {
	current := &gkev1beta1.NodePool{}
	nodepoolutil.SetGKEComputeClassForNodePool(current, "shared-priority")
	nodepoolutil.SetGKEAppliedComputeClassForNodePool(current, "shared-priority")
	expected := &gkev1beta1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				"expected": "annotation",
			},
		},
		Spec: gkev1beta1.NodePoolSpec{
			ForProvider: gkev1beta1.NodePoolParameters{
				Config: &gkev1beta1.NodeConfig{
					Labels: map[string]string{
						consts.GKEComputeClassLabelKey: "shared-priority",
					},
				},
			},
		},
	}

	require.NoError(t, MutateGKENodePool(current, expected))

	require.Equal(t, "annotation", current.Annotations["expected"])
	require.NotContains(t, current.Annotations, consts.AnnoKeyGKENodePoolComputeClass)
	require.NotContains(t, current.Annotations, consts.AnnoKeyGKENodePoolComputeClassApplied)
	require.Equal(t, "shared-priority", nodepoolutil.GKEComputeClassNameForNodePool(current))
	require.Equal(t, "shared-priority", current.Spec.ForProvider.Config.Labels[consts.GKEComputeClassLabelKey])
	require.Empty(t, nodepoolutil.GKEComputeClassSpecTaintForNodePool(current))
}

func TestMutateGKENodePoolKeepsExpectedComputeClassSpecLabelWithoutIntent(t *testing.T) {
	current := &gkev1beta1.NodePool{}
	expected := &gkev1beta1.NodePool{
		Spec: gkev1beta1.NodePoolSpec{
			ForProvider: gkev1beta1.NodePoolParameters{
				Config: &gkev1beta1.NodeConfig{
					Labels: map[string]string{
						consts.GKEComputeClassLabelKey: "expected-priority",
						"desired":                      "label",
					},
				},
			},
		},
	}

	require.NoError(t, MutateGKENodePool(current, expected))

	require.Equal(t, "expected-priority", current.Spec.ForProvider.Config.Labels[consts.GKEComputeClassLabelKey])
	require.Equal(t, "label", current.Spec.ForProvider.Config.Labels["desired"])
	require.Equal(t, "expected-priority", nodepoolutil.GKEComputeClassNameForNodePool(current))
}

func TestMutateGKENodePoolKeepsEmptyExpectedComputeClassSpecLabelWithoutIntent(t *testing.T) {
	current := &gkev1beta1.NodePool{}
	expected := &gkev1beta1.NodePool{
		Spec: gkev1beta1.NodePoolSpec{
			ForProvider: gkev1beta1.NodePoolParameters{
				Config: &gkev1beta1.NodeConfig{
					Labels: map[string]string{
						consts.GKEComputeClassLabelKey: "",
						"desired":                      "label",
					},
				},
			},
		},
	}

	require.NoError(t, MutateGKENodePool(current, expected))

	require.Contains(t, current.Spec.ForProvider.Config.Labels, consts.GKEComputeClassLabelKey)
	require.Empty(t, current.Spec.ForProvider.Config.Labels[consts.GKEComputeClassLabelKey])
	require.Equal(t, "label", current.Spec.ForProvider.Config.Labels["desired"])
	require.Empty(t, nodepoolutil.GKEComputeClassNameForNodePool(current))
}
