package eventhandlers

import (
	"context"
	"github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1"
	"github.com/aws/aws-application-networking-k8s/pkg/k8s"
	"github.com/aws/aws-application-networking-k8s/pkg/model/core"
	"github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog"
	corev1 "k8s.io/api/core/v1"
	discoveryv1 "k8s.io/api/discovery/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/handler"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

type serviceEventHandler struct {
	log    gwlog.Logger
	client client.Client
	mapper *resourceMapper
}

func NewServiceEventHandler(log gwlog.Logger, client client.Client) *serviceEventHandler {
	return &serviceEventHandler{log: log, client: client,
		mapper: &resourceMapper{log: log, client: client}}
}

func (h *serviceEventHandler) MapToRoute(routeType core.RouteType) handler.EventHandler {
	return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
		return h.mapToRoute(ctx, obj, routeType)
	})
}

func (h *serviceEventHandler) MapToServiceExport() handler.EventHandler {
	return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
		return h.mapToServiceExport(ctx, obj)
	})
}

func (h *serviceEventHandler) mapToServiceExport(ctx context.Context, obj client.Object) []reconcile.Request {
	var requests []reconcile.Request

	// Handle TargetGroupPolicy changes more directly for ServiceExport
	if tgp, ok := obj.(*v1alpha1.TargetGroupPolicy); ok {
		requests = h.mapTargetGroupPolicyToServiceExport(ctx, tgp)
		if len(requests) > 0 {
			return requests
		}
	}

	svc := h.mapToService(ctx, obj)
	svcExport := h.mapper.ServiceToServiceExport(ctx, svc)
	if svcExport != nil {
		requests = append(requests, reconcile.Request{
			NamespacedName: k8s.NamespacedName(svcExport),
		})
		h.log.Infow(ctx, "Service impacting resource change triggered ServiceExport update",
			"serviceName", svc.Namespace+"/"+svc.Name)
	}
	return requests
}

func (h *serviceEventHandler) mapToService(ctx context.Context, obj client.Object) *corev1.Service {
	switch typed := obj.(type) {
	case *corev1.Service:
		return typed
	case *v1alpha1.TargetGroupPolicy:
		return h.mapper.TargetGroupPolicyToService(ctx, typed)
	case *discoveryv1.EndpointSlice:
		return h.mapper.EndpointSliceToService(ctx, typed)
	}
	return nil
}

func (h *serviceEventHandler) mapTargetGroupPolicyToServiceExport(ctx context.Context, tgp *v1alpha1.TargetGroupPolicy) []reconcile.Request {
	var requests []reconcile.Request

	targetRef := tgp.GetTargetRef()
	if targetRef == nil {
		return requests
	}

	// Check if the policy directly targets a ServiceExport
	if targetRef.Kind == "ServiceExport" && (targetRef.Group == "" || targetRef.Group == v1alpha1.GroupName) {
		svcExport := &v1alpha1.ServiceExport{}
		key := client.ObjectKey{
			Name:      string(targetRef.Name),
			Namespace: tgp.Namespace,
		}
		if err := h.client.Get(ctx, key, svcExport); err == nil {
			requests = append(requests, reconcile.Request{
				NamespacedName: k8s.NamespacedName(svcExport),
			})
			h.log.Infow(ctx, "TargetGroupPolicy change triggered ServiceExport update",
				"policyName", tgp.Namespace+"/"+tgp.Name,
				"serviceExportName", svcExport.Namespace+"/"+svcExport.Name)
		}
		return requests
	}

	// Check if the policy targets a Service that has a corresponding ServiceExport
	if targetRef.Kind == "Service" && (targetRef.Group == "" || targetRef.Group == corev1.GroupName) {
		svcExport := &v1alpha1.ServiceExport{}
		key := client.ObjectKey{
			Name:      string(targetRef.Name),
			Namespace: tgp.Namespace,
		}
		if err := h.client.Get(ctx, key, svcExport); err == nil {
			requests = append(requests, reconcile.Request{
				NamespacedName: k8s.NamespacedName(svcExport),
			})
			h.log.Infow(ctx, "TargetGroupPolicy change for Service triggered ServiceExport update",
				"policyName", tgp.Namespace+"/"+tgp.Name,
				"serviceName", string(targetRef.Name),
				"serviceExportName", svcExport.Namespace+"/"+svcExport.Name)
		}
	}

	return requests
}

func (h *serviceEventHandler) mapToRoute(ctx context.Context, obj client.Object, routeType core.RouteType) []reconcile.Request {
	svc := h.mapToService(ctx, obj)
	routes := h.mapper.ServiceToRoutes(ctx, svc, routeType)

	var requests []reconcile.Request
	for _, route := range routes {
		routeName := k8s.NamespacedName(route.K8sObject())
		requests = append(requests, reconcile.Request{NamespacedName: routeName})
		h.log.Infow(ctx, "Service impacting resource change triggered Route update",
			"serviceName", svc.Namespace+"/"+svc.Name, "routeName", routeName, "routeType", routeType)
	}
	return requests
}
