// Copyright 2020-2023 Project Capsule Authors.
// SPDX-License-Identifier: Apache-2.0

package e2e

import (
	"context"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
	"github.com/projectcapsule/capsule/pkg/api"
	"github.com/projectcapsule/capsule/pkg/api/meta"
	"github.com/projectcapsule/capsule/pkg/api/rbac"
)

var _ = Describe("creating a Namespace for a Tenant with additional metadata", Ordered, Label("namespace", "metadata"), func() {
	tnt := &capsulev1beta2.Tenant{
		ObjectMeta: metav1.ObjectMeta{
			Name: "e2e-tenant-metadata-admission",
			Labels: map[string]string{
				"env": "e2e",
			},
			OwnerReferences: []metav1.OwnerReference{
				{
					APIVersion: "cap",
					Kind:       "dummy",
					Name:       "tenant-metadata",
					UID:        "tenant-metadata",
				},
			},
		},
		Spec: capsulev1beta2.TenantSpec{
			Owners: rbac.OwnerListSpec{
				{
					CoreOwnerSpec: rbac.CoreOwnerSpec{
						UserSpec: rbac.UserSpec{
							Name: "e2e-tenant-metadata-admission",
							Kind: "User",
						},
					},
				},
			},
		},
	}

	JustAfterEach(func() {
		EventuallyDeletion(tnt)
	})

	It("should reject invalid additional metadata on tenant create", func() {
		tnt := &capsulev1beta2.Tenant{
			ObjectMeta: metav1.ObjectMeta{
				Name: "e2e-invalid-metadata-create",
				Labels: map[string]string{
					"env": "e2e",
				},
			},
			Spec: capsulev1beta2.TenantSpec{
				Owners: rbac.OwnerListSpec{
					{
						CoreOwnerSpec: rbac.CoreOwnerSpec{
							UserSpec: rbac.UserSpec{
								Name: "e2e-invalid-metadata-create",
								Kind: "User",
							},
						},
					},
				},
				NamespaceOptions: &capsulev1beta2.NamespaceOptions{
					ManagedMetadataOnly: false,
					AdditionalMetadataList: []api.AdditionalMetadataSelectorSpec{
						{
							Labels: map[string]string{
								"clastix.io???custom-label": "bar",
							},
						},
					},
				},
			},
		}

		Eventually(func() error {
			tnt.ResourceVersion = ""
			return k8sClient.Create(context.TODO(), tnt)
		}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())
	})

	It("should contain additional Namespace metadata", func() {
		By("prepare tenant", func() {
			tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
				AdditionalMetadata: &api.AdditionalMetadataSpec{
					Labels: map[string]string{
						"k8s.io/custom-label":         "foo",
						"clastix.io/custom-label":     "bar",
						"capsule.clastix.io/tenant":   "tenan-override",
						"kubernetes.io/metadata.name": "namespace-override",
					},
					Annotations: map[string]string{
						"k8s.io/custom-annotation":     "bizz",
						"clastix.io/custom-annotation": "buzz",
					},
				},
			}

			EventuallyCreation(func() error {
				tnt.ResourceVersion = ""
				return k8sClient.Create(context.TODO(), tnt)
			}).Should(Succeed())
			TenantReady(tnt, metav1.ConditionTrue, defaultTimeoutInterval)

			tnt = GetTenantEventually(tnt)
		})

		ns := NewNamespace("", map[string]string{
			meta.TenantLabel: tnt.GetName(),
		})
		NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed())
		NamespaceIsPartOfTenant(tnt, ns).Should(Succeed())

		By("checking additional labels", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Labels {
					if k == "capsule.clastix.io/tenant" || k == "kubernetes.io/metadata.name" {
						continue // this label is managed and shouldn't be set by the user
					}
					if ok, _ = HaveKeyWithValue(k, v).Match(ns.Labels); !ok {
						return
					}
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})
		By("checking managed labels", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				if ok, _ = HaveKeyWithValue("capsule.clastix.io/tenant", tnt.GetName()).Match(ns.Labels); !ok {
					return
				}
				if ok, _ = HaveKeyWithValue("kubernetes.io/metadata.name", ns.GetName()).Match(ns.Labels); !ok {
					return
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})

		By("checking additional annotations", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Annotations {
					if ok, _ = HaveKeyWithValue(k, v).Match(ns.Annotations); !ok {
						return
					}
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})
	})

	It("should contain additional Namespace metadata", func() {
		By("prepare tenant", func() {
			tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
				AdditionalMetadataList: []api.AdditionalMetadataSelectorSpec{
					{
						Labels: map[string]string{
							"k8s.io/custom-label":     "foo",
							"clastix.io/custom-label": "bar",
						},
						Annotations: map[string]string{
							"k8s.io/custom-annotation":     "bizz",
							"clastix.io/custom-annotation": "buzz",
						},
					},
					{
						NamespaceSelector: &metav1.LabelSelector{
							MatchExpressions: []metav1.LabelSelectorRequirement{
								{
									Key:      "matching_namespace_label",
									Operator: metav1.LabelSelectorOpIn,
									Values:   []string{"matching_namespace_label_value"},
								},
							},
						},
						Labels: map[string]string{
							"k8s.io/custom-label_2": "foo",
						},
						Annotations: map[string]string{
							"k8s.io/custom-annotation_2": "bizz",
						},
					},
					{
						NamespaceSelector: &metav1.LabelSelector{
							MatchExpressions: []metav1.LabelSelectorRequirement{
								{
									Key:      "nonmatching_namespace_label",
									Operator: metav1.LabelSelectorOpIn,
									Values:   []string{"nonmatching_namespace_label_value"},
								},
							},
						},
						Labels: map[string]string{
							"k8s.io/custom-label_3": "foo",
						},
						Annotations: map[string]string{
							"k8s.io/custom-annotation_3": "bizz",
						},
					},
					{
						Labels: map[string]string{
							"projectcapsule.dev/templated-tenant-label":    "{{ tenant.name }}",
							"projectcapsule.dev/templated-namespace-label": "{{ namespace }}",
						},
						Annotations: map[string]string{
							"projectcapsule.dev/templated-tenant-annotation":    "{{ tenant.name }}",
							"projectcapsule.dev/templated-namespace-annotation": "{{ namespace }}",
						},
					},
				},
			}

			EventuallyCreation(func() error {
				tnt.ResourceVersion = ""
				return k8sClient.Create(context.TODO(), tnt)
			}).Should(Succeed())

			tnt = GetTenantEventually(tnt)
		})

		ns := NewNamespace("", map[string]string{
			meta.TenantLabel:           tnt.GetName(),
			"matching_namespace_label": "matching_namespace_label_value",
		})
		NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed())
		NamespaceIsPartOfTenant(tnt, ns).Should(Succeed())

		By("checking templated annotations", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				if ok, _ = HaveKeyWithValue("projectcapsule.dev/templated-tenant-annotation", tnt.Name).Match(ns.Annotations); !ok {
					return
				}
				if ok, _ = HaveKeyWithValue("projectcapsule.dev/templated-namespace-annotation", ns.Name).Match(ns.Annotations); !ok {
					return
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})
		By("checking templated labels", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				if ok, _ = HaveKeyWithValue("projectcapsule.dev/templated-tenant-label", tnt.Name).Match(ns.Labels); !ok {
					return
				}
				if ok, _ = HaveKeyWithValue("projectcapsule.dev/templated-namespace-label", ns.Name).Match(ns.Labels); !ok {
					return
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})
		By("checking additional labels from entry without node selector", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[0].Labels {
					if ok, _ = HaveKeyWithValue(k, v).Match(ns.Labels); !ok {
						return
					}
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})
		By("checking additional labels from entry with matching node selector", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[1].Labels {
					if ok, _ = HaveKeyWithValue(k, v).Match(ns.Labels); !ok {
						return
					}
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})
		By("checking additional labels from entry with non-matching node selector", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[2].Labels {
					if ok, _ = HaveKeyWithValue(k, v).Match(ns.Labels); !ok {
						return
					}
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeFalse())
		})
		By("checking additional annotations from entry without node selector", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[0].Annotations {
					if ok, _ = HaveKeyWithValue(k, v).Match(ns.Annotations); !ok {
						return
					}
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})
		By("checking additional annotations from entry with matching node selector", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[1].Annotations {
					if ok, _ = HaveKeyWithValue(k, v).Match(ns.Annotations); !ok {
						return
					}
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})
		By("checking additional annotations from entry with non-matching node selector", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[2].Annotations {
					if ok, _ = HaveKeyWithValue(k, v).Match(ns.Annotations); !ok {
						return
					}
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeFalse())
		})
	})

	It("should contain additional Namespace metadata", Label("skip-on-openshift"), func() {
		By("prepare tenant", func() {
			tnt.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
				ManagedMetadataOnly: false,
				AdditionalMetadataList: []api.AdditionalMetadataSelectorSpec{
					{
						Labels: map[string]string{
							"clastix.io/custom-label": "bar",
						},
						Annotations: map[string]string{
							"clastix.io/custom-annotation": "buzz",
						},
					},
					{
						Labels: map[string]string{
							"k8s.io/custom-label": "foo",
						},
						Annotations: map[string]string{
							"k8s.io/custom-annotation": "bizz",
						},
					},
				},
			}

			EventuallyCreation(func() error {
				tnt.ResourceVersion = ""
				return k8sClient.Create(context.TODO(), tnt)
			}).Should(Succeed())
			Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, tnt)).Should(Succeed())
		})

		ns := NewNamespace("", map[string]string{
			meta.TenantLabel:           tnt.GetName(),
			"matching_namespace_label": "matching_namespace_label_value",
		})
		NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed())
		NamespaceIsPartOfTenant(tnt, ns).Should(Succeed())

		By("checking additional labels", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				for _, mv := range tnt.Spec.NamespaceOptions.AdditionalMetadataList {
					for k, v := range mv.Labels {
						if k == "capsule.clastix.io/tenant" || k == "kubernetes.io/metadata.name" {
							continue // this label is managed and shouldn't be set by the user
						}
						if ok, _ = HaveKeyWithValue(k, v).Match(ns.Labels); !ok {
							return
						}
					}
				}

				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})
		By("checking managed labels", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				if ok, _ = HaveKeyWithValue("capsule.clastix.io/tenant", tnt.GetName()).Match(ns.Labels); !ok {
					return
				}
				if ok, _ = HaveKeyWithValue("kubernetes.io/metadata.name", ns.GetName()).Match(ns.Labels); !ok {
					return
				}
				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})

		By("checking additional annotations", func() {
			Eventually(func() (ok bool) {
				ns = GetNamespaceEventually(ns.GetName())
				for _, mv := range tnt.Spec.NamespaceOptions.AdditionalMetadataList {
					for k, v := range mv.Annotations {
						if ok, _ = HaveKeyWithValue(k, v).Match(ns.Annotations); !ok {
							return
						}
					}
				}

				return
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
		})

		By("patching labels and annotations on the Namespace", func() {
			PatchNamespaceEventually(ns, func(current *corev1.Namespace) {
				if current.Labels == nil {
					current.Labels = map[string]string{}
				}
				if current.Annotations == nil {
					current.Annotations = map[string]string{}
				}

				current.Labels["test-label"] = "test-value"
				current.Labels["k8s.io/custom-label"] = "foo-value"
				current.Annotations["test-annotation"] = "test-value"
				current.Annotations["k8s.io/custom-annotation"] = "bizz-value"
			})

			tnt = GetTenantEventually(tnt)
		})

		By("Add additional annotations (Tenant Owner)", func() {
			ns = GetNamespaceEventually(ns.GetName())

			expectedLabels := map[string]string{
				"test-label":                  "test-value",
				"clastix.io/custom-label":     "bar",
				"k8s.io/custom-label":         "foo",
				"matching_namespace_label":    "matching_namespace_label_value",
				"capsule.clastix.io/tenant":   tnt.GetName(),
				"kubernetes.io/metadata.name": ns.GetName(),
				"env":                         "e2e",
			}

			Eventually(func() map[string]string {
				got := &corev1.Namespace{}
				if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, got); err != nil {
					return nil
				}
				ann := got.GetLabels()
				if ann == nil {
					ann = map[string]string{}
				}
				return ann
			}, defaultTimeoutInterval, defaultPollInterval).Should(Equal(expectedLabels))

			expectedAnnotations := map[string]string{
				"test-annotation":              "test-value",
				"clastix.io/custom-annotation": "buzz",
				"k8s.io/custom-annotation":     "bizz",
			}
			Eventually(func() map[string]string {
				got := &corev1.Namespace{}
				if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, got); err != nil {
					return nil
				}
				ann := got.GetAnnotations()
				if ann == nil {
					ann = map[string]string{}
				}
				return ann
			}, defaultTimeoutInterval, defaultPollInterval).Should(Equal(expectedAnnotations))

			By("verify tenant status", func() {
				Eventually(func(g Gomega) {
					tnt = GetTenantEventually(tnt)

					condition := tnt.Status.Conditions.GetConditionByType(meta.ReadyCondition)
					g.Expect(condition).NotTo(BeNil(), "Condition instance should not be nil")

					g.Expect(condition.Status).To(Equal(metav1.ConditionTrue), "Expected tenant condition status to be True")
					g.Expect(condition.Type).To(Equal(meta.ReadyCondition), "Expected tenant condition type to be Ready")
					g.Expect(condition.Reason).To(Equal(meta.SucceededReason), "Expected tenant condition reason to be Succeeded")
				}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
			})

			By("verify namespace status", func() {
				Eventually(func(g Gomega) {
					currentTenant := GetTenantEventually(tnt)
					currentNamespace := GetNamespaceEventually(ns.GetName())

					instance := currentTenant.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{
						Name: currentNamespace.GetName(),
						UID:  currentNamespace.GetUID(),
					})
					g.Expect(instance).NotTo(BeNil(), "Namespace instance should not be nil")

					condition := instance.Conditions.GetConditionByType(meta.ReadyCondition)
					g.Expect(condition).NotTo(BeNil(), "Condition instance should not be nil")

					g.Expect(instance.Name).To(Equal(currentNamespace.GetName()))
					g.Expect(condition.Status).To(Equal(metav1.ConditionTrue))
					g.Expect(condition.Type).To(Equal(meta.ReadyCondition))
					g.Expect(condition.Reason).To(Equal(meta.SucceededReason))

					expectedMetadata := &capsulev1beta2.TenantStatusNamespaceMetadata{
						Labels: map[string]string{
							"clastix.io/custom-label": "bar",
							"k8s.io/custom-label":     "foo",
						},
						Annotations: map[string]string{
							"clastix.io/custom-annotation": "buzz",
							"k8s.io/custom-annotation":     "bizz",
						},
					}

					g.Expect(instance.Metadata).To(Equal(expectedMetadata))
				}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
			})
		})

		By("change managed additional metadata", func() {
			UpdateTenantEventually(tnt, func(t *capsulev1beta2.Tenant) {
				t.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
					ManagedMetadataOnly: false,
					AdditionalMetadataList: []api.AdditionalMetadataSelectorSpec{
						{
							Labels: map[string]string{
								"clastix.io/custom-label": "bar",
							},
						},
						{
							Annotations: map[string]string{
								"k8s.io/custom-annotation": "bizz",
							},
						},
					},
				}
			})
		})

		By("verify metadata lifecycle (valid update)", func() {
			Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).To(Succeed())

			expectedLabels := map[string]string{
				"test-label":                  "test-value",
				"clastix.io/custom-label":     "bar",
				"matching_namespace_label":    "matching_namespace_label_value",
				"capsule.clastix.io/tenant":   tnt.GetName(),
				"kubernetes.io/metadata.name": ns.GetName(),
				"env":                         "e2e",
			}
			Eventually(func() map[string]string {
				got := &corev1.Namespace{}
				if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, got); err != nil {
					return nil
				}
				ann := got.GetLabels()
				if ann == nil {
					ann = map[string]string{}
				}
				return ann
			}, defaultTimeoutInterval, defaultPollInterval).Should(Equal(expectedLabels))

			expectedAnnotations := map[string]string{
				"test-annotation":          "test-value",
				"k8s.io/custom-annotation": "bizz",
			}
			Eventually(func() map[string]string {
				got := &corev1.Namespace{}
				if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, got); err != nil {
					return nil
				}
				ann := got.GetAnnotations()
				if ann == nil {
					ann = map[string]string{}
				}
				return ann
			}, defaultTimeoutInterval, defaultPollInterval).Should(Equal(expectedAnnotations))

			By("verify tenant status", func() {
				Eventually(func(g Gomega) {
					current := &capsulev1beta2.Tenant{}
					err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, current)
					g.Expect(err).NotTo(HaveOccurred())

					condition := current.Status.Conditions.GetConditionByType(meta.ReadyCondition)
					g.Expect(condition).NotTo(BeNil(), "Condition instance should not be nil")

					g.Expect(condition.Status).To(Equal(metav1.ConditionTrue), "Expected tenant condition status to be True")
					g.Expect(condition.Type).To(Equal(meta.ReadyCondition), "Expected tenant condition type to be Ready")
					g.Expect(condition.Reason).To(Equal(meta.SucceededReason), "Expected tenant condition reason to be Succeeded")
				}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
			})

			By("verify namespace status", func() {
				Eventually(func(g Gomega) {
					current := &capsulev1beta2.Tenant{}
					err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, current)
					g.Expect(err).NotTo(HaveOccurred())

					currentNS := &corev1.Namespace{}
					err = k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, currentNS)
					g.Expect(err).NotTo(HaveOccurred())

					instance := current.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{
						Name: currentNS.GetName(),
						UID:  currentNS.GetUID(),
					})
					g.Expect(instance).NotTo(BeNil(), "Namespace instance should not be nil")

					condition := instance.Conditions.GetConditionByType(meta.ReadyCondition)
					g.Expect(condition).NotTo(BeNil(), "Condition instance should not be nil")

					g.Expect(instance.Name).To(Equal(currentNS.GetName()))
					g.Expect(condition.Status).To(Equal(metav1.ConditionTrue), "Expected namespace condition status to be True")
					g.Expect(condition.Type).To(Equal(meta.ReadyCondition), "Expected namespace condition type to be Ready")
					g.Expect(condition.Reason).To(Equal(meta.SucceededReason), "Expected namespace condition reason to be Succeeded")

					expectedMetadata := &capsulev1beta2.TenantStatusNamespaceMetadata{
						Labels: map[string]string{
							"clastix.io/custom-label": "bar",
						},
						Annotations: map[string]string{
							"k8s.io/custom-annotation": "bizz",
						},
					}

					g.Expect(instance.Metadata).To(Equal(expectedMetadata))
				}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
			})

		})

		By("change managed additional metadata (provoke an error)", func() {
			Eventually(func() error {
				t := &capsulev1beta2.Tenant{}
				Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, t)).To(Succeed())

				t.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
					ManagedMetadataOnly: false,
					AdditionalMetadataList: []api.AdditionalMetadataSelectorSpec{
						{
							Labels: map[string]string{
								"clastix.io???custom-label": "bar",
							},
						},
					},
				}

				return k8sClient.Update(context.TODO(), t)
			}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())

			TenantReadyTrue(tnt)
		})

		By("verify metadata lifecycle (faulty update)", func() {
			Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).To(Succeed())

			expectedLabels := map[string]string{
				"test-label":                  "test-value",
				"clastix.io/custom-label":     "bar",
				"matching_namespace_label":    "matching_namespace_label_value",
				"capsule.clastix.io/tenant":   tnt.GetName(),
				"kubernetes.io/metadata.name": ns.GetName(),
				"env":                         "e2e",
			}
			Eventually(func() map[string]string {
				got := &corev1.Namespace{}
				if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, got); err != nil {
					return nil
				}
				ann := got.GetLabels()
				if ann == nil {
					ann = map[string]string{}
				}
				return ann
			}, defaultTimeoutInterval, defaultPollInterval).Should(Equal(expectedLabels))

			expectedAnnotations := map[string]string{
				"test-annotation":          "test-value",
				"k8s.io/custom-annotation": "bizz",
			}
			Eventually(func() map[string]string {
				got := &corev1.Namespace{}
				if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, got); err != nil {
					return nil
				}
				ann := got.GetAnnotations()
				if ann == nil {
					ann = map[string]string{}
				}
				return ann
			}, defaultTimeoutInterval, defaultPollInterval).Should(Equal(expectedAnnotations))

			By("verify tenant status", func() {
				Eventually(func(g Gomega) {
					t := &capsulev1beta2.Tenant{}

					g.Expect(k8sClient.Get(
						context.TODO(),
						types.NamespacedName{Name: tnt.GetName()},
						t,
					)).To(Succeed())

					condition := t.Status.Conditions.GetConditionByType(meta.ReadyCondition)
					g.Expect(condition).NotTo(BeNil(), "Condition instance should not be nil")

					g.Expect(condition.Type).To(Equal(meta.ReadyCondition), "Expected tenant condition type to be Ready")
					g.Expect(condition.Status).To(Equal(metav1.ConditionTrue), "Expected tenant condition status to be False")
					g.Expect(condition.Reason).To(Equal(meta.SucceededReason), "Expected tenant condition reason to be Failed")
				}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
			})

			By("verify namespace status", func() {
				Eventually(func(g Gomega) {
					t := &capsulev1beta2.Tenant{}
					g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, t)).To(Succeed())

					instance := t.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{Name: ns.GetName(), UID: ns.GetUID()})
					g.Expect(instance).NotTo(BeNil(), "Namespace instance should not be nil")

					condition := instance.Conditions.GetConditionByType(meta.ReadyCondition)
					g.Expect(condition).NotTo(BeNil(), "Condition instance should not be nil")

					g.Expect(instance.Name).To(Equal(ns.GetName()))
					g.Expect(condition.Status).To(Equal(metav1.ConditionTrue), "Expected namespace condition status to be True")
					g.Expect(condition.Type).To(Equal(meta.ReadyCondition), "Expected namespace condition type to be Ready")
					g.Expect(condition.Reason).To(Equal(meta.SucceededReason), "Expected namespace condition reason to be Succeeded")

					expectedMetadata := &capsulev1beta2.TenantStatusNamespaceMetadata{
						Labels: map[string]string{
							"clastix.io/custom-label": "bar",
						},
						Annotations: map[string]string{
							"k8s.io/custom-annotation": "bizz",
						},
					}

					g.Expect(instance.Metadata).To(Equal(expectedMetadata))
				}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
			})
		})

		By("change managed additional metadata (empty update)", func() {
			Eventually(func() error {
				t := &capsulev1beta2.Tenant{}
				Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, t)).To(Succeed())

				t.Spec.NamespaceOptions = &capsulev1beta2.NamespaceOptions{
					ManagedMetadataOnly:    false,
					AdditionalMetadataList: []api.AdditionalMetadataSelectorSpec{},
				}

				return k8sClient.Update(context.TODO(), t)
			}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
		})

		By("verify metadata lifecycle (empty update)", func() {
			Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).To(Succeed())

			expectedLabels := map[string]string{
				"test-label":                  "test-value",
				"matching_namespace_label":    "matching_namespace_label_value",
				"capsule.clastix.io/tenant":   tnt.GetName(),
				"kubernetes.io/metadata.name": ns.GetName(),
				"env":                         "e2e",
			}
			Eventually(func() map[string]string {
				got := &corev1.Namespace{}
				if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, got); err != nil {
					return nil
				}
				ann := got.GetLabels()
				if ann == nil {
					ann = map[string]string{}
				}
				return ann
			}, defaultTimeoutInterval, defaultPollInterval).Should(Equal(expectedLabels))

			expectedAnnotations := map[string]string{
				"test-annotation": "test-value",
			}
			Eventually(func() map[string]string {
				got := &corev1.Namespace{}
				if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, got); err != nil {
					return nil
				}
				ann := got.GetAnnotations()
				if ann == nil {
					ann = map[string]string{}
				}
				return ann
			}, defaultTimeoutInterval, defaultPollInterval).Should(Equal(expectedAnnotations))

			By("verify tenant status", func() {
				Eventually(func(g Gomega) {
					t := &capsulev1beta2.Tenant{}

					g.Expect(k8sClient.Get(
						context.TODO(),
						types.NamespacedName{Name: tnt.GetName()},
						t,
					)).To(Succeed())

					condition := t.Status.Conditions.GetConditionByType(meta.ReadyCondition)
					g.Expect(condition).NotTo(BeNil(), "Condition instance should not be nil")

					g.Expect(condition.Type).To(Equal(meta.ReadyCondition), "Expected tenant condition type to be Ready")
					g.Expect(condition.Status).To(Equal(metav1.ConditionTrue), "Expected tenant condition status to be True")
					g.Expect(condition.Reason).To(Equal(meta.SucceededReason), "Expected tenant condition reason to be Succeeded")
				}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
			})

			By("verify namespace status", func() {
				Eventually(func(g Gomega) {
					t := &capsulev1beta2.Tenant{}
					g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, t)).To(Succeed())

					instance := t.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{Name: ns.GetName(), UID: ns.GetUID()})
					g.Expect(instance).NotTo(BeNil(), "Namespace instance should not be nil")

					condition := instance.Conditions.GetConditionByType(meta.ReadyCondition)
					g.Expect(condition).NotTo(BeNil(), "Condition instance should not be nil")

					g.Expect(instance.Name).To(Equal(ns.GetName()))
					g.Expect(condition.Status).To(Equal(metav1.ConditionTrue), "Expected namespace condition status to be True")
					g.Expect(condition.Type).To(Equal(meta.ReadyCondition), "Expected namespace condition type to be Ready")
					g.Expect(condition.Reason).To(Equal(meta.SucceededReason), "Expected namespace condition reason to be Succeeded")

					expectedMetadata := &capsulev1beta2.TenantStatusNamespaceMetadata{}
					g.Expect(instance.Metadata).To(Equal(expectedMetadata))
				}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
			})
		})
	})
})
