package dispatcher

import (
	"context"
	"reflect"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/shared-provider/pkg/types"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
	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"
)

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

	if cm.Namespace != n.KubeClusterConfigMapNamespace &&
		cm.Name != n.KubeClusterConfigMapName {
		return
	}

	if err := n.enqueueAllUnscheduledNetworks(q); err != nil {
		n.Logger.Error(err, "can't enqueue all unscheduled networks")
	}
}

func (n *network) 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 != n.KubeClusterConfigMapNamespace &&
		newCm.Name != n.KubeClusterConfigMapName {
		return
	}

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

	if err := n.enqueueAllUnscheduledNetworks(q); err != nil {
		n.Logger.Error(err, "can't enqueue all unscheduled networks")
	}
}

func (n *network) enqueueAllUnscheduledNetworks(q workqueue.RateLimitingInterface) error {
	selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
		MatchExpressions: []metav1.LabelSelectorRequirement{{
			Key:      cloudv1.ProviderTypeLabel,
			Operator: metav1.LabelSelectorOpIn,
			Values:   []string{types.ProviderTypeGCPSharedTier, types.ProviderTypeAWSSharedTier},
		}},
	})
	if err != nil {
		return err
	}

	nl := cloudv1.NetworkList{}
	if err := n.InfraCache.List(context.TODO(), &nl, client.MatchingLabelsSelector{Selector: selector}); err != nil {
		return err
	}

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

	return nil
}

func (d *cluster) handleNetworkLabelsUpdation(e event.UpdateEvent, q workqueue.RateLimitingInterface) {
	// nolint: forcetypeassert // obj will always be network
	oldNet := e.ObjectOld.(*cloudv1.Network)
	newNet := e.ObjectNew.(*cloudv1.Network)

	if reflect.DeepEqual(oldNet.Labels, newNet.Labels) {
		return
	}

	cl := cloudv1.ClusterList{}
	if err := d.InfraCache.List(context.TODO(), &cl, client.InNamespace(newNet.Namespace)); err != nil {
		d.Logger.Error(err, "can't list clusters")
		return
	}

	for _, c := range cl.Items {
		if c.Spec.NetworkName == newNet.Name {
			q.Add(ctrl.Request{
				NamespacedName: apitypes.NamespacedName{
					Namespace: c.Namespace,
					Name:      c.Name,
				},
			})
		}
	}
}
