package mutation

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/informers"
	clientSet "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/fake"
	"k8s.io/client-go/tools/cache"
)

// TODO: move to a shared test file
func newFakeKubeClient() (clientSet.Interface, informers.SharedInformerFactory) {
	kubeClient := fake.NewSimpleClientset()
	informerFactory := informers.NewSharedInformerFactory(kubeClient, 0)
	return kubeClient, informerFactory
}

func TestPodNodeSelector(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	nsCreatedCh := make(chan struct{}, 1)
	kubeClient, informerFactory := newFakeKubeClient()
	namespaceInformer := informerFactory.Core().V1().Namespaces()
	namespaceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) { nsCreatedCh <- struct{}{} },
	})
	podNodeSelectorMutator := NewPodNodeSelectorMutator(kubeClient, namespaceInformer)
	informerFactory.Start(ctx.Done())

	type testCase struct {
		name                        string
		expectError                 bool
		namespaceNodeSelectorString string
		namespaceNodeSelector       map[string]string
		podNodeSelector             map[string]string
	}
	cases := []testCase{
		{name: "nodeSelector conflict", expectError: true,
			namespaceNodeSelector: map[string]string{"host": "a"}, podNodeSelector: map[string]string{"host": "b"}},
		{name: "invalid namespaceNodeSelectorString character", expectError: true, namespaceNodeSelectorString: "@#$%^&"},
		{name: "invalid namespaceNodeSelectorString pattern", expectError: true, namespaceNodeSelectorString: "host:a"},
		{name: "empty nodeSelector", expectError: false},
		{name: "empty podNodeSelector", expectError: false, namespaceNodeSelector: map[string]string{"host": "a"}},
		{name: "empty namespaceNodeSelector", expectError: false, podNodeSelector: map[string]string{"host": "a"}},
		{name: "identical nodeSelectors", expectError: false, namespaceNodeSelector: map[string]string{"host": "a"},
			podNodeSelector: map[string]string{"host": "a"}},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			namespaceNodeSelector := labels.Set(c.namespaceNodeSelector).String()
			if len(c.namespaceNodeSelectorString) > 0 {
				namespaceNodeSelector = c.namespaceNodeSelectorString
			}
			ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
				Name:        c.name,
				Annotations: map[string]string{podNodeSelectorAnnotationKey: namespaceNodeSelector}},
			}
			_, err := kubeClient.CoreV1().Namespaces().Create(ctx, &ns, metav1.CreateOptions{})
			assert.NoError(t, err)
			<-nsCreatedCh

			pod := corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{Name: c.name, Namespace: ns.Name},
				Spec:       corev1.PodSpec{NodeSelector: c.podNodeSelector},
			}
			err = podNodeSelectorMutator.Mutate(ctx, &pod)
			if c.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.True(t, labels.Equals(pod.Spec.NodeSelector, labels.Merge(c.namespaceNodeSelector, c.podNodeSelector)),
					"namespaceNodeSelector and pod.spec.nodeselector should be merged")
			}
		})
	}
}
