//go:build unit || all
// +build unit all

package k8s

import (
	"context"
	"testing"

	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/kubernetes/scheme"
	"sigs.k8s.io/controller-runtime/pkg/client"
	_ "sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	_ "sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestSetAnnotationPatch(t *testing.T) {
	const (
		podName = "test-name"
		podNs   = "test-namespace"
	)
	cases := map[string]struct {
		obj    client.Object
		key    string
		value  string
		expect map[string]string
	}{
		"add annotation to object without annotations": {
			obj: &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:      podName,
					Namespace: podNs,
				},
			},
			key:   "foo",
			value: "bar",
			expect: map[string]string{
				"foo": "bar",
			},
		},
		"add annotation to object with existing annotations": {
			obj: &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Annotations: map[string]string{
						"hello": "world",
					},
					Name:      podName,
					Namespace: podNs,
				},
			},
			key:   "foo",
			value: "bar",
			expect: map[string]string{
				"hello": "world",
				"foo":   "bar",
			},
		},
		"replace existing annotation": {
			obj: &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Annotations: map[string]string{
						"foo": "old-value",
					},
					Name:      podName,
					Namespace: podNs,
				},
			},
			key:   "foo",
			value: "new-value",
			expect: map[string]string{
				"foo": "new-value",
			},
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			s := runtime.NewScheme()
			g.Expect(scheme.AddToScheme(s)).To(Succeed())
			cli := fake.NewClientBuilder().WithScheme(s).WithObjects(tc.obj).Build()
			patch, err := ConstructPatch(SetAnnotationPatch(tc.key, tc.value)(tc.obj))
			g.Expect(err).To(Succeed())
			g.Expect(cli.Patch(context.TODO(), tc.obj, patch)).To(Succeed())
			newObj := corev1.Pod{}
			g.Expect(cli.Get(context.TODO(), types.NamespacedName{Name: podName, Namespace: podNs}, &newObj)).To(Succeed())
			g.Expect(newObj.GetAnnotations()).To(Equal(tc.expect))
		})
	}
}

func TestRemoveAnnotationPatch(t *testing.T) {
	const (
		podName = "test-name"
		podNs   = "test-namespace"
	)
	cases := map[string]struct {
		obj    client.Object
		key    string
		expect map[string]string
	}{
		"remove existing annotation": {
			obj: &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Annotations: map[string]string{
						"foo": "bar",
						"baz": "qux",
					},
					Name:      podName,
					Namespace: podNs,
				},
			},
			key: "foo",
			expect: map[string]string{
				"baz": "qux",
			},
		},
		"remove non-existing annotation": {
			obj: &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Annotations: map[string]string{
						"baz": "qux",
					},
					Name:      podName,
					Namespace: podNs,
				},
			},
			key: "foo",
			expect: map[string]string{
				"baz": "qux",
			},
		},
		"remove annotation from object without annotations": {
			obj: &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:      podName,
					Namespace: podNs,
				},
			},
			key:    "foo",
			expect: nil,
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			s := runtime.NewScheme()
			g.Expect(scheme.AddToScheme(s)).To(Succeed())
			cli := fake.NewClientBuilder().WithScheme(s).WithObjects(tc.obj).Build()
			patch, err := ConstructPatch(RemoveAnnotationPatch(tc.key)(tc.obj))
			g.Expect(err).To(Succeed())
			g.Expect(cli.Patch(context.TODO(), tc.obj, patch)).To(Succeed())
			newObj := corev1.Pod{}
			g.Expect(cli.Get(context.TODO(), types.NamespacedName{Name: podName, Namespace: podNs}, &newObj)).To(Succeed())
			g.Expect(newObj.GetAnnotations()).To(Equal(tc.expect))
		})
	}
}
