package labels

import (
	"testing"

	"github.com/stretchr/testify/assert"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func TestNewOwnerLabels(t *testing.T) {
	cases := []struct {
		desc           string
		owner          client.Object
		labels         map[string]string
		expectedLabels map[string]string
	}{
		{
			desc: "normal case",
			owner: &corev1.Namespace{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Namespace",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					UID:  types.UID("test-uid"),
				},
			},
			labels: map[string]string{},
			expectedLabels: map[string]string{
				LabelKeyName:      "test",
				LabelKeyNamespace: "",
				LabelKeyUID:       "test-uid",
				LabelKeyOwnedBy:   "v1.namespace",
			},
		},
		{
			desc: "case which api group is not core",
			owner: &appsv1.Deployment{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Deployment",
					APIVersion: "apps/v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test",
					Namespace: "test",
					UID:       types.UID("test-uid"),
				},
			},
			labels: map[string]string{},
			expectedLabels: map[string]string{
				LabelKeyName:      "test",
				LabelKeyNamespace: "test",
				LabelKeyUID:       "test-uid",
				LabelKeyOwnedBy:   "apps.v1.deployment",
			},
		},
		{
			desc: "nil label case",
			owner: &corev1.Namespace{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Namespace",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					UID:  types.UID("test-uid"),
				},
			},
			labels: nil,
			expectedLabels: map[string]string{
				LabelKeyName:      "test",
				LabelKeyNamespace: "",
				LabelKeyUID:       "test-uid",
				LabelKeyOwnedBy:   "v1.namespace",
			},
		},
		{
			desc: "label has other keys",
			owner: &corev1.Namespace{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Namespace",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					UID:  types.UID("test-uid"),
				},
			},
			labels: map[string]string{
				"others": "others",
			},
			expectedLabels: map[string]string{
				"others":          "others",
				LabelKeyName:      "test",
				LabelKeyNamespace: "",
				LabelKeyUID:       "test-uid",
				LabelKeyOwnedBy:   "v1.namespace",
			},
		},
	}

	for _, c := range cases {
		labels := NewOwnerLabels(c.owner, c.labels)
		assert.Equal(t, c.expectedLabels, labels, c.desc)
	}
}

func TestMatchOwnerLabels(t *testing.T) {
	cases := []struct {
		desc    string
		owner   client.Object
		labels  map[string]string
		owned   bool
		matched bool
	}{
		{
			desc: "normal case",
			owner: &corev1.Namespace{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Namespace",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					UID:  types.UID("test-uid"),
				},
			},
			labels: map[string]string{
				LabelKeyName:      "test",
				LabelKeyNamespace: "",
				LabelKeyUID:       "test-uid",
				LabelKeyOwnedBy:   "v1.namespace",
			},
			owned:   true,
			matched: true,
		},
		{
			desc: "labels is nil",
			owner: &corev1.Namespace{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Namespace",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					UID:  types.UID("test-uid"),
				},
			},
			labels:  nil,
			owned:   false,
			matched: false,
		},
		{
			desc: "label owned-by not matched",
			owner: &corev1.Namespace{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Namespace",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					UID:  types.UID("test-uid"),
				},
			},
			labels: map[string]string{
				LabelKeyName:      "test",
				LabelKeyNamespace: "",
				LabelKeyUID:       "test-uid",
				LabelKeyOwnedBy:   "apps.v1.deployment",
			},
			owned:   false,
			matched: false,
		},
		{
			desc: "label namespace not matched",
			owner: &corev1.Namespace{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Namespace",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					UID:  types.UID("test-uid"),
				},
			},
			labels: map[string]string{
				LabelKeyName:      "test",
				LabelKeyNamespace: "xxx",
				LabelKeyUID:       "test-uid",
				LabelKeyOwnedBy:   "v1.namespace",
			},
			owned:   false,
			matched: false,
		},
		{
			desc: "label name not matched",
			owner: &corev1.Namespace{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Namespace",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					UID:  types.UID("test-uid"),
				},
			},
			labels: map[string]string{
				LabelKeyName:      "testxxx",
				LabelKeyNamespace: "",
				LabelKeyUID:       "test-uid",
				LabelKeyOwnedBy:   "v1.namespace",
			},
			owned:   false,
			matched: false,
		},
		{
			desc: "label uid not matched",
			owner: &corev1.Namespace{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Namespace",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
					UID:  types.UID("test-uid"),
				},
			},
			labels: map[string]string{
				LabelKeyName:      "test",
				LabelKeyNamespace: "",
				LabelKeyUID:       "test-uid-xxx",
				LabelKeyOwnedBy:   "v1.namespace",
			},
			owned:   true,
			matched: false,
		},
	}

	for _, c := range cases {
		owned, matched := MatchOwnerLabels(c.owner, c.labels)
		assert.Equal(t, c.owned, owned, c.desc)
		assert.Equal(t, c.matched, matched, c.desc)
	}
}

func TestGetOwnerFromLabels(t *testing.T) {
	cases := []struct {
		desc      string
		labels    map[string]string
		ownedBy   string
		namespace string
		name      string
		uid       string
	}{
		{
			desc: "normal case",
			labels: map[string]string{
				LabelKeyName:      "test",
				LabelKeyNamespace: "xxx",
				LabelKeyUID:       "test-uid",
				LabelKeyOwnedBy:   "v1.namespace",
			},
			ownedBy:   "v1.namespace",
			namespace: "xxx",
			name:      "test",
			uid:       "test-uid",
		},
		{
			desc:      "labels is nil",
			labels:    nil,
			ownedBy:   "",
			namespace: "",
			name:      "",
			uid:       "",
		},
		{
			desc: "some label key is not found",
			labels: map[string]string{
				LabelKeyOwnedBy: "v1.namespace",
			},
			ownedBy:   "v1.namespace",
			namespace: "",
			name:      "",
			uid:       "",
		},
	}

	for _, c := range cases {
		ownedBy, ns, name, uid := GetOwnerFromLabels(c.labels)
		assert.Equal(t, c.ownedBy, ownedBy, c.desc)
		assert.Equal(t, c.namespace, ns, c.desc)
		assert.Equal(t, c.name, name, c.desc)
		assert.Equal(t, c.uid, uid, c.desc)
	}
}

func TestGVKString(t *testing.T) {
	cases := []struct {
		desc string
		gvk  schema.GroupVersionKind
		str  string
	}{
		{
			desc: "normal case",
			gvk: schema.GroupVersionKind{
				Group:   "aaa",
				Version: "bbb",
				Kind:    "ccc",
			},
			str: "aaa.bbb.ccc",
		},
		{
			desc: "group is empty",
			gvk: schema.GroupVersionKind{
				Group:   "",
				Version: "bbb",
				Kind:    "ccc",
			},
			str: "bbb.ccc",
		},
	}

	for _, c := range cases {
		s := GVKString(&c.gvk)
		assert.Equal(t, c.str, s, c.desc)
	}
}
