package network

import (
	"context"
	"reflect"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	apitypes "k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/util/workqueue"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/shared-provider/pkg/types"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
)

func (r *Reconciler) handleCreateNamespaceEvent(e event.CreateEvent, q workqueue.RateLimitingInterface) {
	// nolint: forcetypeassert // obj will always be namespace
	ns := e.Object.(*corev1.Namespace)
	r.addNetworkFromNamespace(ns, q)
}

func (r *Reconciler) handleDeleteNamespaceEvent(e event.DeleteEvent, q workqueue.RateLimitingInterface) {
	// nolint: forcetypeassert // obj will always be namespace
	ns := e.Object.(*corev1.Namespace)
	r.addNetworkFromNamespace(ns, q)
}

func (r *Reconciler) handleUpdateNamespaceEvent(e event.UpdateEvent, q workqueue.RateLimitingInterface) {
	// nolint: forcetypeassert // obj will always be namespace
	oldNs := e.ObjectOld.(*corev1.Namespace)
	// nolint: forcetypeassert // obj will always be namespace
	newNs := e.ObjectNew.(*corev1.Namespace)

	if reflect.DeepEqual(oldNs.Labels, newNs.Labels) {
		return
	}

	r.addNetworkFromNamespace(oldNs, q)
	r.addNetworkFromNamespace(newNs, q)
}

func (r *Reconciler) addNetworkFromNamespace(ns *corev1.Namespace, q workqueue.RateLimitingInterface) {
	ownedBy, netNs, name, _ := labels.GetOwnerFromLabels(ns.Labels)
	if ownedBy != labels.GVKString(&types.GVKNetwork) {
		return
	}
	key := apitypes.NamespacedName{
		Namespace: netNs,
		Name:      name,
	}

	r.Logger.Info("enqueue network because of namespace", "network", key, "namespace", ns.Name)

	q.Add(ctrl.Request{
		NamespacedName: key,
	})
}

func (r *Reconciler) handleCreateClusterEvent(e event.CreateEvent, q workqueue.RateLimitingInterface) {
	// nolint: forcetypeassert // obj will always be cluster
	cluster := e.Object.(*cloudv1.Cluster)

	key := apitypes.NamespacedName{
		Namespace: cluster.Namespace,
		Name:      cluster.Spec.NetworkName,
	}

	r.Logger.Info("enqueue network because of cluster creation", "network", key, "cluster", cluster.Name)

	q.Add(ctrl.Request{
		NamespacedName: key,
	})
}

func (r *Reconciler) handleUpdateClusterEvent(e event.UpdateEvent, q workqueue.RateLimitingInterface) {
	// nolint: forcetypeassert // obj will always be cluster
	oldCluster := e.ObjectOld.(*cloudv1.Cluster)
	// nolint: forcetypeassert // obj will always be cluster
	newCluster := e.ObjectNew.(*cloudv1.Cluster)

	// check whether labels are changed
	if reflect.DeepEqual(oldCluster.Labels, newCluster.Labels) {
		return
	}

	key := apitypes.NamespacedName{
		Namespace: newCluster.Namespace,
		Name:      newCluster.Spec.NetworkName,
	}

	r.Logger.Info("enqueue network because of cluster updation", "network", key, "cluster", newCluster.Name)

	// NetworkName cannot be changed, so only add new one into queue
	q.Add(ctrl.Request{
		NamespacedName: key,
	})
}

func (r *Reconciler) handleDeleteClusterEvent(e event.DeleteEvent, q workqueue.RateLimitingInterface) {
	// nolint: forcetypeassert // obj will always be cluster
	cluster := e.Object.(*cloudv1.Cluster)

	key := apitypes.NamespacedName{
		Namespace: cluster.Namespace,
		Name:      cluster.Spec.NetworkName,
	}

	r.Logger.Info("enqueue network because of cluster deletion", "network", key, "cluster", cluster.Name)

	q.Add(ctrl.Request{
		NamespacedName: key,
	})
}

func (r *Reconciler) handleCreateInNamespaceResource(e event.CreateEvent, q workqueue.RateLimitingInterface) {
	r.Logger.Info("handle in namespace resource create", "object", client.ObjectKeyFromObject(e.Object))
	reqs := r.handleInNamespaceResource(e.Object)
	for _, req := range reqs {
		q.Add(req)
	}
}

func (r *Reconciler) handleUpdateInNamespaceResource(e event.UpdateEvent, q workqueue.RateLimitingInterface) {
	oldReqs := r.handleInNamespaceResource(e.ObjectOld)
	for _, req := range oldReqs {
		q.Add(req)
	}
	newReqs := r.handleInNamespaceResource(e.ObjectNew)
	for _, req := range newReqs {
		q.Add(req)
	}
}

func (r *Reconciler) handleDeleteInNamespaceResource(e event.DeleteEvent, q workqueue.RateLimitingInterface) {
	r.Logger.Info("handle in namespace resource delete", "object", client.ObjectKeyFromObject(e.Object))
	reqs := r.handleInNamespaceResource(e.Object)
	for _, req := range reqs {
		q.Add(req)
	}
}

func (r *Reconciler) handleInNamespaceResource(obj client.Object) []ctrl.Request {
	// NOTE: gvk is missing in event
	tk := &TypeAndKey{
		Type: reflect.TypeOf(obj),
		Name: obj.GetName(),
	}

	r.Logger.Info("handle in namespace resource", "type", tk.Type, "name", tk.Name)

	if found := r.tracker.Has(tk); !found {
		return nil
	}

	ns := &corev1.Namespace{}
	key := client.ObjectKey{
		Name: obj.GetNamespace(),
	}

	if err := r.Cache.Get(context.Background(), key, ns); err != nil {
		// can't do anything, just return
		return nil
	}

	ownedBy, netNs, name, _ := labels.GetOwnerFromLabels(ns.Labels)
	if ownedBy != labels.GVKString(&types.GVKNetwork) {
		return nil
	}

	netKey := apitypes.NamespacedName{
		Namespace: netNs,
		Name:      name,
	}

	r.Logger.Info("enqueue network because of in namespace resource", "network", netKey, "type", tk.Type, "name", tk.Name)

	return []ctrl.Request{
		{
			NamespacedName: netKey,
		},
	}
}

func (r *Reconciler) handleCreateConfigMapEvent(e event.CreateEvent, q workqueue.RateLimitingInterface) {
	// nolint: forcetypeassert // obj will always be configmap
	cm := e.Object.(*corev1.ConfigMap)

	if cm.Namespace != r.AddonConfigMapNamespace ||
		cm.Name != r.AddonConfigMapName {
		return
	}
	if err := r.tmpl.Flush(cm.Data); err != nil {
		r.Logger.Error(err, "can't flush configmap: %w", err)
		return
	}

	r.enqueueAllNetworks(q)
}

func (r *Reconciler) handleUpdateConfigMapEvent(e event.UpdateEvent, q workqueue.RateLimitingInterface) {
	// nolint: forcetypeassert // obj will always be configmap
	oldCm := e.ObjectOld.(*corev1.ConfigMap)
	// nolint: forcetypeassert // obj will always be configmap
	newCm := e.ObjectNew.(*corev1.ConfigMap)

	if newCm.Namespace != r.AddonConfigMapNamespace ||
		newCm.Name != r.AddonConfigMapName {
		return
	}

	if reflect.DeepEqual(oldCm.Data, newCm.Data) {
		return
	}

	if err := r.tmpl.Flush(newCm.Data); err != nil {
		r.Logger.Error(err, "can't flush configmap: %w", err)
		return
	}
	r.enqueueAllNetworks(q)
}

func (r *Reconciler) enqueueAllNetworks(q workqueue.RateLimitingInterface) {
	r.Logger.Info("enqueue all networks which are triggered by configmap changes")

	selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
		MatchLabels: map[string]string{
			labels.LabelKeyKubernetesCluster: r.KubeClusterName,
		},
		MatchExpressions: []metav1.LabelSelectorRequirement{{
			Key:      cloudv1.ProviderTypeLabel,
			Operator: metav1.LabelSelectorOpIn,
			Values:   []string{types.ProviderTypeGCPSharedTier, types.ProviderTypeAWSSharedTier},
		}},
	})
	if err != nil {
		r.Logger.Error(err, "can't get LabelSelectorAsSelector")
		return
	}

	nl := cloudv1.NetworkList{}
	if err := r.InfraCache.List(context.TODO(), &nl, client.MatchingLabelsSelector{Selector: selector}); err != nil {
		r.Logger.Error(err, "can't list all networks and enqueue")
		return
	}

	for i := range nl.Items {
		net := &nl.Items[i]
		q.Add(ctrl.Request{
			NamespacedName: apitypes.NamespacedName{
				Namespace: net.Namespace,
				Name:      net.Name,
			},
		})
	}
}
