// 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"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

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

var _ = Describe("ResourcePoolClaim Tests", Ordered, Label("resourcepool", "claim"), func() {
	_ = &capsulev1beta2.Tenant{
		ObjectMeta: metav1.ObjectMeta{
			Name: "e2e-test-claims-1",
			Labels: map[string]string{
				"env": "e2e",
			},
		},
		Spec: capsulev1beta2.TenantSpec{
			Owners: rbac.OwnerListSpec{
				{
					CoreOwnerSpec: rbac.CoreOwnerSpec{
						UserSpec: rbac.UserSpec{
							Name: "wind-user",
							Kind: "User",
						},
					},
				},
			},
		},
	}

	JustAfterEach(func() {
		Eventually(func() error {
			poolList := &capsulev1beta2.TenantList{}
			labelSelector := client.MatchingLabels{"e2e-resourcepoolclaims": "test"}
			if err := k8sClient.List(context.TODO(), poolList, labelSelector); err != nil {
				return err
			}

			for _, pool := range poolList.Items {
				EventuallyDeletion(&pool)
			}

			return nil
		}, "30s", "5s").Should(Succeed())

		Eventually(func() error {
			poolList := &capsulev1beta2.ResourcePoolList{}
			labelSelector := client.MatchingLabels{"e2e-resourcepoolclaims": "test"}
			if err := k8sClient.List(context.TODO(), poolList, labelSelector); err != nil {
				return err
			}

			for _, pool := range poolList.Items {
				EventuallyDeletion(&pool)
			}

			return nil
		}, "30s", "5s").Should(Succeed())

		Eventually(func() error {
			poolList := &corev1.NamespaceList{}
			labelSelector := client.MatchingLabels{"e2e-resourcepoolclaims": "test"}
			if err := k8sClient.List(context.TODO(), poolList, labelSelector); err != nil {
				return err
			}

			for _, pool := range poolList.Items {
				EventuallyDeletion(&pool)
			}

			return nil
		}, "30s", "5s").Should(Succeed())

	})

	It("Claim to Pool Assignment", func() {
		pool1 := &capsulev1beta2.ResourcePool{
			ObjectMeta: metav1.ObjectMeta{
				Name: "test-binding-claims",
				Labels: map[string]string{
					"e2e-resourcepoolclaims": "test",
				},
			},
			Spec: capsulev1beta2.ResourcePoolSpec{
				Selectors: []selectors.NamespaceSelector{
					{
						LabelSelector: &metav1.LabelSelector{
							MatchLabels: map[string]string{
								"e2e.capsule.dev/test-suite": "claims-bindings",
							},
						},
					},
					{
						LabelSelector: &metav1.LabelSelector{
							MatchLabels: map[string]string{
								"e2e.capsule.dev/test-suite": "claims-bindings-2",
							},
						},
					},
				},
				Quota: corev1.ResourceQuotaSpec{
					Hard: corev1.ResourceList{
						corev1.ResourceLimitsCPU:      resource.MustParse("2"),
						corev1.ResourceLimitsMemory:   resource.MustParse("2Gi"),
						corev1.ResourceRequestsCPU:    resource.MustParse("2"),
						corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
					},
				},
			},
		}

		claim1 := &capsulev1beta2.ResourcePoolClaim{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "assign-pool-claim-1",
				Namespace: "ns-1-pool-assign",
			},
			Spec: capsulev1beta2.ResourcePoolClaimSpec{
				Pool: "test-binding-claims",
				ResourceClaims: corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("0"),
					corev1.ResourceLimitsMemory:   resource.MustParse("0"),
					corev1.ResourceRequestsCPU:    resource.MustParse("0"),
					corev1.ResourceRequestsMemory: resource.MustParse("0"),
				},
			},
		}

		claim2 := &capsulev1beta2.ResourcePoolClaim{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "assign-pool-claim-2",
				Namespace: "ns-2-pool-assign",
			},
			Spec: capsulev1beta2.ResourcePoolClaimSpec{
				Pool: "test-binding-claims",
				ResourceClaims: corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("0"),
					corev1.ResourceLimitsMemory:   resource.MustParse("0"),
					corev1.ResourceRequestsCPU:    resource.MustParse("0"),
					corev1.ResourceRequestsMemory: resource.MustParse("0"),
				},
			},
		}

		By("Create the ResourcePool", func() {
			EventuallyCreation(func() error {
				pool1.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), pool1)
			}).Should(Succeed(), "Failed to create ResourcePool %s", pool1)
		})

		By("Get Applied revision", func() {
			err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: pool1.Name}, pool1)
			Expect(err).Should(Succeed())
		})

		By("Create Namespaces, which are selected by the pool", func() {
			ns1 := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: "ns-1-pool-assign",
					Labels: map[string]string{
						"e2e-resourcepoolclaims":     "test",
						"e2e.capsule.dev/test-suite": "claims-bindings",
					},
				},
			}

			err := k8sClient.Create(context.TODO(), ns1)
			Expect(err).Should(Succeed())

			ns2 := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: "ns-2-pool-assign",
					Labels: map[string]string{
						"e2e-resourcepoolclaims":     "test",
						"e2e.capsule.dev/test-suite": "claims-bindings-2",
					},
				},
			}

			err = k8sClient.Create(context.TODO(), ns2)
			Expect(err).Should(Succeed())

			ns3 := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: "ns-3-pool-assign",
					Labels: map[string]string{
						"e2e-resourcepoolclaims":     "test",
						"e2e.capsule.dev/test-suite": "something-else",
					},
				},
			}

			err = k8sClient.Create(context.TODO(), ns3)
			Expect(err).Should(Succeed())
		})

		By("Verify Namespaces are shown as allowed targets", func() {
			expectedNamespaces := []string{"ns-1-pool-assign", "ns-2-pool-assign"}

			Eventually(func(g Gomega) {
				stat := &capsulev1beta2.ResourcePool{}
				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: pool1.Name}, stat)
				g.Expect(err).Should(Succeed())

				g.Expect(stat.Status.Namespaces).To(Equal(expectedNamespaces))
				g.Expect(stat.Status.NamespaceSize).To(Equal(uint(2)))
			}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
		})

		By("Create a first claim and verify binding", func() {
			err := k8sClient.Create(context.TODO(), claim1)
			Expect(err).Should(Succeed(), "Failed to create Claim %s", claim1)

			isSuccessfullyBoundAndUnsedToPool(pool1, claim1)
		})

		By("Create a second claim and verify binding", func() {
			err := k8sClient.Create(context.TODO(), claim2)
			Expect(err).Should(Succeed(), "Failed to create Claim %s", claim2)

			isSuccessfullyBoundAndUnsedToPool(pool1, claim2)
		})

		By("Create a third claim and verify error", func() {
			claim := &capsulev1beta2.ResourcePoolClaim{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "assign-pool-claim-3",
					Namespace: "ns-3-pool-assign",
				},
				Spec: capsulev1beta2.ResourcePoolClaimSpec{
					Pool: "test-binding-claims",
					ResourceClaims: corev1.ResourceList{
						corev1.ResourceLimitsCPU:      resource.MustParse("0"),
						corev1.ResourceLimitsMemory:   resource.MustParse("0"),
						corev1.ResourceRequestsCPU:    resource.MustParse("0"),
						corev1.ResourceRequestsMemory: resource.MustParse("0"),
					},
				},
			}

			err := k8sClient.Create(context.TODO(), claim)
			Expect(err).Should(Succeed(), "Failed to create Claim %s", claim)

			Eventually(func(g Gomega) {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				g.Expect(err).Should(Succeed())

				expectedPool := meta.LocalRFC1123ObjectReferenceWithUID{}
				g.Expect(stat.Status.Pool).To(Equal(expectedPool), "expected pool name to be empty")

				g.Expect(len(stat.Status.Conditions)).To(Equal(1), "expected single condition")
				g.Expect(len(stat.OwnerReferences)).To(Equal(0), "expected no ownerreferences")
				assigned := stat.Status.Conditions.GetConditionByType(meta.ReadyCondition)
				g.Expect(assigned.Status).To(Equal(metav1.ConditionFalse), "failed to verify condition status")
				g.Expect(assigned.Type).To(Equal(meta.ReadyCondition), "failed to verify condition type")
				g.Expect(assigned.Reason).To(Equal(meta.FailedReason), "failed to verify condition reason")
			}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
		})
	})

	It("Admission (Validation) - Patch Guard", Label("skip-on-openshift"), func() {
		pool := &capsulev1beta2.ResourcePool{
			ObjectMeta: metav1.ObjectMeta{
				Name: "test-admission-claims",
				Labels: map[string]string{
					"e2e-resourcepoolclaims": "test",
				},
			},
			Spec: capsulev1beta2.ResourcePoolSpec{
				Config: capsulev1beta2.ResourcePoolSpecConfiguration{
					DeleteBoundResources: ptr.To(false),
				},
				Selectors: []selectors.NamespaceSelector{
					{
						LabelSelector: &metav1.LabelSelector{
							MatchLabels: map[string]string{
								"e2e.capsule.dev/test-suite": "admission-guards",
							},
						},
					},
				},
				Quota: corev1.ResourceQuotaSpec{
					Hard: corev1.ResourceList{
						corev1.ResourceLimitsCPU:      resource.MustParse("2"),
						corev1.ResourceLimitsMemory:   resource.MustParse("2Gi"),
						corev1.ResourceRequestsCPU:    resource.MustParse("2"),
						corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
					},
				},
			},
		}

		claim := &capsulev1beta2.ResourcePoolClaim{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "admission-pool-claim-1",
				Namespace: "ns-1-pool-admission",
			},
			Spec: capsulev1beta2.ResourcePoolClaimSpec{
				Pool: pool.GetName(),
				ResourceClaims: corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("1"),
					corev1.ResourceLimitsMemory:   resource.MustParse("1Gi"),
					corev1.ResourceRequestsCPU:    resource.MustParse("1"),
					corev1.ResourceRequestsMemory: resource.MustParse("1Gi"),
				},
			},
		}

		By("Create the Claim", func() {
			ns := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: claim.Namespace,
					Labels: map[string]string{
						"e2e-resourcepoolclaims":     "test",
						"e2e.capsule.dev/test-suite": "admission-guards",
					},
				},
			}

			EventuallyCreation(func() error {
				ns.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), ns)
			}).Should(Succeed(), "Failed to create %s", ns)

			EventuallyCreation(func() error {
				claim.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), claim)
			}).Should(Succeed(), "Failed to create %s", claim)

		})

		By("Create the ResourcePool", func() {
			EventuallyCreation(func() error {
				pool.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), pool)
			}).Should(Succeed(), "Failed to create %s", pool)
		})

		By("Get Applied revision", func() {
			err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: pool.Name}, pool)
			Expect(err).Should(Succeed())
		})

		By("Bind a claim", func() {
			Eventually(func(g Gomega) {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				g.Expect(err).Should(Succeed())

				expectedPool := meta.LocalRFC1123ObjectReferenceWithUID{
					Name: meta.RFC1123Name(pool.Name),
					UID:  pool.GetUID(),
				}

				g.Expect(stat.Status.Pool).To(Equal(expectedPool), "expected pool name to match")
			}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())

			isBoundAndUnusedCondition(claim)
		})

		By("Create a pod with resource requests/limits", func() {
			pod := &corev1.Pod{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "claim-pod",
					Namespace: claim.Namespace,
					Labels: map[string]string{
						"e2e": "claim-pod",
					},
				},
				Spec: corev1.PodSpec{
					SecurityContext: nobodyPodSecurityContext(),
					// optional: helps schedule quickly, avoid restarts
					RestartPolicy: corev1.RestartPolicyNever,
					Containers: []corev1.Container{
						{
							Name:            "pause",
							Image:           "registry.k8s.io/pause:3.9",
							SecurityContext: restrictedContainerSecurityContext(),
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:    resource.MustParse("10m"),
									corev1.ResourceMemory: resource.MustParse("16Mi"),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceCPU:    resource.MustParse("20m"),
									corev1.ResourceMemory: resource.MustParse("32Mi"),
								},
							},
						},
					},
				},
			}

			Expect(k8sClient.Create(context.TODO(), pod)).To(Succeed())
		})

		By("Verify the claim is used and cant be deleted", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				Expect(err).Should(Succeed())

				isBoundAndUsedCondition(stat)

				return k8sClient.Delete(context.TODO(), stat)
			}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())
		})

		By("Error on patching resources for claim (Increase)", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				Expect(err).Should(Succeed())

				stat.Spec.ResourceClaims = corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("2"),
					corev1.ResourceLimitsMemory:   resource.MustParse("2Gi"),
					corev1.ResourceRequestsCPU:    resource.MustParse("2"),
					corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
				}

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

		By("Error on patching resources for claim (Decrease)", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				Expect(err).Should(Succeed())

				stat.Spec.ResourceClaims = corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("0"),
					corev1.ResourceLimitsMemory:   resource.MustParse("0Gi"),
					corev1.ResourceRequestsCPU:    resource.MustParse("0"),
					corev1.ResourceRequestsMemory: resource.MustParse("0Gi"),
				}

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

		By("Error on patching pool name", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				Expect(err).Should(Succeed())

				stat.Spec.Pool = "some-random-pool"

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

		By("Make the claim unused", func() {
			key := client.ObjectKey{Name: "claim-pod", Namespace: claim.Namespace}

			pod := &corev1.Pod{}
			err := k8sClient.Get(context.TODO(), key, pod)
			Expect(err).To(Succeed(), "pod must exist before deleting")

			Expect(k8sClient.Delete(context.TODO(), pod, &client.DeleteOptions{
				GracePeriodSeconds: ptr.To(int64(0)),
			})).To(Succeed())

			Eventually(func() bool {
				p := &corev1.Pod{}
				err := k8sClient.Get(
					context.TODO(),
					key,
					p,
				)
				return apierrors.IsNotFound(err)
			}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())

		})

		By("Bind a claim", func() {
			Eventually(func(g Gomega) {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				g.Expect(err).Should(Succeed())

				expectedPool := meta.LocalRFC1123ObjectReferenceWithUID{
					Name: meta.RFC1123Name(pool.Name),
					UID:  pool.GetUID(),
				}

				isBoundAndUnusedCondition(stat)
				g.Expect(stat.Status.Pool).To(Equal(expectedPool), "expected pool name to match")
			}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
		})

		By("Allow on patching resources for claim (Increase)", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				Expect(err).Should(Succeed())

				stat.Spec.ResourceClaims = corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("2"),
					corev1.ResourceLimitsMemory:   resource.MustParse("2Gi"),
					corev1.ResourceRequestsCPU:    resource.MustParse("2"),
					corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
				}

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

		By("Allow on patching resources for claim (Decrease)", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				if err := k8sClient.Get(
					context.TODO(),
					client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace},
					stat,
				); err != nil {
					return err
				}

				stat.Spec.ResourceClaims = corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("0"),
					corev1.ResourceLimitsMemory:   resource.MustParse("0Gi"),
					corev1.ResourceRequestsCPU:    resource.MustParse("0"),
					corev1.ResourceRequestsMemory: resource.MustParse("0Gi"),
				}

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

		By("Allow on patching pool name", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}

				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				Expect(err).Should(Succeed())

				stat.Spec.Pool = "some-random-pool"

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

		By("Delete Pool", func() {
			EventuallyDeletion(pool)
		})

		By("Verify claim is no longer bound", func() {
			isUnassignedCondition(claim)
		})

		By("Allow patching resources for claim (Increase)", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}

				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				Expect(err).Should(Succeed())

				stat.Spec.ResourceClaims = corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("2"),
					corev1.ResourceLimitsMemory:   resource.MustParse("2Gi"),
					corev1.ResourceRequestsCPU:    resource.MustParse("2"),
					corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
				}

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

		By("Allow patching resources for claim (Decrease)", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}

				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				Expect(err).Should(Succeed())

				stat.Spec.ResourceClaims = corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("0"),
					corev1.ResourceLimitsMemory:   resource.MustParse("0Gi"),
					corev1.ResourceRequestsCPU:    resource.MustParse("0"),
					corev1.ResourceRequestsMemory: resource.MustParse("0Gi"),
				}

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

		By("Allow patching pool name", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}

				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				Expect(err).Should(Succeed())

				stat.Spec.Pool = "some-random-pool"

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

	It("Admission (Mutation) - Release Bound Claim", Label("skip-on-openshift"), func() {
		pool := &capsulev1beta2.ResourcePool{
			ObjectMeta: metav1.ObjectMeta{
				Name: "test-release-bound-claim",
				Labels: map[string]string{
					"e2e-resourcepoolclaims": "test",
				},
			},
			Spec: capsulev1beta2.ResourcePoolSpec{
				Config: capsulev1beta2.ResourcePoolSpecConfiguration{
					DeleteBoundResources: ptr.To(false),
				},
				Selectors: []selectors.NamespaceSelector{
					{
						LabelSelector: &metav1.LabelSelector{
							MatchLabels: map[string]string{
								"e2e.capsule.dev/test-suite": "release-bound-claim",
							},
						},
					},
				},
				Quota: corev1.ResourceQuotaSpec{
					Hard: corev1.ResourceList{
						corev1.ResourceLimitsCPU:      resource.MustParse("2"),
						corev1.ResourceLimitsMemory:   resource.MustParse("2Gi"),
						corev1.ResourceRequestsCPU:    resource.MustParse("2"),
						corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
					},
				},
			},
		}

		claim := &capsulev1beta2.ResourcePoolClaim{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "release-bound-claim",
				Namespace: "ns-release-bound-claim",
			},
			Spec: capsulev1beta2.ResourcePoolClaimSpec{
				Pool: pool.GetName(),
				ResourceClaims: corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("1"),
					corev1.ResourceLimitsMemory:   resource.MustParse("1Gi"),
					corev1.ResourceRequestsCPU:    resource.MustParse("1"),
					corev1.ResourceRequestsMemory: resource.MustParse("1Gi"),
				},
			},
		}

		By("Create the ResourcePool and namespace", func() {
			EventuallyCreation(func() error {
				pool.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), pool)
			}).Should(Succeed(), "Failed to create %s", pool)

			ns := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: claim.Namespace,
					Labels: map[string]string{
						"e2e-resourcepoolclaims":     "test",
						"e2e.capsule.dev/test-suite": "release-bound-claim",
					},
				},
			}

			EventuallyCreation(func() error {
				ns.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), ns)
			}).Should(Succeed(), "Failed to create %s", ns)
		})

		By("Bind the claim to the pool", func() {
			EventuallyCreation(func() error {
				claim.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), claim)
			}).Should(Succeed(), "Failed to create %s", claim)

			isSuccessfullyBoundAndUnsedToPool(pool, claim)
		})

		By("Release the bound claim with the release annotation", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				if err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat); err != nil {
					return err
				}

				if stat.Annotations == nil {
					stat.Annotations = map[string]string{}
				}

				stat.Annotations[meta.ReleaseAnnotation] = meta.ReleaseAnnotationTrigger

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

		By("Verify the claim is disassociated and the release annotation was consumed", func() {
			Eventually(func(g Gomega) {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				g.Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)).To(Succeed())

				bound := stat.Status.Conditions.GetConditionByType(meta.BoundCondition)

				g.Expect(bound).ToNot(BeNil(), "Bound condition should not be nil")
				g.Expect(bound.Status).To(Equal(metav1.ConditionFalse), "Bound condition status should be False")

				g.Expect(stat.Annotations).NotTo(HaveKey(meta.ReleaseAnnotation))

			}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
		})

		By("Allow updating the released claim resources while the pool still exists", func() {
			Eventually(func() error {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				if err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat); err != nil {
					return err
				}

				stat.Spec.ResourceClaims = corev1.ResourceList{
					corev1.ResourceLimitsCPU:      resource.MustParse("2"),
					corev1.ResourceLimitsMemory:   resource.MustParse("2Gi"),
					corev1.ResourceRequestsCPU:    resource.MustParse("2"),
					corev1.ResourceRequestsMemory: resource.MustParse("2Gi"),
				}

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

	It("Admission (Mutation) - Auto Pool Assign", Label("skip-on-openshift"), func() {
		pool1 := &capsulev1beta2.ResourcePool{
			ObjectMeta: metav1.ObjectMeta{
				Name: "test-auto-assign-1",
				Labels: map[string]string{
					"e2e-resourcepoolclaims": "test",
				},
			},
			Spec: capsulev1beta2.ResourcePoolSpec{
				Config: capsulev1beta2.ResourcePoolSpecConfiguration{
					DeleteBoundResources: ptr.To(false),
				},
				Selectors: []selectors.NamespaceSelector{
					{
						LabelSelector: &metav1.LabelSelector{
							MatchLabels: map[string]string{
								"e2e.capsule.dev/test-suite": "admission-auto-assign",
							},
						},
					},
				},
				Quota: corev1.ResourceQuotaSpec{
					Hard: corev1.ResourceList{
						corev1.ResourceLimitsCPU:   resource.MustParse("2"),
						corev1.ResourceRequestsCPU: resource.MustParse("2"),
					},
				},
			},
		}

		pool2 := &capsulev1beta2.ResourcePool{
			ObjectMeta: metav1.ObjectMeta{
				Name: "test-auto-assign-2",
				Labels: map[string]string{
					"e2e-resourcepoolclaims": "test",
				},
			},
			Spec: capsulev1beta2.ResourcePoolSpec{
				Config: capsulev1beta2.ResourcePoolSpecConfiguration{
					DeleteBoundResources: ptr.To(false),
				},
				Selectors: []selectors.NamespaceSelector{
					{
						LabelSelector: &metav1.LabelSelector{
							MatchLabels: map[string]string{
								"e2e.capsule.dev/test-suite": "admission-auto-assign",
							},
						},
					},
				},
				Quota: corev1.ResourceQuotaSpec{
					Hard: corev1.ResourceList{
						corev1.ResourceLimitsMemory:   resource.MustParse("2"),
						corev1.ResourceRequestsMemory: resource.MustParse("2"),
					},
				},
			},
		}

		By("Create the ResourcePools", func() {
			EventuallyCreation(func() error {
				pool1.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), pool1)
			}).Should(Succeed(), "Failed to create %s", pool1)

			EventuallyCreation(func() error {
				pool2.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), pool2)
			}).Should(Succeed(), "Failed to create %s", pool2)
		})

		By("Auto Assign Claim (CPU)", func() {
			claim := &capsulev1beta2.ResourcePoolClaim{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "auto-assign-1",
					Namespace: "ns-1-pool-assign",
				},
				Spec: capsulev1beta2.ResourcePoolClaimSpec{
					ResourceClaims: corev1.ResourceList{
						corev1.ResourceLimitsCPU:   resource.MustParse("1"),
						corev1.ResourceRequestsCPU: resource.MustParse("1"),
					},
				},
			}

			ns := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: claim.Namespace,
					Labels: map[string]string{
						"e2e-resourcepoolclaims":     "test",
						"e2e.capsule.dev/test-suite": "admission-auto-assign",
					},
				},
			}

			EventuallyCreation(func() error {
				return k8sClient.Create(context.TODO(), ns)
			}).Should(Succeed())

			ExpectNamespaceInResourcePoolEventually(pool1.Name, ns.Name)

			EventuallyCreation(func() error {
				claim.ResourceVersion = ""
				return k8sClient.Create(context.TODO(), claim)
			}).Should(Succeed(), "Failed to create Claim %s/%s", claim.Namespace, claim.Name)

			Eventually(func(g Gomega) {
				stat := &capsulev1beta2.ResourcePoolClaim{}

				g.Expect(k8sClient.Get(
					context.TODO(),
					client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace},
					stat,
				)).To(Succeed())

				g.Expect(stat.Spec.Pool).To(Equal(pool1.Name), "expected pool name to match")
			}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
		})

		By("Auto Assign Claim (Memory)", func() {
			claim := &capsulev1beta2.ResourcePoolClaim{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "auto-assign-1",
					Namespace: "ns-2-pool-assign",
				},
				Spec: capsulev1beta2.ResourcePoolClaimSpec{
					ResourceClaims: corev1.ResourceList{
						corev1.ResourceLimitsMemory:   resource.MustParse("1"),
						corev1.ResourceRequestsMemory: resource.MustParse("1"),
					},
				},
			}

			ns := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: claim.Namespace,
					Labels: map[string]string{
						"e2e-resourcepoolclaims":     "test",
						"e2e.capsule.dev/test-suite": "admission-auto-assign",
					},
				},
			}

			err := k8sClient.Create(context.TODO(), ns)
			Expect(err).Should(Succeed())

			ExpectNamespaceInResourcePoolEventually(pool2.Name, ns.Name)

			err = k8sClient.Create(context.TODO(), claim)
			Expect(err).Should(Succeed(), "Failed to create Claim %s", claim)

			Eventually(func(g Gomega) {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				err = k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				g.Expect(err).Should(Succeed())

				g.Expect(stat.Spec.Pool).To(Equal(pool2.Name), "expected pool name to match")
			}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
		})

		By("No Default available (Storage)", func() {
			claim := &capsulev1beta2.ResourcePoolClaim{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "auto-assign-3",
					Namespace: "ns-3-pool-assign",
				},
				Spec: capsulev1beta2.ResourcePoolClaimSpec{
					ResourceClaims: corev1.ResourceList{
						corev1.ResourceRequestsStorage: resource.MustParse("1"),
					},
				},
			}

			ns := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: claim.Namespace,
					Labels: map[string]string{
						"e2e-resourcepoolclaims":     "test",
						"e2e.capsule.dev/test-suite": "admission-auto-assign",
					},
				},
			}

			EventuallyCreation(func() error {
				ns.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), ns)
			}).Should(Succeed(), "Failed to create %s", ns)

			ExpectNamespaceInResourcePoolEventually(pool1.Name, ns.Name)

			EventuallyCreation(func() error {
				claim.ResourceVersion = ""

				return k8sClient.Create(context.TODO(), claim)
			}).Should(Succeed(), "Failed to create %s", claim)

			Eventually(func(g Gomega) {
				stat := &capsulev1beta2.ResourcePoolClaim{}
				err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, stat)
				g.Expect(err).Should(Succeed())

				g.Expect(stat.Spec.Pool).To(Equal(""), "expected pool name to match")
			}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
		})
	})
})

func isUnassignedCondition(claim *capsulev1beta2.ResourcePoolClaim) {
	Eventually(func(g Gomega) {
		cl := &capsulev1beta2.ResourcePoolClaim{}
		err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, cl)
		g.Expect(err).Should(Succeed())

		assigned := cl.Status.Conditions.GetConditionByType(meta.ReadyCondition)
		g.Expect(assigned).NotTo(BeNil(), "Ready condition should be present")

		g.Expect(assigned.Status).To(Equal(metav1.ConditionFalse), "failed to verify condition status")
		g.Expect(assigned.Type).To(Equal(meta.ReadyCondition), "failed to verify condition type")
		g.Expect(assigned.Reason).To(Equal(meta.FailedReason), "failed to verify condition reason")
	}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
}

func isBoundAndUnusedCondition(claim *capsulev1beta2.ResourcePoolClaim) {
	Eventually(func(g Gomega) {
		cl := &capsulev1beta2.ResourcePoolClaim{}
		err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, cl)
		g.Expect(err).Should(Succeed())

		bound := cl.Status.Conditions.GetConditionByType(meta.BoundCondition)
		g.Expect(bound).NotTo(BeNil(), "Bound condition should be present")

		g.Expect(bound.Type).To(Equal(meta.BoundCondition), "failed to verify condition type")
		g.Expect(bound.Reason).To(Equal(meta.UnusedReason), "failed to verify condition reason")
		g.Expect(bound.Status).To(Equal(metav1.ConditionFalse), "failed to verify condition status")
	}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
}

func isBoundAndUsedCondition(claim *capsulev1beta2.ResourcePoolClaim) {
	Eventually(func(g Gomega) {
		cl := &capsulev1beta2.ResourcePoolClaim{}
		err := k8sClient.Get(context.TODO(), client.ObjectKey{Name: claim.Name, Namespace: claim.Namespace}, cl)
		g.Expect(err).Should(Succeed())

		bound := cl.Status.Conditions.GetConditionByType(meta.BoundCondition)
		g.Expect(bound).NotTo(BeNil(), "Bound condition should be present")

		g.Expect(bound.Status).To(Equal(metav1.ConditionTrue), "failed to verify condition status")
		g.Expect(bound.Type).To(Equal(meta.BoundCondition), "failed to verify condition type")
		g.Expect(bound.Reason).To(Equal(meta.InUseReason), "failed to verify condition reason")
	}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
}
