/*
Copyright 2021 Kong, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Code generated by Kong; DO NOT EDIT.

package configuration

import (
	"context"
	"fmt"
	"reflect"
	"time"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	extv1beta1 "k8s.io/api/extensions/v1beta1"
	netv1 "k8s.io/api/networking/v1"
	netv1beta1 "k8s.io/api/networking/v1beta1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	knativev1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1"
	knativeApis "knative.dev/pkg/apis"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/handler"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
	"sigs.k8s.io/controller-runtime/pkg/source"

	ctrlutils "github.com/kong/kubernetes-ingress-controller/v2/internal/controllers/utils"
	"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane"
	"github.com/kong/kubernetes-ingress-controller/v2/internal/util"
	"github.com/kong/kubernetes-ingress-controller/v2/internal/util/kubernetes/object/status"
	kongv1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1"
	kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1beta1"
)

// -----------------------------------------------------------------------------
// CoreV1 Service - Reconciler
// -----------------------------------------------------------------------------

// CoreV1ServiceReconciler reconciles Service resources
type CoreV1ServiceReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient
}

// SetupWithManager sets up the controller with the Manager.
func (r *CoreV1ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("CoreV1Service", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	return c.Watch(
		&source.Kind{Type: &corev1.Service{}},
		&handler.EnqueueRequestForObject{},
	)
}

//+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch
//+kubebuilder:rbac:groups="",resources=services/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *CoreV1ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("CoreV1Service", req.NamespacedName)

	// get the relevant object
	obj := new(corev1.Service)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Service", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// CoreV1 Endpoints - Reconciler
// -----------------------------------------------------------------------------

// CoreV1EndpointsReconciler reconciles Endpoints resources
type CoreV1EndpointsReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient
}

// SetupWithManager sets up the controller with the Manager.
func (r *CoreV1EndpointsReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("CoreV1Endpoints", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	return c.Watch(
		&source.Kind{Type: &corev1.Endpoints{}},
		&handler.EnqueueRequestForObject{},
	)
}

//+kubebuilder:rbac:groups="",resources=endpoints,verbs=list;watch
//+kubebuilder:rbac:groups="",resources=endpoints/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *CoreV1EndpointsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("CoreV1Endpoints", req.NamespacedName)

	// get the relevant object
	obj := new(corev1.Endpoints)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Endpoints", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// CoreV1 Secret - Reconciler
// -----------------------------------------------------------------------------

// CoreV1SecretReconciler reconciles Secret resources
type CoreV1SecretReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient
}

// SetupWithManager sets up the controller with the Manager.
func (r *CoreV1SecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("CoreV1Secret", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	return c.Watch(
		&source.Kind{Type: &corev1.Secret{}},
		&handler.EnqueueRequestForObject{},
	)
}

//+kubebuilder:rbac:groups="",resources=secrets,verbs=list;watch
//+kubebuilder:rbac:groups="",resources=secrets/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *CoreV1SecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("CoreV1Secret", req.NamespacedName)

	// get the relevant object
	obj := new(corev1.Secret)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Secret", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// NetV1 Ingress - Reconciler
// -----------------------------------------------------------------------------

// NetV1IngressReconciler reconciles Ingress resources
type NetV1IngressReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient

	DataplaneAddressFinder *dataplane.AddressFinder
	StatusQueue            *status.Queue

	IngressClassName string
}

// SetupWithManager sets up the controller with the Manager.
func (r *NetV1IngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("NetV1Ingress", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	// if configured, start the status updater controller
	if r.StatusQueue != nil {
		if err := c.Watch(
			&source.Channel{Source: r.StatusQueue.Subscribe(schema.GroupVersionKind{
				Group:   "networking.k8s.io",
				Version: "v1",
				Kind:    "Ingress",
			})},
			&handler.EnqueueRequestForObject{},
		); err != nil {
			return err
		}
	}
	// err = c.Watch(
	// 	&source.Kind{Type: &netv1.IngressClass{}},
	// 	handler.EnqueueRequestsFromMapFunc(r.listClassless),
	// 	predicate.NewPredicateFuncs(ctrlutils.IsDefaultIngressClass),
	// )
	// if err != nil {
	// 	return err
	// }
	preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName)
	return c.Watch(
		&source.Kind{Type: &netv1.Ingress{}},
		&handler.EnqueueRequestForObject{},
		preds,
	)
}
// listClassless finds and reconciles all objects without ingress class information
func (r *NetV1IngressReconciler) listClassless(obj client.Object) []reconcile.Request {
	resourceList := &netv1.IngressList{}
	if err := r.Client.List(context.Background(), resourceList); err != nil {
		r.Log.Error(err, "failed to list classless ingresses")
		return nil
	}
	var recs []reconcile.Request
	for _, resource := range resourceList.Items {
		if ctrlutils.IsIngressClassEmpty(&resource) {
			recs = append(recs, reconcile.Request{
				NamespacedName: types.NamespacedName{
					Namespace: resource.Namespace,
					Name:      resource.Name,
				},
			})
		}
	}
	return recs
}

//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch
//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *NetV1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("NetV1Ingress", req.NamespacedName)

	// get the relevant object
	obj := new(netv1.Ingress)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Ingress", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	class := new(netv1beta1.IngressClass)
	if err := r.Get(ctx, types.NamespacedName{Name: r.IngressClassName}, class); err != nil {
		// we log this without taking action to support legacy configurations that only set ingressClassName or
		// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
		// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
		// if none exists.
		log.V(util.DebugLevel).Info("could not retrieve IngressClass", "ingressclass", r.IngressClassName)
	}
	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}
	// if status updates are enabled report the status for the object
	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
		log.V(util.DebugLevel).Info("determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
		if !r.DataplaneClient.KubernetesObjectIsConfigured(obj) {
			log.V(util.DebugLevel).Error(fmt.Errorf("resource not yet configured in the data-plane"), "namespace", req.Namespace, "name", req.Name)
			return ctrl.Result{Requeue: true}, nil // requeue until the object has been properly configured
		}

		log.V(util.DebugLevel).Info("determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
		addrs, err := r.DataplaneAddressFinder.GetLoadBalancerAddresses()
		if err != nil {
			return ctrl.Result{}, err
		}

		log.V(util.DebugLevel).Info("found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
		if len(obj.Status.LoadBalancer.Ingress) != len(addrs) || !reflect.DeepEqual(obj.Status.LoadBalancer.Ingress, addrs) {
			obj.Status.LoadBalancer.Ingress = addrs
			return ctrl.Result{}, r.Status().Update(ctx, obj)
		} else {
			log.V(util.DebugLevel).Info("status update not needed", "namespace", req.Namespace, "name", req.Name)
		}
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// NetV1 IngressClass - Reconciler
// -----------------------------------------------------------------------------

// NetV1IngressClassReconciler reconciles IngressClass resources
type NetV1IngressClassReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient
}

// SetupWithManager sets up the controller with the Manager.
func (r *NetV1IngressClassReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("NetV1IngressClass", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	return c.Watch(
		&source.Kind{Type: &netv1.IngressClass{}},
		&handler.EnqueueRequestForObject{},
	)
}

//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingressclasses,verbs=get;list;watch

// Reconcile processes the watched objects
func (r *NetV1IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("NetV1IngressClass", req.NamespacedName)

	// get the relevant object
	obj := new(netv1.IngressClass)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "IngressClass", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// NetV1Beta1 Ingress - Reconciler
// -----------------------------------------------------------------------------

// NetV1Beta1IngressReconciler reconciles Ingress resources
type NetV1Beta1IngressReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient

	DataplaneAddressFinder *dataplane.AddressFinder
	StatusQueue            *status.Queue

	IngressClassName string
}

// SetupWithManager sets up the controller with the Manager.
func (r *NetV1Beta1IngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("NetV1Beta1Ingress", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	// if configured, start the status updater controller
	if r.StatusQueue != nil {
		if err := c.Watch(
			&source.Channel{Source: r.StatusQueue.Subscribe(schema.GroupVersionKind{
				Group:   "networking.k8s.io",
				Version: "v1beta1",
				Kind:    "Ingress",
			})},
			&handler.EnqueueRequestForObject{},
		); err != nil {
			return err
		}
	}
	// err = c.Watch(
	// 	&source.Kind{Type: &netv1.IngressClass{}},
	// 	handler.EnqueueRequestsFromMapFunc(r.listClassless),
	// 	predicate.NewPredicateFuncs(ctrlutils.IsDefaultIngressClass),
	// )
	// if err != nil {
	// 	return err
	// }
	preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName)
	return c.Watch(
		&source.Kind{Type: &netv1beta1.Ingress{}},
		&handler.EnqueueRequestForObject{},
		preds,
	)
}
// listClassless finds and reconciles all objects without ingress class information
func (r *NetV1Beta1IngressReconciler) listClassless(obj client.Object) []reconcile.Request {
	resourceList := &netv1beta1.IngressList{}
	if err := r.Client.List(context.Background(), resourceList); err != nil {
		r.Log.Error(err, "failed to list classless ingresses")
		return nil
	}
	var recs []reconcile.Request
	for _, resource := range resourceList.Items {
		if ctrlutils.IsIngressClassEmpty(&resource) {
			recs = append(recs, reconcile.Request{
				NamespacedName: types.NamespacedName{
					Namespace: resource.Namespace,
					Name:      resource.Name,
				},
			})
		}
	}
	return recs
}

//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch
//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *NetV1Beta1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("NetV1Beta1Ingress", req.NamespacedName)

	// get the relevant object
	obj := new(netv1beta1.Ingress)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Ingress", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	class := new(netv1beta1.IngressClass)
	if err := r.Get(ctx, types.NamespacedName{Name: r.IngressClassName}, class); err != nil {
		// we log this without taking action to support legacy configurations that only set ingressClassName or
		// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
		// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
		// if none exists.
		log.V(util.DebugLevel).Info("could not retrieve IngressClass", "ingressclass", r.IngressClassName)
	}
	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}
	// if status updates are enabled report the status for the object
	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
		log.V(util.DebugLevel).Info("determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
		if !r.DataplaneClient.KubernetesObjectIsConfigured(obj) {
			log.V(util.DebugLevel).Error(fmt.Errorf("resource not yet configured in the data-plane"), "namespace", req.Namespace, "name", req.Name)
			return ctrl.Result{Requeue: true}, nil // requeue until the object has been properly configured
		}

		log.V(util.DebugLevel).Info("determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
		addrs, err := r.DataplaneAddressFinder.GetLoadBalancerAddresses()
		if err != nil {
			return ctrl.Result{}, err
		}

		log.V(util.DebugLevel).Info("found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
		if len(obj.Status.LoadBalancer.Ingress) != len(addrs) || !reflect.DeepEqual(obj.Status.LoadBalancer.Ingress, addrs) {
			obj.Status.LoadBalancer.Ingress = addrs
			return ctrl.Result{}, r.Status().Update(ctx, obj)
		} else {
			log.V(util.DebugLevel).Info("status update not needed", "namespace", req.Namespace, "name", req.Name)
		}
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// NetV1Beta1 IngressClass - Reconciler
// -----------------------------------------------------------------------------

// NetV1Beta1IngressClassReconciler reconciles IngressClass resources
type NetV1Beta1IngressClassReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient
}

// SetupWithManager sets up the controller with the Manager.
func (r *NetV1Beta1IngressClassReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("NetV1Beta1IngressClass", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	return c.Watch(
		&source.Kind{Type: &netv1beta1.IngressClass{}},
		&handler.EnqueueRequestForObject{},
	)
}

//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingressclasses,verbs=get;list;watch

// Reconcile processes the watched objects
func (r *NetV1Beta1IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("NetV1Beta1IngressClass", req.NamespacedName)

	// get the relevant object
	obj := new(netv1beta1.IngressClass)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "IngressClass", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// ExtV1Beta1 Ingress - Reconciler
// -----------------------------------------------------------------------------

// ExtV1Beta1IngressReconciler reconciles Ingress resources
type ExtV1Beta1IngressReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient

	DataplaneAddressFinder *dataplane.AddressFinder
	StatusQueue            *status.Queue

	IngressClassName string
}

// SetupWithManager sets up the controller with the Manager.
func (r *ExtV1Beta1IngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("ExtV1Beta1Ingress", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	// if configured, start the status updater controller
	if r.StatusQueue != nil {
		if err := c.Watch(
			&source.Channel{Source: r.StatusQueue.Subscribe(schema.GroupVersionKind{
				Group:   "extensions",
				Version: "v1beta1",
				Kind:    "Ingress",
			})},
			&handler.EnqueueRequestForObject{},
		); err != nil {
			return err
		}
	}
	// err = c.Watch(
	// 	&source.Kind{Type: &netv1.IngressClass{}},
	// 	handler.EnqueueRequestsFromMapFunc(r.listClassless),
	// 	predicate.NewPredicateFuncs(ctrlutils.IsDefaultIngressClass),
	// )
	// if err != nil {
	// 	return err
	// }
	preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName)
	return c.Watch(
		&source.Kind{Type: &extv1beta1.Ingress{}},
		&handler.EnqueueRequestForObject{},
		preds,
	)
}
// listClassless finds and reconciles all objects without ingress class information
func (r *ExtV1Beta1IngressReconciler) listClassless(obj client.Object) []reconcile.Request {
	resourceList := &extv1beta1.IngressList{}
	if err := r.Client.List(context.Background(), resourceList); err != nil {
		r.Log.Error(err, "failed to list classless ingresses")
		return nil
	}
	var recs []reconcile.Request
	for _, resource := range resourceList.Items {
		if ctrlutils.IsIngressClassEmpty(&resource) {
			recs = append(recs, reconcile.Request{
				NamespacedName: types.NamespacedName{
					Namespace: resource.Namespace,
					Name:      resource.Name,
				},
			})
		}
	}
	return recs
}

//+kubebuilder:rbac:groups=extensions,resources=ingresses,verbs=get;list;watch
//+kubebuilder:rbac:groups=extensions,resources=ingresses/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *ExtV1Beta1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("ExtV1Beta1Ingress", req.NamespacedName)

	// get the relevant object
	obj := new(extv1beta1.Ingress)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Ingress", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	class := new(netv1beta1.IngressClass)
	if err := r.Get(ctx, types.NamespacedName{Name: r.IngressClassName}, class); err != nil {
		// we log this without taking action to support legacy configurations that only set ingressClassName or
		// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
		// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
		// if none exists.
		log.V(util.DebugLevel).Info("could not retrieve IngressClass", "ingressclass", r.IngressClassName)
	}
	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}
	// if status updates are enabled report the status for the object
	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
		log.V(util.DebugLevel).Info("determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
		if !r.DataplaneClient.KubernetesObjectIsConfigured(obj) {
			log.V(util.DebugLevel).Error(fmt.Errorf("resource not yet configured in the data-plane"), "namespace", req.Namespace, "name", req.Name)
			return ctrl.Result{Requeue: true}, nil // requeue until the object has been properly configured
		}

		log.V(util.DebugLevel).Info("determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
		addrs, err := r.DataplaneAddressFinder.GetLoadBalancerAddresses()
		if err != nil {
			return ctrl.Result{}, err
		}

		log.V(util.DebugLevel).Info("found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
		if len(obj.Status.LoadBalancer.Ingress) != len(addrs) || !reflect.DeepEqual(obj.Status.LoadBalancer.Ingress, addrs) {
			obj.Status.LoadBalancer.Ingress = addrs
			return ctrl.Result{}, r.Status().Update(ctx, obj)
		} else {
			log.V(util.DebugLevel).Info("status update not needed", "namespace", req.Namespace, "name", req.Name)
		}
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// KongV1 KongIngress - Reconciler
// -----------------------------------------------------------------------------

// KongV1KongIngressReconciler reconciles KongIngress resources
type KongV1KongIngressReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient
}

// SetupWithManager sets up the controller with the Manager.
func (r *KongV1KongIngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("KongV1KongIngress", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	return c.Watch(
		&source.Kind{Type: &kongv1.KongIngress{}},
		&handler.EnqueueRequestForObject{},
	)
}

//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongingresses,verbs=get;list;watch
//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongingresses/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *KongV1KongIngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("KongV1KongIngress", req.NamespacedName)

	// get the relevant object
	obj := new(kongv1.KongIngress)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "KongIngress", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// KongV1 KongPlugin - Reconciler
// -----------------------------------------------------------------------------

// KongV1KongPluginReconciler reconciles KongPlugin resources
type KongV1KongPluginReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient
}

// SetupWithManager sets up the controller with the Manager.
func (r *KongV1KongPluginReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("KongV1KongPlugin", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	return c.Watch(
		&source.Kind{Type: &kongv1.KongPlugin{}},
		&handler.EnqueueRequestForObject{},
	)
}

//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongplugins,verbs=get;list;watch
//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongplugins/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *KongV1KongPluginReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("KongV1KongPlugin", req.NamespacedName)

	// get the relevant object
	obj := new(kongv1.KongPlugin)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "KongPlugin", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// KongV1 KongClusterPlugin - Reconciler
// -----------------------------------------------------------------------------

// KongV1KongClusterPluginReconciler reconciles KongClusterPlugin resources
type KongV1KongClusterPluginReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient

	IngressClassName string
}

// SetupWithManager sets up the controller with the Manager.
func (r *KongV1KongClusterPluginReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("KongV1KongClusterPlugin", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	// err = c.Watch(
	// 	&source.Kind{Type: &netv1.IngressClass{}},
	// 	handler.EnqueueRequestsFromMapFunc(r.listClassless),
	// 	predicate.NewPredicateFuncs(ctrlutils.IsDefaultIngressClass),
	// )
	// if err != nil {
	// 	return err
	// }
	preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName)
	return c.Watch(
		&source.Kind{Type: &kongv1.KongClusterPlugin{}},
		&handler.EnqueueRequestForObject{},
		preds,
	)
}
// listClassless finds and reconciles all objects without ingress class information
func (r *KongV1KongClusterPluginReconciler) listClassless(obj client.Object) []reconcile.Request {
	resourceList := &kongv1.KongClusterPluginList{}
	if err := r.Client.List(context.Background(), resourceList); err != nil {
		r.Log.Error(err, "failed to list classless kongclusterplugins")
		return nil
	}
	var recs []reconcile.Request
	for _, resource := range resourceList.Items {
		if ctrlutils.IsIngressClassEmpty(&resource) {
			recs = append(recs, reconcile.Request{
				NamespacedName: types.NamespacedName{
					Namespace: resource.Namespace,
					Name:      resource.Name,
				},
			})
		}
	}
	return recs
}

//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongclusterplugins,verbs=get;list;watch
//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongclusterplugins/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *KongV1KongClusterPluginReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("KongV1KongClusterPlugin", req.NamespacedName)

	// get the relevant object
	obj := new(kongv1.KongClusterPlugin)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "KongClusterPlugin", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	class := new(netv1beta1.IngressClass)
	if err := r.Get(ctx, types.NamespacedName{Name: r.IngressClassName}, class); err != nil {
		// we log this without taking action to support legacy configurations that only set ingressClassName or
		// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
		// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
		// if none exists.
		log.V(util.DebugLevel).Info("could not retrieve IngressClass", "ingressclass", r.IngressClassName)
	}
	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// KongV1 KongConsumer - Reconciler
// -----------------------------------------------------------------------------

// KongV1KongConsumerReconciler reconciles KongConsumer resources
type KongV1KongConsumerReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient

	IngressClassName string
}

// SetupWithManager sets up the controller with the Manager.
func (r *KongV1KongConsumerReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("KongV1KongConsumer", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	// err = c.Watch(
	// 	&source.Kind{Type: &netv1.IngressClass{}},
	// 	handler.EnqueueRequestsFromMapFunc(r.listClassless),
	// 	predicate.NewPredicateFuncs(ctrlutils.IsDefaultIngressClass),
	// )
	// if err != nil {
	// 	return err
	// }
	preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName)
	return c.Watch(
		&source.Kind{Type: &kongv1.KongConsumer{}},
		&handler.EnqueueRequestForObject{},
		preds,
	)
}
// listClassless finds and reconciles all objects without ingress class information
func (r *KongV1KongConsumerReconciler) listClassless(obj client.Object) []reconcile.Request {
	resourceList := &kongv1.KongConsumerList{}
	if err := r.Client.List(context.Background(), resourceList); err != nil {
		r.Log.Error(err, "failed to list classless kongconsumers")
		return nil
	}
	var recs []reconcile.Request
	for _, resource := range resourceList.Items {
		if ctrlutils.IsIngressClassEmpty(&resource) {
			recs = append(recs, reconcile.Request{
				NamespacedName: types.NamespacedName{
					Namespace: resource.Namespace,
					Name:      resource.Name,
				},
			})
		}
	}
	return recs
}

//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongconsumers,verbs=get;list;watch
//+kubebuilder:rbac:groups=configuration.konghq.com,resources=kongconsumers/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *KongV1KongConsumerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("KongV1KongConsumer", req.NamespacedName)

	// get the relevant object
	obj := new(kongv1.KongConsumer)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "KongConsumer", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	class := new(netv1beta1.IngressClass)
	if err := r.Get(ctx, types.NamespacedName{Name: r.IngressClassName}, class); err != nil {
		// we log this without taking action to support legacy configurations that only set ingressClassName or
		// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
		// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
		// if none exists.
		log.V(util.DebugLevel).Info("could not retrieve IngressClass", "ingressclass", r.IngressClassName)
	}
	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// KongV1Beta1 TCPIngress - Reconciler
// -----------------------------------------------------------------------------

// KongV1Beta1TCPIngressReconciler reconciles TCPIngress resources
type KongV1Beta1TCPIngressReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient

	DataplaneAddressFinder *dataplane.AddressFinder
	StatusQueue            *status.Queue

	IngressClassName string
}

// SetupWithManager sets up the controller with the Manager.
func (r *KongV1Beta1TCPIngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("KongV1Beta1TCPIngress", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	// if configured, start the status updater controller
	if r.StatusQueue != nil {
		if err := c.Watch(
			&source.Channel{Source: r.StatusQueue.Subscribe(schema.GroupVersionKind{
				Group:   "configuration.konghq.com",
				Version: "v1beta1",
				Kind:    "TCPIngress",
			})},
			&handler.EnqueueRequestForObject{},
		); err != nil {
			return err
		}
	}
	// err = c.Watch(
	// 	&source.Kind{Type: &netv1.IngressClass{}},
	// 	handler.EnqueueRequestsFromMapFunc(r.listClassless),
	// 	predicate.NewPredicateFuncs(ctrlutils.IsDefaultIngressClass),
	// )
	// if err != nil {
	// 	return err
	// }
	preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName)
	return c.Watch(
		&source.Kind{Type: &kongv1beta1.TCPIngress{}},
		&handler.EnqueueRequestForObject{},
		preds,
	)
}
// listClassless finds and reconciles all objects without ingress class information
func (r *KongV1Beta1TCPIngressReconciler) listClassless(obj client.Object) []reconcile.Request {
	resourceList := &kongv1beta1.TCPIngressList{}
	if err := r.Client.List(context.Background(), resourceList); err != nil {
		r.Log.Error(err, "failed to list classless tcpingresses")
		return nil
	}
	var recs []reconcile.Request
	for _, resource := range resourceList.Items {
		if ctrlutils.IsIngressClassEmpty(&resource) {
			recs = append(recs, reconcile.Request{
				NamespacedName: types.NamespacedName{
					Namespace: resource.Namespace,
					Name:      resource.Name,
				},
			})
		}
	}
	return recs
}

//+kubebuilder:rbac:groups=configuration.konghq.com,resources=tcpingresses,verbs=get;list;watch
//+kubebuilder:rbac:groups=configuration.konghq.com,resources=tcpingresses/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *KongV1Beta1TCPIngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("KongV1Beta1TCPIngress", req.NamespacedName)

	// get the relevant object
	obj := new(kongv1beta1.TCPIngress)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "TCPIngress", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	class := new(netv1beta1.IngressClass)
	if err := r.Get(ctx, types.NamespacedName{Name: r.IngressClassName}, class); err != nil {
		// we log this without taking action to support legacy configurations that only set ingressClassName or
		// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
		// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
		// if none exists.
		log.V(util.DebugLevel).Info("could not retrieve IngressClass", "ingressclass", r.IngressClassName)
	}
	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}
	// if status updates are enabled report the status for the object
	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
		log.V(util.DebugLevel).Info("determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
		if !r.DataplaneClient.KubernetesObjectIsConfigured(obj) {
			log.V(util.DebugLevel).Error(fmt.Errorf("resource not yet configured in the data-plane"), "namespace", req.Namespace, "name", req.Name)
			return ctrl.Result{Requeue: true}, nil // requeue until the object has been properly configured
		}

		log.V(util.DebugLevel).Info("determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
		addrs, err := r.DataplaneAddressFinder.GetLoadBalancerAddresses()
		if err != nil {
			return ctrl.Result{}, err
		}

		log.V(util.DebugLevel).Info("found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
		if len(obj.Status.LoadBalancer.Ingress) != len(addrs) || !reflect.DeepEqual(obj.Status.LoadBalancer.Ingress, addrs) {
			obj.Status.LoadBalancer.Ingress = addrs
			return ctrl.Result{}, r.Status().Update(ctx, obj)
		} else {
			log.V(util.DebugLevel).Info("status update not needed", "namespace", req.Namespace, "name", req.Name)
		}
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// KongV1Beta1 UDPIngress - Reconciler
// -----------------------------------------------------------------------------

// KongV1Beta1UDPIngressReconciler reconciles UDPIngress resources
type KongV1Beta1UDPIngressReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient

	DataplaneAddressFinder *dataplane.AddressFinder
	StatusQueue            *status.Queue

	IngressClassName string
}

// SetupWithManager sets up the controller with the Manager.
func (r *KongV1Beta1UDPIngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("KongV1Beta1UDPIngress", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	// if configured, start the status updater controller
	if r.StatusQueue != nil {
		if err := c.Watch(
			&source.Channel{Source: r.StatusQueue.Subscribe(schema.GroupVersionKind{
				Group:   "configuration.konghq.com",
				Version: "v1beta1",
				Kind:    "UDPIngress",
			})},
			&handler.EnqueueRequestForObject{},
		); err != nil {
			return err
		}
	}
	// err = c.Watch(
	// 	&source.Kind{Type: &netv1.IngressClass{}},
	// 	handler.EnqueueRequestsFromMapFunc(r.listClassless),
	// 	predicate.NewPredicateFuncs(ctrlutils.IsDefaultIngressClass),
	// )
	// if err != nil {
	// 	return err
	// }
	preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName)
	return c.Watch(
		&source.Kind{Type: &kongv1beta1.UDPIngress{}},
		&handler.EnqueueRequestForObject{},
		preds,
	)
}
// listClassless finds and reconciles all objects without ingress class information
func (r *KongV1Beta1UDPIngressReconciler) listClassless(obj client.Object) []reconcile.Request {
	resourceList := &kongv1beta1.UDPIngressList{}
	if err := r.Client.List(context.Background(), resourceList); err != nil {
		r.Log.Error(err, "failed to list classless udpingresses")
		return nil
	}
	var recs []reconcile.Request
	for _, resource := range resourceList.Items {
		if ctrlutils.IsIngressClassEmpty(&resource) {
			recs = append(recs, reconcile.Request{
				NamespacedName: types.NamespacedName{
					Namespace: resource.Namespace,
					Name:      resource.Name,
				},
			})
		}
	}
	return recs
}

//+kubebuilder:rbac:groups=configuration.konghq.com,resources=udpingresses,verbs=get;list;watch
//+kubebuilder:rbac:groups=configuration.konghq.com,resources=udpingresses/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *KongV1Beta1UDPIngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("KongV1Beta1UDPIngress", req.NamespacedName)

	// get the relevant object
	obj := new(kongv1beta1.UDPIngress)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "UDPIngress", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	class := new(netv1beta1.IngressClass)
	if err := r.Get(ctx, types.NamespacedName{Name: r.IngressClassName}, class); err != nil {
		// we log this without taking action to support legacy configurations that only set ingressClassName or
		// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
		// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
		// if none exists.
		log.V(util.DebugLevel).Info("could not retrieve IngressClass", "ingressclass", r.IngressClassName)
	}
	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}
	// if status updates are enabled report the status for the object
	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
		log.V(util.DebugLevel).Info("determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
		if !r.DataplaneClient.KubernetesObjectIsConfigured(obj) {
			log.V(util.DebugLevel).Error(fmt.Errorf("resource not yet configured in the data-plane"), "namespace", req.Namespace, "name", req.Name)
			return ctrl.Result{Requeue: true}, nil // requeue until the object has been properly configured
		}

		log.V(util.DebugLevel).Info("determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
		addrs, err := r.DataplaneAddressFinder.GetLoadBalancerAddresses()
		if err != nil {
			return ctrl.Result{}, err
		}

		log.V(util.DebugLevel).Info("found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
		if len(obj.Status.LoadBalancer.Ingress) != len(addrs) || !reflect.DeepEqual(obj.Status.LoadBalancer.Ingress, addrs) {
			obj.Status.LoadBalancer.Ingress = addrs
			return ctrl.Result{}, r.Status().Update(ctx, obj)
		} else {
			log.V(util.DebugLevel).Info("status update not needed", "namespace", req.Namespace, "name", req.Name)
		}
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// Knativev1alpha1 Ingress - Reconciler
// -----------------------------------------------------------------------------

// Knativev1alpha1IngressReconciler reconciles Ingress resources
type Knativev1alpha1IngressReconciler struct {
	client.Client

	Log             logr.Logger
	Scheme          *runtime.Scheme
	DataplaneClient *dataplane.KongClient

	DataplaneAddressFinder *dataplane.AddressFinder
	StatusQueue            *status.Queue

	IngressClassName string
}

// SetupWithManager sets up the controller with the Manager.
func (r *Knativev1alpha1IngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
	c, err := controller.New("Knativev1alpha1Ingress", mgr, controller.Options{
		Reconciler: r,
		Log:        r.Log,
	})
	if err != nil {
		return err
	}
	// if configured, start the status updater controller
	if r.StatusQueue != nil {
		if err := c.Watch(
			&source.Channel{Source: r.StatusQueue.Subscribe(schema.GroupVersionKind{
				Group:   "networking.internal.knative.dev",
				Version: "v1alpha1",
				Kind:    "Ingress",
			})},
			&handler.EnqueueRequestForObject{},
		); err != nil {
			return err
		}
	}
	// err = c.Watch(
	// 	&source.Kind{Type: &netv1.IngressClass{}},
	// 	handler.EnqueueRequestsFromMapFunc(r.listClassless),
	// 	predicate.NewPredicateFuncs(ctrlutils.IsDefaultIngressClass),
	// )
	// if err != nil {
	// 	return err
	// }
	preds := ctrlutils.GeneratePredicateFuncsForIngressClassFilter(r.IngressClassName)
	return c.Watch(
		&source.Kind{Type: &knativev1alpha1.Ingress{}},
		&handler.EnqueueRequestForObject{},
		preds,
	)
}
// listClassless finds and reconciles all objects without ingress class information
func (r *Knativev1alpha1IngressReconciler) listClassless(obj client.Object) []reconcile.Request {
	resourceList := &knativev1alpha1.IngressList{}
	if err := r.Client.List(context.Background(), resourceList); err != nil {
		r.Log.Error(err, "failed to list classless ingresses")
		return nil
	}
	var recs []reconcile.Request
	for _, resource := range resourceList.Items {
		if ctrlutils.IsIngressClassEmpty(&resource) {
			recs = append(recs, reconcile.Request{
				NamespacedName: types.NamespacedName{
					Namespace: resource.Namespace,
					Name:      resource.Name,
				},
			})
		}
	}
	return recs
}

//+kubebuilder:rbac:groups=networking.internal.knative.dev,resources=ingresses,verbs=get;list;watch
//+kubebuilder:rbac:groups=networking.internal.knative.dev,resources=ingresses/status,verbs=get;update;patch

// Reconcile processes the watched objects
func (r *Knativev1alpha1IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("Knativev1alpha1Ingress", req.NamespacedName)

	// get the relevant object
	obj := new(knativev1alpha1.Ingress)
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		if errors.IsNotFound(err) {
			obj.Namespace = req.Namespace
			obj.Name = req.Name
			return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
		}
		return ctrl.Result{}, err
	}
	log.V(util.DebugLevel).Info("reconciling resource", "namespace", req.Namespace, "name", req.Name)

	// clean the object up if it's being deleted
	if !obj.DeletionTimestamp.IsZero() && time.Now().After(obj.DeletionTimestamp.Time) {
		log.V(util.DebugLevel).Info("resource is being deleted, its configuration will be removed", "type", "Ingress", "namespace", req.Namespace, "name", req.Name)
		objectExistsInCache, err := r.DataplaneClient.ObjectExists(obj)
		if err != nil {
			return ctrl.Result{}, err
		}
		if objectExistsInCache {
			if err := r.DataplaneClient.DeleteObject(obj); err != nil {
				return ctrl.Result{}, err
			}
			return ctrl.Result{Requeue: true}, nil // wait until the object is no longer present in the cache
		}
		return ctrl.Result{}, nil
	}

	class := new(netv1beta1.IngressClass)
	if err := r.Get(ctx, types.NamespacedName{Name: r.IngressClassName}, class); err != nil {
		// we log this without taking action to support legacy configurations that only set ingressClassName or
		// used the class annotation and did not create a corresponding IngressClass. We only need this to determine
		// if the IngressClass is default or to configure default settings, and can assume no/no additional defaults
		// if none exists.
		log.V(util.DebugLevel).Info("could not retrieve IngressClass", "ingressclass", r.IngressClassName)
	}
	// if the object is not configured with our ingress.class, then we need to ensure it's removed from the cache
	if !ctrlutils.MatchesIngressClass(obj, r.IngressClassName, ctrlutils.IsDefaultIngressClass(class)) {
		log.V(util.DebugLevel).Info("object missing ingress class, ensuring it's removed from configuration", "namespace", req.Namespace, "name", req.Name)
		return ctrl.Result{}, r.DataplaneClient.DeleteObject(obj)
	}

	// update the kong Admin API with the changes
	if err := r.DataplaneClient.UpdateObject(obj); err != nil {
		return ctrl.Result{}, err
	}
	// if status updates are enabled report the status for the object
	if r.DataplaneClient.AreKubernetesObjectReportsEnabled() {
		log.V(util.DebugLevel).Info("determining whether data-plane configuration has succeeded", "namespace", req.Namespace, "name", req.Name)
		if !r.DataplaneClient.KubernetesObjectIsConfigured(obj) {
			log.V(util.DebugLevel).Error(fmt.Errorf("resource not yet configured in the data-plane"), "namespace", req.Namespace, "name", req.Name)
			return ctrl.Result{Requeue: true}, nil // requeue until the object has been properly configured
		}

		log.V(util.DebugLevel).Info("determining gateway addresses for object status updates", "namespace", req.Namespace, "name", req.Name)
		addrs, err := r.DataplaneAddressFinder.GetLoadBalancerAddresses()
		if err != nil {
			return ctrl.Result{}, err
		}

		log.V(util.DebugLevel).Info("found addresses for data-plane updating object status", "namespace", req.Namespace, "name", req.Name)
		var knativeLBIngress []knativev1alpha1.LoadBalancerIngressStatus
		for _, addr := range addrs {
			knativeIng := knativev1alpha1.LoadBalancerIngressStatus{
				IP:     addr.IP,
				Domain: addr.Hostname,
			}
			knativeLBIngress = append(knativeLBIngress, knativeIng)
		}
		ingressCondSet := knativeApis.NewLivingConditionSet()
		if obj.Status.PublicLoadBalancer == nil || len(obj.Status.PublicLoadBalancer.Ingress) != len(addrs) || !reflect.DeepEqual(obj.Status.PublicLoadBalancer.Ingress, knativeLBIngress) {
			obj.Status.MarkLoadBalancerReady(knativeLBIngress, knativeLBIngress)
			ingressCondSet.Manage(&obj.Status).MarkTrue(knativev1alpha1.IngressConditionReady)
			ingressCondSet.Manage(&obj.Status).MarkTrue(knativev1alpha1.IngressConditionNetworkConfigured)
			obj.Status.ObservedGeneration = obj.Generation
			return ctrl.Result{}, r.Status().Update(ctx, obj)
		} else {
			log.V(util.DebugLevel).Info("status update not needed", "namespace", req.Namespace, "name", req.Name)
		}
	}

	return ctrl.Result{}, nil
}

// -----------------------------------------------------------------------------
// API Group "" resource nodes
// -----------------------------------------------------------------------------

//+kubebuilder:rbac:groups="",resources=nodes,verbs=list;watch

// -----------------------------------------------------------------------------
// API Group "" resource pods
// -----------------------------------------------------------------------------

//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch

// -----------------------------------------------------------------------------
// API Group "" resource events
// -----------------------------------------------------------------------------

//+kubebuilder:rbac:groups="",resources=events,verbs=create;patch
