package transfer

import (
	"context"
	"fmt"
	"strings"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/types"

	"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/manager"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
	"sigs.k8s.io/controller-runtime/pkg/source"

	cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
)

func indexKeyFunc(namespace, name string) string {
	return fmt.Sprintf("%s/%s", namespace, name)
}

func indexAndWatch(mgr manager.Manager, ctrl controller.Controller, obj client.Object, field string) error {
	// setup index for the type
	if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &cdiv1.ObjectTransfer{}, field, func(obj client.Object) []string {
		var result []string
		ot := obj.(*cdiv1.ObjectTransfer)

		if strings.ToLower(ot.Spec.Source.Kind) == field {
			result = []string{
				indexKeyFunc(ot.Spec.Source.Namespace, ot.Spec.Source.Name),
				indexKeyFunc(getTransferTargetNamespace(ot), getTransferTargetName(ot)),
			}
		}

		return result
	}); err != nil {
		return err
	}

	if err := ctrl.Watch(source.Kind(mgr.GetCache(), obj, handler.EnqueueRequestsFromMapFunc(
		func(ctx context.Context, obj client.Object) []reconcile.Request {
			value := indexKeyFunc(obj.GetNamespace(), obj.GetName())
			return indexLookup(ctx, mgr.GetClient(), field, value)
		},
	))); err != nil {
		return err
	}

	return nil
}

func indexLookup(ctx context.Context, c client.Client, field, value string) []reconcile.Request {
	var result []reconcile.Request
	var objs cdiv1.ObjectTransferList

	if err := c.List(ctx, &objs, &client.ListOptions{
		FieldSelector: fields.OneTermEqualSelector(field, value),
	}); err != nil {
		return nil
	}

	for _, obj := range objs.Items {
		result = append(result, reconcile.Request{
			NamespacedName: types.NamespacedName{
				Name: obj.Name,
			},
		})
	}

	return result
}

func watchObjectTransfers(mgr manager.Manager, ctrl controller.Controller) error {
	return ctrl.Watch(source.Kind(mgr.GetCache(), &cdiv1.ObjectTransfer{}, handler.TypedEnqueueRequestsFromMapFunc[*cdiv1.ObjectTransfer](
		func(_ context.Context, ot *cdiv1.ObjectTransfer) []reconcile.Request {
			result := []reconcile.Request{
				{
					NamespacedName: types.NamespacedName{
						Name: ot.Name,
					},
				},
			}

			if ot.Spec.ParentName != nil {
				result = append(result, reconcile.Request{
					NamespacedName: types.NamespacedName{
						Name: *ot.Spec.ParentName,
					},
				})
			}

			return result
		},
	)))
}

func watchPVs(mgr manager.Manager, ctrl controller.Controller) error {
	return ctrl.Watch(source.Kind(mgr.GetCache(), &corev1.PersistentVolume{}, handler.TypedEnqueueRequestsFromMapFunc[*corev1.PersistentVolume](
		func(ctx context.Context, pv *corev1.PersistentVolume) []reconcile.Request {
			if pv.Spec.ClaimRef == nil {
				return nil
			}
			value := indexKeyFunc(pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name)
			return indexLookup(ctx, mgr.GetClient(), "persistentvolumeclaim", value)
		},
	)))
}

func addObjectTransferControllerWatches(mgr manager.Manager, ctrl controller.Controller) error {
	if err := watchObjectTransfers(mgr, ctrl); err != nil {
		return err
	}

	if err := indexAndWatch(mgr, ctrl, &cdiv1.DataVolume{}, "datavolume"); err != nil {
		return err
	}

	if err := indexAndWatch(mgr, ctrl, &corev1.PersistentVolumeClaim{}, "persistentvolumeclaim"); err != nil {
		return err
	}

	if err := watchPVs(mgr, ctrl); err != nil {
		return err
	}

	return nil
}
