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

package predicates

import (
	"reflect"

	corev1 "k8s.io/api/core/v1"
	networkingv1 "k8s.io/api/networking/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/predicate"

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

func ClassChanged() predicate.Predicate {
	return predicate.Or(
		predicate.GenerationChangedPredicate{},
		UpdatedMetadataPredicate{},
		DeletionChangedPredicate{},
	)
}

// TenantManagedResourceChangedPredicate admits drift in fields reconciled by
// the Tenant controller while filtering status-only updates. Several built-in
// resources do not reliably increment metadata.generation when their desired
// fields change, so GenerationChangedPredicate alone cannot detect tampering.
type TenantManagedResourceChangedPredicate struct{ predicate.Funcs }

func (TenantManagedResourceChangedPredicate) Create(event.CreateEvent) bool   { return true }
func (TenantManagedResourceChangedPredicate) Delete(event.DeleteEvent) bool   { return true }
func (TenantManagedResourceChangedPredicate) Generic(event.GenericEvent) bool { return false }
func (TenantManagedResourceChangedPredicate) Update(e event.UpdateEvent) bool {
	if e.ObjectOld == nil || e.ObjectNew == nil {
		return false
	}

	if !utils.MapEqual(e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels()) ||
		!utils.MapEqual(e.ObjectOld.GetAnnotations(), e.ObjectNew.GetAnnotations()) ||
		!reflect.DeepEqual(e.ObjectOld.GetOwnerReferences(), e.ObjectNew.GetOwnerReferences()) ||
		!reflect.DeepEqual(e.ObjectOld.GetFinalizers(), e.ObjectNew.GetFinalizers()) ||
		(e.ObjectOld.GetDeletionTimestamp() == nil) != (e.ObjectNew.GetDeletionTimestamp() == nil) {
		return true
	}

	switch oldObject := e.ObjectOld.(type) {
	case *corev1.LimitRange:
		newObject, ok := e.ObjectNew.(*corev1.LimitRange)

		return ok && !reflect.DeepEqual(oldObject.Spec, newObject.Spec)
	case *corev1.ResourceQuota:
		newObject, ok := e.ObjectNew.(*corev1.ResourceQuota)

		return ok && !reflect.DeepEqual(oldObject.Spec, newObject.Spec)
	case *networkingv1.NetworkPolicy:
		newObject, ok := e.ObjectNew.(*networkingv1.NetworkPolicy)

		return ok && !reflect.DeepEqual(oldObject.Spec, newObject.Spec)
	case *rbacv1.RoleBinding:
		newObject, ok := e.ObjectNew.(*rbacv1.RoleBinding)

		return ok && (!reflect.DeepEqual(oldObject.RoleRef, newObject.RoleRef) ||
			!reflect.DeepEqual(oldObject.Subjects, newObject.Subjects))
	default:
		return false
	}
}

type NamespaceTenantStateChangedPredicate struct{ predicate.Funcs }

func (NamespaceTenantStateChangedPredicate) Create(event.CreateEvent) bool   { return true }
func (NamespaceTenantStateChangedPredicate) Delete(event.DeleteEvent) bool   { return true }
func (NamespaceTenantStateChangedPredicate) Generic(event.GenericEvent) bool { return false }
func (NamespaceTenantStateChangedPredicate) Update(e event.UpdateEvent) bool {
	oldNamespace, oldOK := e.ObjectOld.(*corev1.Namespace)

	newNamespace, newOK := e.ObjectNew.(*corev1.Namespace)

	if !oldOK || !newOK {
		return false
	}

	return !reflect.DeepEqual(oldNamespace.Labels, newNamespace.Labels) ||
		!reflect.DeepEqual(oldNamespace.Annotations, newNamespace.Annotations) ||
		!reflect.DeepEqual(oldNamespace.OwnerReferences, newNamespace.OwnerReferences) ||
		(oldNamespace.DeletionTimestamp == nil) != (newNamespace.DeletionTimestamp == nil)
}

type QuantityLedgerWorkChangedPredicate struct{ predicate.Funcs }

func (QuantityLedgerWorkChangedPredicate) Create(e event.CreateEvent) bool {
	ledger, ok := e.Object.(*capsulev1beta2.QuantityLedger)
	if !ok {
		return false
	}

	// A ledger can be created and receive admission reservations before the
	// informer observes its first Add event. In that case the Add is the only
	// work notification; filtering it would strand the reservations forever.
	return quantityLedgerHasWork(ledger)
}

func (QuantityLedgerWorkChangedPredicate) Delete(event.DeleteEvent) bool   { return true }
func (QuantityLedgerWorkChangedPredicate) Generic(event.GenericEvent) bool { return false }
func (QuantityLedgerWorkChangedPredicate) Update(e event.UpdateEvent) bool {
	oldLedger, oldOK := e.ObjectOld.(*capsulev1beta2.QuantityLedger)

	newLedger, newOK := e.ObjectNew.(*capsulev1beta2.QuantityLedger)

	if !oldOK || !newOK {
		return false
	}

	if oldLedger.Generation != newLedger.Generation {
		return true
	}

	// Informers may coalesce an intermediate settled state. An update can
	// therefore replace reservations with pending deletes (or append more
	// reservations) while both observed snapshots contain work. Admit every
	// newly introduced or changed work item so it cannot be stranded. Pure
	// settlement/removal updates remain filtered.
	return quantityLedgerIntroducesWork(oldLedger, newLedger)
}

func quantityLedgerHasWork(ledger *capsulev1beta2.QuantityLedger) bool {
	return len(ledger.Status.Reservations) > 0 ||
		len(ledger.Status.PendingDeletes) > 0
}

func quantityLedgerIntroducesWork(oldLedger, newLedger *capsulev1beta2.QuantityLedger) bool {
	matchedReservations := make([]bool, len(oldLedger.Status.Reservations))

	for _, newReservation := range newLedger.Status.Reservations {
		matched := false

		for i, oldReservation := range oldLedger.Status.Reservations {
			if matchedReservations[i] || !reflect.DeepEqual(oldReservation, newReservation) {
				continue
			}

			matchedReservations[i] = true
			matched = true

			break
		}

		if !matched {
			return true
		}
	}

	matchedPendingDeletes := make([]bool, len(oldLedger.Status.PendingDeletes))

	for _, newPendingDelete := range newLedger.Status.PendingDeletes {
		matched := false

		for i, oldPendingDelete := range oldLedger.Status.PendingDeletes {
			if matchedPendingDeletes[i] || !reflect.DeepEqual(oldPendingDelete, newPendingDelete) {
				continue
			}

			matchedPendingDeletes[i] = true
			matched = true

			break
		}

		if !matched {
			return true
		}
	}

	return false
}

type ProvisionerSubjectsChangedPredicate struct{ predicate.Funcs }

func (ProvisionerSubjectsChangedPredicate) Create(event.CreateEvent) bool   { return true }
func (ProvisionerSubjectsChangedPredicate) Delete(event.DeleteEvent) bool   { return false }
func (ProvisionerSubjectsChangedPredicate) Generic(event.GenericEvent) bool { return false }
func (ProvisionerSubjectsChangedPredicate) Update(e event.UpdateEvent) bool {
	oldConfig, oldOK := e.ObjectOld.(*capsulev1beta2.CapsuleConfiguration)

	newConfig, newOK := e.ObjectNew.(*capsulev1beta2.CapsuleConfiguration)

	if !oldOK || !newOK {
		return false
	}

	return !reflect.DeepEqual(oldConfig.Spec.Administrators, newConfig.Spec.Administrators) ||
		!reflect.DeepEqual(oldConfig.Status.Users, newConfig.Status.Users) ||
		oldConfig.Spec.AllowServiceAccountPromotion != newConfig.Spec.AllowServiceAccountPromotion ||
		!reflect.DeepEqual(oldConfig.Spec.RBAC, newConfig.Spec.RBAC)
}

type ResourcePoolNamespacesChangedPredicate struct{ predicate.Funcs }

func (ResourcePoolNamespacesChangedPredicate) Create(event.CreateEvent) bool   { return true }
func (ResourcePoolNamespacesChangedPredicate) Delete(event.DeleteEvent) bool   { return true }
func (ResourcePoolNamespacesChangedPredicate) Generic(event.GenericEvent) bool { return false }
func (ResourcePoolNamespacesChangedPredicate) Update(e event.UpdateEvent) bool {
	oldPool, oldOK := e.ObjectOld.(*capsulev1beta2.ResourcePool)

	newPool, newOK := e.ObjectNew.(*capsulev1beta2.ResourcePool)

	if !oldOK || !newOK {
		return false
	}

	return !reflect.DeepEqual(oldPool.Status.Namespaces, newPool.Status.Namespaces) ||
		(oldPool.DeletionTimestamp == nil) != (newPool.DeletionTimestamp == nil)
}

// ResourceQuotaUsageChangedPredicate admits only usage changes from the quota
// controller. ResourcePool claim Bound conditions are derived from status.used,
// while status.hard and other status updates do not affect claim scheduling.
type ResourceQuotaUsageChangedPredicate struct{ predicate.Funcs }

func (ResourceQuotaUsageChangedPredicate) Create(event.CreateEvent) bool   { return false }
func (ResourceQuotaUsageChangedPredicate) Delete(event.DeleteEvent) bool   { return false }
func (ResourceQuotaUsageChangedPredicate) Generic(event.GenericEvent) bool { return false }
func (ResourceQuotaUsageChangedPredicate) Update(e event.UpdateEvent) bool {
	oldQuota, oldOK := e.ObjectOld.(*corev1.ResourceQuota)

	newQuota, newOK := e.ObjectNew.(*corev1.ResourceQuota)

	if !oldOK || !newOK {
		return false
	}

	return !reflect.DeepEqual(oldQuota.Status.Used, newQuota.Status.Used)
}

type DependencyStateChangedPredicate struct{ predicate.Funcs }

func (DependencyStateChangedPredicate) Create(event.CreateEvent) bool   { return true }
func (DependencyStateChangedPredicate) Delete(event.DeleteEvent) bool   { return true }
func (DependencyStateChangedPredicate) Generic(event.GenericEvent) bool { return false }
func (DependencyStateChangedPredicate) Update(e event.UpdateEvent) bool {
	if e.ObjectOld == nil || e.ObjectNew == nil {
		return false
	}

	return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() ||
		(e.ObjectOld.GetDeletionTimestamp() == nil) != (e.ObjectNew.GetDeletionTimestamp() == nil) ||
		!reflect.DeepEqual(resourceReadyCondition(e.ObjectOld), resourceReadyCondition(e.ObjectNew))
}

type TenantSelectionChangedPredicate struct{ predicate.Funcs }

func (TenantSelectionChangedPredicate) Create(event.CreateEvent) bool   { return true }
func (TenantSelectionChangedPredicate) Delete(event.DeleteEvent) bool   { return true }
func (TenantSelectionChangedPredicate) Generic(event.GenericEvent) bool { return false }
func (TenantSelectionChangedPredicate) Update(e event.UpdateEvent) bool {
	oldTenant, oldOK := e.ObjectOld.(*capsulev1beta2.Tenant)

	newTenant, newOK := e.ObjectNew.(*capsulev1beta2.Tenant)

	if !oldOK || !newOK {
		return false
	}

	return !utils.MapEqual(oldTenant.Labels, newTenant.Labels) ||
		!reflect.DeepEqual(oldTenant.Status.Namespaces, newTenant.Status.Namespaces) ||
		(oldTenant.DeletionTimestamp == nil) != (newTenant.DeletionTimestamp == nil)
}

type TenantPodOptionsChangedPredicate struct{ predicate.Funcs }

func (TenantPodOptionsChangedPredicate) Create(event.CreateEvent) bool   { return false }
func (TenantPodOptionsChangedPredicate) Delete(event.DeleteEvent) bool   { return false }
func (TenantPodOptionsChangedPredicate) Generic(event.GenericEvent) bool { return false }

//nolint:staticcheck
func (TenantPodOptionsChangedPredicate) Update(e event.UpdateEvent) bool {
	oldTenant, oldOK := e.ObjectOld.(*capsulev1beta2.Tenant)
	newTenant, newOK := e.ObjectNew.(*capsulev1beta2.Tenant)

	return oldOK && newOK && !reflect.DeepEqual(oldTenant.Spec.PodOptions, newTenant.Spec.PodOptions)
}

type TenantServiceOptionsChangedPredicate struct{ predicate.Funcs }

func (TenantServiceOptionsChangedPredicate) Create(event.CreateEvent) bool   { return false }
func (TenantServiceOptionsChangedPredicate) Delete(event.DeleteEvent) bool   { return false }
func (TenantServiceOptionsChangedPredicate) Generic(event.GenericEvent) bool { return false }

//nolint:staticcheck
func (TenantServiceOptionsChangedPredicate) Update(e event.UpdateEvent) bool {
	oldTenant, oldOK := e.ObjectOld.(*capsulev1beta2.Tenant)
	newTenant, newOK := e.ObjectNew.(*capsulev1beta2.Tenant)

	return oldOK && newOK && !reflect.DeepEqual(oldTenant.Spec.ServiceOptions, newTenant.Spec.ServiceOptions)
}

type TenantNamespacesChangedPredicate struct{ predicate.Funcs }

func (TenantNamespacesChangedPredicate) Create(event.CreateEvent) bool   { return true }
func (TenantNamespacesChangedPredicate) Delete(event.DeleteEvent) bool   { return true }
func (TenantNamespacesChangedPredicate) Generic(event.GenericEvent) bool { return false }
func (TenantNamespacesChangedPredicate) Update(e event.UpdateEvent) bool {
	oldTenant, oldOK := e.ObjectOld.(*capsulev1beta2.Tenant)
	newTenant, newOK := e.ObjectNew.(*capsulev1beta2.Tenant)

	return oldOK && newOK && !reflect.DeepEqual(oldTenant.Status.Namespaces, newTenant.Status.Namespaces)
}

type NamespaceMetadataChangedPredicate struct{ predicate.Funcs }

func (NamespaceMetadataChangedPredicate) Create(event.CreateEvent) bool   { return true }
func (NamespaceMetadataChangedPredicate) Delete(event.DeleteEvent) bool   { return true }
func (NamespaceMetadataChangedPredicate) Generic(event.GenericEvent) bool { return false }
func (NamespaceMetadataChangedPredicate) Update(e event.UpdateEvent) bool {
	oldNamespace, oldOK := e.ObjectOld.(*corev1.Namespace)
	newNamespace, newOK := e.ObjectNew.(*corev1.Namespace)

	return oldOK && newOK && (!reflect.DeepEqual(oldNamespace.Labels, newNamespace.Labels) ||
		!reflect.DeepEqual(oldNamespace.Annotations, newNamespace.Annotations))
}

func resourceReadyCondition(obj any) any {
	switch typed := obj.(type) {
	case *capsulev1beta2.GlobalTenantResource:
		return typed.Status.Conditions.GetConditionByType(capmeta.ReadyCondition)
	case *capsulev1beta2.TenantResource:
		return typed.Status.Conditions.GetConditionByType(capmeta.ReadyCondition)
	default:
		return nil
	}
}
