package agent

import (
	"context"
	"log/slog"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/api"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/module/remote_development/agent/k8s"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/logz"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/cache"
)

func secretEventHandler(log *slog.Logger, ctx context.Context, tracker *imagePullSecretTracker, k8sClient k8s.Client, agentKey api.AgentKey) cache.ResourceEventHandlerFuncs {
	// the imagePullSecretTracker is populated during a full reconciliation where the secret gets created/updated.
	// the informer gets started before the reconciliation happens.
	// any events that get handled here before the tracker is populated will not affect us
	// since the secret will get created/updated as part of the full reconciliation.
	// for partial reconciliation, the tracker would already be populated.
	return cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj any) {
			// No-op because the initial clone of the image pull secret will be done during workspace creation
			u := obj.(*unstructured.Unstructured)
			log.Debug("Received add event for secret event informer", logz.K8sObjectNamespace(u.GetNamespace()), logz.K8sObjectName(u.GetName()))
		},
		UpdateFunc: func(oldObj, newObj any) {
			newU := newObj.(*unstructured.Unstructured)
			oldU := oldObj.(*unstructured.Unstructured)

			if _, exist := newU.GetLabels()[api.AgentIDKey]; exist {
				// This is a managed by an agent ignore update
				return
			}

			isK8sDefinedSecretType, err := isK8sDefinedSecretType(newU)
			if err != nil {
				log.Error(
					"Unable to determine secret type",
					logz.Error(err),
					logz.K8sObjectNamespace(newU.GetNamespace()),
					logz.K8sObjectName(newU.GetName()),
				)
				return
			}

			if !isK8sDefinedSecretType {
				return
			}

			if equality.Semantic.DeepEqual(oldU, newU) {
				return
			}

			log.Debug("Received update event for secret event informer", logz.K8sObjectNamespace(newU.GetNamespace()), logz.K8sObjectName(newU.GetName()))
			var upstreamSecret *corev1.Secret
			err = runtime.DefaultUnstructuredConverter.FromUnstructured(newU.Object, &upstreamSecret)
			if err != nil {
				log.Error(
					"Unable to convert secret type to unstructured type",
					logz.Error(err),
					logz.K8sObjectNamespace(newU.GetNamespace()),
					logz.K8sObjectName(newU.GetName()),
				)
				return
			}

			namespacesToUpdate := tracker.get(newU.GetNamespace(), newU.GetName())
			for _, namespace := range namespacesToUpdate {
				log.Debug("Updating workspace image pull secret", logz.K8sObjectNamespace(namespace), logz.K8sObjectName(newU.GetName()))
				secret := generateWorkspaceImagePullSecret(agentKey, namespace, upstreamSecret)
				_, err = k8sClient.UpdateOrCreate(ctx, secretGVR, namespace, secret, true)
				if err != nil {
					log.Error("Error updating secret",
						logz.K8sObjectNamespace(namespace),
						logz.K8sObjectName(secret.Name),
						logz.Error(err),
					)
				}
			}
		},

		DeleteFunc: func(obj any) {
			var u *unstructured.Unstructured

			switch obj := obj.(type) {
			case cache.DeletedFinalStateUnknown:
				if unstrObj, ok := obj.Obj.(*unstructured.Unstructured); ok {
					u = unstrObj
				} else {
					log.Error("unable to decode DeletedFinalStateUnknown secret object to *unstructured.Unstructured")
					return
				}
			case *unstructured.Unstructured:
				u = obj

			default:
				log.Debug("Received unknown delete event for secret event informer")
				return
			}

			if _, exist := u.GetLabels()[api.AgentIDKey]; exist {
				// This is a managed by an agent ignore update
				return
			}

			isK8sDefinedSecretType, err := isK8sDefinedSecretType(u)
			if err != nil {
				log.Error(
					"Unable to determine secret type",
					logz.Error(err),
					logz.K8sObjectNamespace(u.GetNamespace()),
					logz.K8sObjectName(u.GetName()),
				)
				return
			}

			if !isK8sDefinedSecretType {
				return
			}

			log.Debug("Received delete event for secret event informer", logz.K8sObjectNamespace(u.GetNamespace()), logz.K8sObjectName(u.GetName()))
			namespacesToUpdate := tracker.get(u.GetNamespace(), u.GetName())
			for _, namespace := range namespacesToUpdate {
				log.Debug("Deleting workspace image pull secret", logz.WorkspaceNamespace(namespace), logz.K8sObjectName(u.GetName()))
				tracker.delete(u.GetNamespace(), u.GetName(), namespace)
				err := k8sClient.Delete(ctx, secretGVR, namespace, u.GetName())
				if err != nil && !k8serrors.IsNotFound(err) {
					log.Error("Error deleting secret",
						logz.K8sObjectNamespace(namespace),
						logz.K8sObjectName(u.GetName()),
						logz.Error(err),
					)
				}
			}
		},
	}
}

func deploymentEventHandler(log *slog.Logger) cache.ResourceEventHandlerFuncs {
	return cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj any) {
			// Handler logic
			u := obj.(*unstructured.Unstructured)
			log.Debug("Received add event for deployment event informer", logz.WorkspaceNamespace(u.GetNamespace()), logz.WorkspaceName(u.GetName()))
		},
		UpdateFunc: func(oldObj, newObj any) {
			// Handler logic
			newU := newObj.(*unstructured.Unstructured)
			oldU := oldObj.(*unstructured.Unstructured)
			if equality.Semantic.DeepEqual(oldU, newU) {
				return
			}
			log.Debug("Received update event for deployment event informer", logz.WorkspaceNamespace(newU.GetNamespace()), logz.WorkspaceName(newU.GetName()))
		},
		DeleteFunc: func(obj any) {
			// Handler logic
			var u *unstructured.Unstructured
			switch obj := obj.(type) {
			case *unstructured.Unstructured:
				u = obj
				log.Debug("Received delete event for deployment event informer", logz.WorkspaceNamespace(u.GetNamespace()), logz.WorkspaceName(u.GetName()))
			case cache.DeletedFinalStateUnknown:
				if unstrObj, ok := obj.Obj.(*unstructured.Unstructured); ok {
					u = unstrObj
				} else {
					log.Error("unable to decode DeletedFinalStateUnknown deployment object to *unstructured.Unstructured")
					return
				}
				log.Debug("Received missed delete event for deployment event informer", logz.WorkspaceNamespace(u.GetNamespace()), logz.WorkspaceName(u.GetName()))
			default:
				log.Debug("Received unknown delete event for deployment event informer")
			}
		},
	}
}
