package networkpolicy

import (
	"context"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	netv1 "k8s.io/api/networking/v1"
	apiequality "k8s.io/apimachinery/pkg/api/equality"
	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"

	"github.com/tidbcloud/shared-provider/pkg/types"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
)

func isOwnedByNetwork(ns *corev1.Namespace) bool {
	// nolint: dogsled // TODO: change to use a struct as owner
	ownedBy, _, _, _ := labels.GetOwnerFromLabels(ns.Labels)

	return ownedBy == labels.GVKString(&types.GVKNetwork)
}

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

	for _, name := range AllPolicyNames {
		q.Add(ctrl.Request{
			NamespacedName: apitypes.NamespacedName{
				Namespace: ns.Name,
				Name:      name,
			},
		})
	}
}

func isDefaultKubernetesEndpoints(logger logr.Logger, ep *corev1.Endpoints) bool {
	if ep.Name != types.KubernetesEndpointName || ep.Namespace != corev1.NamespaceDefault {
		// unexpected
		logger.Info("unexpected endpoints creation event", "endpoints", ep)

		return false
	}

	return true
}

func (r *Reconciler) handleCreateEndpointsEvent(e event.CreateEvent, q workqueue.RateLimitingInterface) {
	r.Logger.Info("handle kubernetes endpoints creation", "count", r.endpointsSyncCount)
	// nolint: forcetypeassert // obj will always be endpoints
	ep := e.Object.(*corev1.Endpoints)

	if !isDefaultKubernetesEndpoints(r.Logger, ep) {
		return
	}

	err := r.addAllDependonKubernetesPolicies(q)
	if err != nil {
		r.Logger.Error(err, "failed to add policies")
	}
}

// Normally never happened.
func (r *Reconciler) handleDeleteEndpointsEvent(e event.DeleteEvent, q workqueue.RateLimitingInterface) {
	r.Logger.Info("handle kubernetes endpoints deletion", "count", r.endpointsSyncCount)
	// nolint: forcetypeassert // obj will always be endpoints
	ep := e.Object.(*corev1.Endpoints)

	if !isDefaultKubernetesEndpoints(r.Logger, ep) {
		return
	}

	err := r.addAllDependonKubernetesPolicies(q)
	if err != nil {
		r.Logger.Error(err, "failed to add policies")
	}
}

func (r *Reconciler) handleUpdateEndpointsEvent(e event.UpdateEvent, q workqueue.RateLimitingInterface) {
	r.Logger.Info("handle kubernetes endpoints updation", "count", r.endpointsSyncCount)
	// nolint: forcetypeassert // obj will always be endpoints
	oldEp := e.ObjectOld.(*corev1.Endpoints)
	// nolint: forcetypeassert // obj will always be endpoints
	newEp := e.ObjectNew.(*corev1.Endpoints)

	if oldEp.ResourceVersion == newEp.ResourceVersion {
		return
	}

	if apiequality.Semantic.DeepEqual(oldEp.Subsets, newEp.Subsets) {
		return
	}

	if !isDefaultKubernetesEndpoints(r.Logger, oldEp) {
		return
	}
	if !isDefaultKubernetesEndpoints(r.Logger, newEp) {
		return
	}

	err := r.addAllDependonKubernetesPolicies(q)
	if err != nil {
		r.Logger.Error(err, "failed to add policies")
	}
}

func (r *Reconciler) addAllDependonKubernetesPolicies(q workqueue.RateLimitingInterface) error {
	ctx := context.Background()

	r.endpointsSyncCount++

	list := &netv1.NetworkPolicyList{}

	err := r.List(ctx, list, client.MatchingLabels(
		map[string]string{
			DependOnKubernetesLabelKey: DependOnKubernetesLabelValue,
		},
	))

	if err != nil {
		return err
	}

	for _, item := range list.Items {
		q.Add(ctrl.Request{
			NamespacedName: apitypes.NamespacedName{
				Namespace: item.Namespace,
				Name:      item.Name,
			},
		})
	}

	return nil
}
