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

package resourcepools

import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"slices"
	"sort"
	"strings"

	"github.com/go-logr/logr"
	gherrors "github.com/pkg/errors"
	"golang.org/x/sync/errgroup"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/events"
	"k8s.io/client-go/util/retry"
	"sigs.k8s.io/cluster-api/util/patch"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/builder"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/handler"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"

	capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
	ctrlutils "github.com/projectcapsule/capsule/internal/controllers/utils"
	"github.com/projectcapsule/capsule/internal/metrics"
	"github.com/projectcapsule/capsule/pkg/api"
	caperrors "github.com/projectcapsule/capsule/pkg/api/errors"
	"github.com/projectcapsule/capsule/pkg/api/meta"
	evt "github.com/projectcapsule/capsule/pkg/runtime/events"
	"github.com/projectcapsule/capsule/pkg/runtime/predicates"
	"github.com/projectcapsule/capsule/pkg/utils"
)

type resourcePoolController struct {
	client.Client

	reader client.Reader

	metrics  *metrics.ResourcePoolRecorder
	log      logr.Logger
	recorder events.EventRecorder
}

func (r *resourcePoolController) SetupWithManager(mgr ctrl.Manager, ctrlConfig ctrlutils.ControllerOptions) error {
	r.reader = mgr.GetAPIReader()

	return ctrl.NewControllerManagedBy(mgr).
		Named("capsule/resourcepools/pools").
		For(
			&capsulev1beta2.ResourcePool{},
			builder.WithPredicates(predicate.Or(
				predicate.GenerationChangedPredicate{},
				predicates.UpdatedMetadataPredicate{},
				predicates.DeletionChangedPredicate{},
			)),
		).
		Owns(
			&corev1.ResourceQuota{},
			builder.WithPredicates(predicate.Or(
				predicate.GenerationChangedPredicate{},
				predicates.UpdatedMetadataPredicate{},
				predicates.DeletionChangedPredicate{},
				predicates.ResourceQuotaUsageChangedPredicate{},
			)),
		).
		Watches(&capsulev1beta2.ResourcePoolClaim{},
			handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &capsulev1beta2.ResourcePool{}),
		).
		Watches(&corev1.Namespace{},
			handler.EnqueueRequestsFromMapFunc(r.resourcePoolsForNamespace),
			builder.WithPredicates(predicates.UpdatedMetadataPredicate{}),
		).
		WithOptions(ctrlConfig.Runtime.ToControllerOptions()).
		Complete(r)
}

func (r resourcePoolController) Reconcile(ctx context.Context, request ctrl.Request) (result ctrl.Result, err error) {
	log := r.log.WithValues("Request.Name", request.Name)

	instance := &capsulev1beta2.ResourcePool{}
	if err = r.Get(ctx, request.NamespacedName, instance); err != nil {
		if apierrors.IsNotFound(err) {
			log.V(5).Info("Request object not found, could have been deleted after reconcile request")

			r.metrics.DeleteResourcePoolMetric(request.Name)

			return reconcile.Result{}, nil
		}

		log.Error(err, "Error reading the object")

		return result, err
	}

	patchHelper, err := patch.NewHelper(instance, r.Client)
	if err != nil {
		return reconcile.Result{}, gherrors.Wrap(err, "failed to init patch helper")
	}

	defer func() {
		r.finalize(ctx, instance)

		if uerr := r.updateStatus(ctx, instance, err); uerr != nil {
			if caperrors.IgnoreGone(uerr) {
				err = nil

				return
			}

			err = fmt.Errorf("cannot update pool status: %w", uerr)

			return
		}

		r.metrics.ResourceUsageMetrics(instance)

		if e := patchHelper.Patch(ctx, instance); e != nil {
			if caperrors.IgnoreGone(e) {
				err = nil

				return
			}

			err = gherrors.Wrap(e, "failed to patch")

			return
		}

		err = nil
	}()

	err = r.reconcile(ctx, log, instance)

	return ctrl.Result{}, err
}

func (r *resourcePoolController) resourcePoolsForNamespace(ctx context.Context, obj client.Object) []reconcile.Request {
	ns, ok := obj.(*corev1.Namespace)
	if !ok {
		return nil
	}

	poolList := &capsulev1beta2.ResourcePoolList{}
	if err := r.List(ctx, poolList); err != nil {
		r.log.Error(err, "failed to list ResourcePools for namespace event", "namespace", ns.Name)

		return nil
	}

	requests := make([]reconcile.Request, 0)

	for i := range poolList.Items {
		pool := &poolList.Items[i]
		matched := slices.Contains(pool.Status.Namespaces, ns.Name)

		for _, spec := range pool.Spec.Selectors {
			if spec.LabelSelector == nil {
				continue
			}

			selector, err := metav1.LabelSelectorAsSelector(spec.LabelSelector)
			if err == nil && selector.Matches(labels.Set(ns.Labels)) {
				matched = true

				break
			}
		}

		if matched {
			requests = append(requests, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(pool)})
		}
	}

	return requests
}

func (r *resourcePoolController) finalize(
	ctx context.Context,
	pool *capsulev1beta2.ResourcePool,
) {
	managedResources := pool.Status.ClaimSize + pool.Status.NamespaceSize

	// Case: all managed resources are gone, remove finalizer
	if managedResources == 0 && controllerutil.ContainsFinalizer(pool, meta.ControllerFinalizer) {
		controllerutil.RemoveFinalizer(pool, meta.ControllerFinalizer)
	}

	// Case: managed resources still exist, add finalizer if not already present
	if managedResources > 0 && !controllerutil.ContainsFinalizer(pool, meta.ControllerFinalizer) {
		controllerutil.AddFinalizer(pool, meta.ControllerFinalizer)
	}
}

func (r *resourcePoolController) reconcile(
	ctx context.Context,
	log logr.Logger,
	pool *capsulev1beta2.ResourcePool,
) (err error) {
	r.handlePoolHardResources(pool)

	namespaces, err := r.gatherMatchingNamespaces(ctx, log, pool)
	if err != nil {
		return err
	}

	currentNamespaces := make(map[string]struct{}, len(namespaces))
	for _, ns := range namespaces {
		currentNamespaces[ns.Name] = struct{}{}
	}

	claims, err := r.gatherMatchingClaims(ctx, log, pool, currentNamespaces)
	if err != nil {
		return err
	}

	log.V(5).Info("Collected assigned claims", "count", len(claims))

	if err := r.garbageCollection(ctx, log, pool, claims, currentNamespaces); err != nil {
		return err
	}

	pool.AssignNamespaces(namespaces)

	// Sort by creation timestamp (oldest first)
	sort.Slice(claims, func(i, j int) bool {
		return claims[i].CreationTimestamp.Before(&claims[j].CreationTimestamp)
	})

	// Keeps track of resources which are exhausted by previous resource
	// This is only required when Ordered is active
	exhaustions := make(map[string]api.PoolExhaustionResource)

	// Soft-fail step: reconcile each claim, collect errors, continue
	var errs []error

	for i := range claims {
		claim := &claims[i]

		log.V(5).Info("Found claim",
			"name", claim.Name,
			"namespace", claim.Namespace,
			"created", claim.CreationTimestamp,
		)

		if err := r.reconcileResourceClaim(ctx, log.WithValues("Claim", claim.Name), pool, claim, exhaustions); err != nil {
			log.Error(err, "Failed to reconcile ResourceQuotaClaim", "claim", claim.Name, "namespace", claim.Namespace)
			errs = append(errs, fmt.Errorf("claim %s/%s: %w", claim.Namespace, claim.Name, err))
		}
	}

	if err := errors.Join(errs...); err != nil {
		return err
	}

	log.V(7).Info("finalized reconciling claims", "exhaustions", exhaustions)

	r.metrics.CalculateExhaustions(pool, exhaustions)
	pool.Status.Exhaustions = exhaustions

	pool.CalculateClaimedResources()
	pool.AssignClaims()

	if err := r.syncResourceQuotas(ctx, r.Client, r.reader, pool, namespaces); err != nil {
		return fmt.Errorf("sync resourcequotas: %w", err)
	}

	claimsByNS := make(map[string][]capsulev1beta2.ResourcePoolClaim, 16)

	for i := range claims {
		cl := &claims[i]

		exhausted := cl.Status.Conditions.GetConditionByType(meta.ExhaustedCondition)
		if exhausted == nil || exhausted.Status != metav1.ConditionTrue {
			claimsByNS[cl.Namespace] = append(claimsByNS[cl.Namespace], *cl)

			continue
		}

		cond := meta.NewBoundCondition(cl)
		cond.Status = metav1.ConditionFalse
		cond.Reason = meta.FailedReason
		cond.Message = "claim causes exhaustions"

		if err := updateStatusAndEmitEvent(ctx, r.Client, r.recorder, cl, pool, cond); err != nil {
			errs = append(errs, fmt.Errorf("update exhausted claim condition %s/%s: %w", cl.Namespace, cl.Name, err))
		}
	}

	if err := r.reconcileClaimsInUse(ctx, log, pool, claimsByNS); err != nil {
		errs = append(errs, fmt.Errorf("reconcile claims in use: %w", err))
	}

	return errors.Join(errs...)
}

func (r *resourcePoolController) reconcileClaimsInUse(
	ctx context.Context,
	log logr.Logger,
	pool *capsulev1beta2.ResourcePool,
	claimsByNS map[string][]capsulev1beta2.ResourcePoolClaim,
) error {
	group := new(errgroup.Group)

	for ns, nsClaims := range claimsByNS {
		group.Go(func() error {
			if err := r.reconcileClaimsInUseForNamespace(ctx, log, pool, ns, nsClaims); err != nil {
				return fmt.Errorf("namespace %s: %w", ns, err)
			}

			return nil
		})
	}

	return group.Wait()
}

func (r *resourcePoolController) reconcileClaimsInUseForNamespace(
	ctx context.Context,
	log logr.Logger,
	pool *capsulev1beta2.ResourcePool,
	namespace string,
	claims []capsulev1beta2.ResourcePoolClaim,
) error {
	// Fetch the quota we manage for this namespace
	rq := &corev1.ResourceQuota{}
	if err := r.reader.Get(ctx, types.NamespacedName{
		Name: pool.GetQuotaName(), Namespace: namespace,
	}, rq); err != nil {
		if apierrors.IsNotFound(err) {
			return nil
		}

		return err
	}

	used := rq.Status.Used.DeepCopy()
	if used == nil {
		used = corev1.ResourceList{}
	}

	// Consider only resources the pool manages (pool.Spec.Quota.Hard keys).
	used = filterResourceListByKeys(used, pool.Spec.Quota.Hard)

	// Compute selected claims (by UID) needed to cover used.
	selected := selectClaimsCoveringUsageGreedy(used, claims)

	// Update conditions only when needed (avoid write storms).
	for i := range claims {
		cl := &claims[i]

		_, shouldBeInUse := selected[string(cl.UID)]

		cond := meta.NewBoundCondition(cl)
		if !shouldBeInUse {
			cond.Status = metav1.ConditionFalse
			cond.Reason = meta.UnusedReason
			cond.Message = "claim is unused"
		} else {
			cond.Status = metav1.ConditionTrue
			cond.Reason = meta.InUseReason
			cond.Message = "claim is used"
		}

		err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
			current := &capsulev1beta2.ResourcePoolClaim{}
			if err := r.reader.Get(ctx, client.ObjectKeyFromObject(cl), current); err != nil {
				return fmt.Errorf("failed to refetch instance before update: %w", err)
			}

			current.Status.Conditions.UpdateConditionByType(cond)

			return r.Status().Update(ctx, current)
		})
		if err != nil {
			return err
		}
	}

	return nil
}

// Reconciles a single ResourceClaim.
func (r *resourcePoolController) reconcileResourceClaim(
	ctx context.Context,
	log logr.Logger,
	pool *capsulev1beta2.ResourcePool,
	claim *capsulev1beta2.ResourcePoolClaim,
	exhaustion map[string]api.PoolExhaustionResource,
) (err error) {
	t := pool.GetClaimFromStatus(claim)
	if t != nil {
		if reflect.DeepEqual(t.Claims, claim.Spec.ResourceClaims) {
			return r.handleClaimToPoolBinding(ctx, pool, claim)
		}

		exhaustions := canClaimWithinPoolExcludingClaim(log, pool, claim, t)
		if len(exhaustions) != 0 {
			log.V(5).Info("resized claim exhausts resources", "amount", len(exhaustions))

			pool.RemoveClaimFromStatus(claim)
			pool.CalculateClaimedResources()

			return r.handleClaimResourceExhaustion(
				ctx,
				pool,
				claim,
				exhaustions,
				exhaustion,
			)
		}

		return r.handleClaimToPoolBinding(ctx, pool, claim)
	}

	// Verify if a resource was already exhausted by a previous claim
	if *pool.Spec.Config.OrderedQueue {
		var queued bool

		queued, err = r.handleClaimOrderedExhaustion(
			ctx,
			pool,
			claim,
			exhaustion,
		)
		if err != nil {
			return err
		}

		if queued {
			log.V(5).Info("Claim is queued", "claim", claim.Name)

			return nil
		}
	}

	// Check if Resources can be Assigned (Enough Resources to claim)
	exhaustions := r.canClaimWithinNamespace(log, pool, claim)
	if len(exhaustions) != 0 {
		log.V(5).Info("exhausting resources", "amount", len(exhaustions))

		return r.handleClaimResourceExhaustion(
			ctx,
			pool,
			claim,
			exhaustions,
			exhaustion,
		)
	}

	return r.handleClaimToPoolBinding(ctx, pool, claim)
}

func (r *resourcePoolController) canClaimWithinNamespace(
	log logr.Logger,
	pool *capsulev1beta2.ResourcePool,
	claim *capsulev1beta2.ResourcePoolClaim,
) (res map[string]api.PoolExhaustionResource) {
	return canClaimWithinPoolExcludingClaim(log, pool, claim, nil)
}

func canClaimWithinPoolExcludingClaim(
	log logr.Logger,
	pool *capsulev1beta2.ResourcePool,
	claim *capsulev1beta2.ResourcePoolClaim,
	excluded *capsulev1beta2.ResourcePoolClaimsItem,
) (res map[string]api.PoolExhaustionResource) {
	claimable := pool.GetAvailableClaimableResources()

	if excluded != nil {
		for resourceName, qt := range excluded.Claims {
			available := claimable[resourceName]
			available.Add(qt)
			claimable[resourceName] = available
		}
	}

	log.V(5).Info("claimable resources", "claimable", claimable)

	_, namespaceClaimed := pool.GetNamespaceClaims(claim.Namespace)
	log.V(5).Info("namespace claimed resources", "claimed", namespaceClaimed)

	res = make(map[string]api.PoolExhaustionResource)

	for resourceName, req := range claim.Spec.ResourceClaims {
		// Verify if total Quota is available
		available, exists := claimable[resourceName]
		if !exists || available.IsZero() || available.Cmp(req) < 0 {
			log.V(5).Info("not enough resources available", "available", available, "requesting", req)

			res[resourceName.String()] = api.PoolExhaustionResource{
				Available:  available,
				Requesting: req,
			}

			continue
		}
	}

	return res
}

// Handles exhaustions when a exhaustion was already declared in the given map.
func (r *resourcePoolController) handleClaimOrderedExhaustion(
	ctx context.Context,
	pool *capsulev1beta2.ResourcePool,
	claim *capsulev1beta2.ResourcePoolClaim,
	exhaustions map[string]api.PoolExhaustionResource,
) (queued bool, err error) {
	status := make([]string, 0)

	for resourceName, qt := range claim.Spec.ResourceClaims {
		req, ok := exhaustions[resourceName.String()]
		if !ok {
			continue
		}

		line := fmt.Sprintf(
			"requested: %s=%s, queued: %s=%s",
			resourceName,
			qt.String(),
			resourceName,
			req.Requesting.String(),
		)
		status = append(status, line)
	}

	if len(status) != 0 {
		queued = true

		cond := meta.NewExhaustedCondition(claim)
		cond.Status = metav1.ConditionTrue
		cond.Reason = meta.QueueExhaustedReason
		cond.Message = strings.Join(status, "; ")

		return queued, updateStatusAndEmitEvent(ctx, r.Client, r.recorder, claim, pool, cond)
	}

	return queued, err
}

func (r *resourcePoolController) handleClaimResourceExhaustion(
	ctx context.Context,
	pool *capsulev1beta2.ResourcePool,
	claim *capsulev1beta2.ResourcePoolClaim,
	currentExhaustions map[string]api.PoolExhaustionResource,
	exhaustions map[string]api.PoolExhaustionResource,
) (err error) {
	resourceNames := make([]string, 0, len(currentExhaustions))
	for resourceName := range currentExhaustions {
		resourceNames = append(resourceNames, resourceName)
	}

	sort.Strings(resourceNames)

	status := make([]string, 0, len(resourceNames))

	for _, resourceName := range resourceNames {
		ex := currentExhaustions[resourceName]

		ext, ok := exhaustions[resourceName]
		if ok {
			ext.Requesting.Add(ex.Requesting)
			exhaustions[resourceName] = ext
		} else {
			exhaustions[resourceName] = ex
		}

		line := fmt.Sprintf(
			"requested: %s=%s, available: %s=%s",
			resourceName,
			ex.Requesting.String(),
			resourceName,
			ex.Available.String(),
		)

		status = append(status, line)
	}

	if len(status) != 0 {
		cond := meta.NewExhaustedCondition(claim)
		cond.Status = metav1.ConditionTrue
		cond.Reason = meta.PoolExhaustedReason
		cond.Message = strings.Join(status, "; ")

		return updateStatusAndEmitEvent(ctx, r.Client, r.recorder, claim, pool, cond)
	}

	return err
}

func (r *resourcePoolController) handleClaimToPoolBinding(
	ctx context.Context,
	pool *capsulev1beta2.ResourcePool,
	claim *capsulev1beta2.ResourcePoolClaim,
) (err error) {
	cond := meta.NewExhaustedCondition(claim)
	cond.Status = metav1.ConditionFalse
	cond.Reason = meta.NoExhaustionsReason
	cond.Message = "resource claimable from pool"

	if err = updateStatusAndEmitEvent(ctx, r.Client, r.recorder, claim, pool, cond); err != nil {
		return err
	}

	pool.AddClaimToStatus(claim)

	return err
}

// Attempts to garbage collect a ResourceQuota resource.
func (r *resourcePoolController) handleClaimDisassociation(
	ctx context.Context,
	log logr.Logger,
	pool *capsulev1beta2.ResourcePool,
	claim *capsulev1beta2.ResourcePoolClaimsItem,
) error {
	current := &capsulev1beta2.ResourcePoolClaim{
		ObjectMeta: metav1.ObjectMeta{
			Name:      claim.Name.String(),
			Namespace: claim.Namespace.String(),
			UID:       claim.UID,
		},
	}

	err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
		if err := r.reader.Get(ctx, types.NamespacedName{
			Name:      claim.Name.String(),
			Namespace: claim.Namespace.String(),
		}, current); err != nil {
			if apierrors.IsNotFound(err) {
				return nil
			}

			return fmt.Errorf("failed to refetch claim before patch: %w", err)
		}

		// Remove Status Items
		current.Status.Pool = meta.LocalRFC1123ObjectReferenceWithUID{}
		current.Status.Conditions.RemoveConditionByType(meta.BoundCondition)
		current.Status.Conditions.RemoveConditionByType(meta.ExhaustedCondition)

		if err := r.Client.Status().Update(ctx, current); err != nil {
			return fmt.Errorf("failed to update claim status: %w", err)
		}

		if !*pool.Spec.Config.DeleteBoundResources || meta.ReleaseAnnotationTriggers(current) {
			patch := client.MergeFrom(current.DeepCopy())
			meta.RemoveLooseOwnerReference(current, meta.GetLooseOwnerReference(pool))
			meta.ReleaseAnnotationRemove(current)

			if err := r.Patch(ctx, current, patch); err != nil {
				return fmt.Errorf("failed to patch claim: %w", err)
			}
		}

		r.recorder.Eventf(
			current,
			pool,
			corev1.EventTypeNormal,
			evt.ReasonDisassociated,
			evt.ActionDisassociating,
			"claim is disassociated from the pool",
		)

		return nil
	})
	if err != nil {
		log.V(3).Info("Removing owner reference failed", "claim", current.Name, "pool", pool.Name, "error", err)

		return err
	}

	pool.RemoveClaimFromStatus(current)

	return nil
}

// Synchronize resources quotas in all the given namespaces (routines).
func (r *resourcePoolController) syncResourceQuotas(
	ctx context.Context,
	c client.Client,
	reader client.Reader,
	quota *capsulev1beta2.ResourcePool,
	namespaces []corev1.Namespace,
) (err error) {
	group := new(errgroup.Group)

	for _, ns := range namespaces {
		namespace := ns

		group.Go(func() error {
			return r.syncResourceQuota(ctx, c, reader, quota, namespace)
		})
	}

	return group.Wait()
}

// Synchronize a single resourcequota.
func (r *resourcePoolController) syncResourceQuota(
	ctx context.Context,
	c client.Client,
	reader client.Reader,
	pool *capsulev1beta2.ResourcePool,
	namespace corev1.Namespace,
) (err error) {
	// getting ResourceQuota labels for the mutateFn
	var quotaLabel string

	if quotaLabel, err = utils.GetTypeLabel(&capsulev1beta2.ResourcePool{}); err != nil {
		return err
	}

	target := &corev1.ResourceQuota{
		ObjectMeta: metav1.ObjectMeta{
			Name:      pool.GetQuotaName(),
			Namespace: namespace.GetName(),
		},
	}

	if err := reader.Get(ctx, types.NamespacedName{Name: target.Name, Namespace: target.Namespace}, target); err != nil && !apierrors.IsNotFound(err) {
		return err
	}

	err = retry.RetryOnConflict(retry.DefaultBackoff, func() (retryErr error) {
		_, retryErr = controllerutil.CreateOrUpdate(ctx, c, target, func() (err error) {
			targetLabels := target.GetLabels()
			if targetLabels == nil {
				targetLabels = map[string]string{}
			}

			targetLabels[quotaLabel] = pool.Name
			targetLabels[meta.NewManagedByCapsuleLabel] = meta.ValueController

			target.SetLabels(targetLabels)
			target.Spec.Scopes = pool.Spec.Quota.Scopes
			target.Spec.ScopeSelector = pool.Spec.Quota.ScopeSelector

			// Assign to resourcequota all the claims + defaults
			target.Spec.Hard = pool.GetResourceQuotaHardResources(namespace.GetName())

			return controllerutil.SetControllerReference(pool, target, c.Scheme())
		})

		return retryErr
	})
	if err != nil {
		return err
	}

	return nil
}

// Handles new allocated resources before they are passed on to the pool itself.
// It does not verify the same stuff, as the admission for resourcepools.
func (r *resourcePoolController) handlePoolHardResources(pool *capsulev1beta2.ResourcePool) {
	if &pool.Status.Allocation.Hard != &pool.Spec.Quota.Hard {
		for resourceName := range pool.Status.Allocation.Hard {
			if _, ok := pool.Spec.Quota.Hard[resourceName]; !ok {
				r.metrics.DeleteResourcePoolSingleResourceMetric(pool.Name, resourceName.String())
			}
		}
	}

	pool.Status.Allocation.Hard = pool.Spec.Quota.Hard
}

// Get Currently selected namespaces for the resourcepool.
func (r *resourcePoolController) gatherMatchingNamespaces(
	ctx context.Context,
	log logr.Logger,
	pool *capsulev1beta2.ResourcePool,
) (namespaces []corev1.Namespace, err error) {
	// Collect Namespaces (Matching)
	namespaces = make([]corev1.Namespace, 0)
	seenNamespaces := make(map[string]struct{})

	if !pool.DeletionTimestamp.IsZero() {
		return namespaces, err
	}

	for _, selector := range pool.Spec.Selectors {
		selected, serr := selector.GetMatchingNamespaces(ctx, r.reader)
		if serr != nil {
			log.Error(err, "Cannot get matching namespaces")

			continue
		}

		for _, ns := range selected {
			if !ns.DeletionTimestamp.IsZero() {
				continue
			}

			if _, exists := seenNamespaces[ns.Name]; exists {
				continue
			}

			seenNamespaces[ns.Name] = struct{}{}

			namespaces = append(namespaces, ns)
		}
	}

	return namespaces, err
}

// Get Currently selected claims for the resourcepool.
func (r *resourcePoolController) gatherMatchingClaims(
	ctx context.Context,
	log logr.Logger,
	pool *capsulev1beta2.ResourcePool,
	namespaces map[string]struct{},
) (claims []capsulev1beta2.ResourcePoolClaim, err error) {
	if !pool.DeletionTimestamp.IsZero() {
		return claims, err
	}

	claimList := &capsulev1beta2.ResourcePoolClaimList{}
	if err := r.List(ctx, claimList, client.MatchingFields{".status.pool.uid": string(pool.GetUID())}); err != nil {
		log.Error(err, "failed to list ResourceQuotaClaims")

		return claims, err
	}

	filteredClaims := make([]capsulev1beta2.ResourcePoolClaim, 0)

	for _, claim := range claimList.Items {
		if meta.ReleaseAnnotationTriggers(&claim) {
			continue
		}

		if _, ok := namespaces[claim.Namespace]; !ok {
			continue
		}

		filteredClaims = append(filteredClaims, claim)
	}

	// Sort by creation timestamp (oldest first)
	sort.Slice(filteredClaims, func(i, j int) bool {
		a := filteredClaims[i]
		b := filteredClaims[j]

		// First, sort by CreationTimestamp
		if !a.CreationTimestamp.Equal(&b.CreationTimestamp) {
			return a.CreationTimestamp.Before(&b.CreationTimestamp)
		}

		// Tiebreaker: use name as a stable secondary sort - If CreationTimestamp is equal
		// (e.g., when two claims are created at the same time in Gitops environments or CI/CD pipelines)
		if a.Name != b.Name {
			return a.Name < b.Name
		}

		return a.Namespace < b.Namespace
	})

	return filteredClaims, nil
}

// Attempts to garbage collect a ResourceQuota resource.
func (r *resourcePoolController) garbageCollection(
	ctx context.Context,
	log logr.Logger,
	pool *capsulev1beta2.ResourcePool,
	claims []capsulev1beta2.ResourcePoolClaim,
	namespaces map[string]struct{},
) error {
	activeClaims := make(map[string]struct{}, len(claims))
	for _, claim := range claims {
		activeClaims[string(claim.UID)] = struct{}{}
	}

	log.V(5).Info("available items", "namespaces", namespaces, "claims", activeClaims)

	namespaceMarkedForGC := make(map[string]bool, len(pool.Status.Namespaces))

	for _, ns := range pool.Status.Namespaces {
		_, exists := namespaces[ns]
		if !exists {
			log.V(5).Info("garbage collecting namespace", "namespace", ns)

			namespaceMarkedForGC[ns] = true

			if err := r.garbageCollectNamespace(ctx, pool, ns); err != nil {
				r.log.Error(err, "Failed to garbage collect resource quota", "namespace", ns)

				return err
			}
		}
	}

	// Garbage collect namespaces which no longer match selector
	for ns, clms := range pool.Status.Claims {
		nsMarked := namespaceMarkedForGC[ns]

		for _, cl := range clms {
			_, claimActive := activeClaims[string(cl.UID)]

			if nsMarked || !claimActive {
				log.V(5).Info("Disassociating claim", "claim", cl.Name, "namespace", ns, "uid", cl.UID, "nsGC", nsMarked, "claimGC", claimActive)

				cl.Namespace = meta.RFC1123SubdomainName(ns)
				if err := r.handleClaimDisassociation(ctx, log, pool, cl); err != nil {
					r.log.Error(err, "Failed to disassociate claim", "namespace", ns, "uid", cl.UID)

					return err
				}
			}
		}

		if nsMarked || len(pool.Status.Claims[ns]) == 0 {
			delete(pool.Status.Claims, ns)
		}
	}

	// We can recalculate the usage in the end
	// Since it's only going to decrease
	pool.CalculateClaimedResources()

	return nil
}

// Attempts to garbage collect a ResourceQuota resource.
func (r *resourcePoolController) garbageCollectNamespace(
	ctx context.Context,
	pool *capsulev1beta2.ResourcePool,
	namespace string,
) error {
	r.metrics.DeleteResourcePoolNamespaceMetric(pool.Name, namespace)

	// Check if the namespace still exists
	ns := &corev1.Namespace{}
	if err := r.reader.Get(ctx, types.NamespacedName{Name: namespace}, ns); err != nil {
		if apierrors.IsNotFound(err) {
			r.log.V(5).Info("Namespace does not exist, skipping garbage collection", "namespace", namespace)

			return nil
		}

		return fmt.Errorf("failed to check namespace existence: %w", err)
	}

	name := pool.GetQuotaName()

	// Attempt to delete the ResourceQuota
	target := &corev1.ResourceQuota{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
		},
	}

	err := r.reader.Get(ctx, types.NamespacedName{Namespace: namespace, Name: target.GetName()}, target)
	if err != nil {
		if apierrors.IsNotFound(err) {
			r.log.V(5).Info("ResourceQuota already deleted", "namespace", namespace, "name", name)

			return nil
		}

		return err
	}

	// Delete the ResourceQuota
	if err := r.Delete(ctx, target); err != nil {
		return fmt.Errorf("failed to delete ResourceQuota %s in namespace %s: %w", name, namespace, err)
	}

	return nil
}

func (r *resourcePoolController) updateStatus(ctx context.Context, instance *capsulev1beta2.ResourcePool, reconcileError error) error {
	return retry.RetryOnConflict(retry.DefaultBackoff, func() (err error) {
		latest := &capsulev1beta2.ResourcePool{}
		if err = r.reader.Get(ctx, types.NamespacedName{Name: instance.GetName()}, latest); err != nil {
			if apierrors.IsNotFound(err) {
				return nil
			}

			return err
		}

		originalStatus := latest.Status.DeepCopy()

		latest.Status = instance.Status
		latest.Status.ObservedGeneration = instance.GetGeneration()

		// Set Ready Condition
		readyCondition := meta.NewReadyCondition(instance)
		if reconcileError != nil {
			readyCondition.Message = reconcileError.Error()
			readyCondition.Status = metav1.ConditionFalse
			readyCondition.Reason = meta.FailedReason
		}

		latest.Status.Conditions.UpdateConditionByType(readyCondition)

		// Set Exhaustion Condition
		exCondition := meta.NewExhaustedCondition(instance)
		if len(latest.Status.Exhaustions) != 0 {
			exCondition.Message = "Pool has exhaustions"
			exCondition.Status = metav1.ConditionTrue
			exCondition.Reason = meta.PoolExhaustedReason
		}

		latest.Status.Conditions.UpdateConditionByType(exCondition)

		if reflect.DeepEqual(*originalStatus, latest.Status) {
			return nil
		}

		if err := r.Client.Status().Update(ctx, latest); err != nil {
			return err
		}

		// Keep the in-memory object aligned with what we just wrote.
		instance.Status = latest.Status

		return nil
	})
}
