package networkpolicy

import (
	"context"
	"fmt"
	"time"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
	corev1 "k8s.io/api/core/v1"
	netv1 "k8s.io/api/networking/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/builder"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/handler"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"sigs.k8s.io/controller-runtime/pkg/source"
)

const (
	LabelKeyNamespace = "kubernetes.io/metadata.name"
)

// Reconciler reconciles a NetworkPolicy object.
type Reconciler struct {
	Logger logr.Logger
	client.Client

	endpointsSyncCount int

	KubeClusterName string

	LoadBalancerCIDRs []string
}

//+kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch
//+kubebuilder:rbac:groups=core,resources=endpoints,verbs=get;list;watch
//+kubebuilder:rbac:groups=networking.k8s.io,resources=networkpolicies,verbs=get;list;watch;create;update;patch;delete

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)
	logger.Info("Start reconcile")

	startTime := time.Now()
	defer func() {
		logger.Info("Finish reconcile",
			"time", time.Since(startTime),
		)
	}()

	ns := corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: req.Namespace,
		},
	}

	err := r.Get(ctx, types.NamespacedName{Name: ns.Name}, &ns)
	if err != nil {
		if apierrors.IsNotFound(err) {
			logger.Info("namespace has been deleted")
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}

	// skip all namespaces which is not owned by network
	if !isOwnedByNetwork(&ns) {
		logger.Info("skip not owned by network")
		return ctrl.Result{}, nil
	}

	if err := r.sync(ctx, req.Namespace, req.Name); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(mgr manager.Manager) error {
	r.Logger = mgr.GetLogger()

	cp, err := predicate.LabelSelectorPredicate(metav1.LabelSelector{
		MatchLabels: map[string]string{
			labels.LabelKeyKubernetesCluster: r.KubeClusterName,
		},
	})
	if err != nil {
		return fmt.Errorf("invalid label selector: %w", err)
	}

	return ctrl.NewControllerManagedBy(mgr).For(&netv1.NetworkPolicy{}, builder.WithPredicates(cp)).
		Watches(source.NewKindWithCache(&corev1.Endpoints{}, mgr.GetCache()), &handler.Funcs{
			CreateFunc: r.handleCreateEndpointsEvent,
			UpdateFunc: r.handleUpdateEndpointsEvent,
			DeleteFunc: r.handleDeleteEndpointsEvent,
		}).
		Watches(source.NewKindWithCache(&corev1.Namespace{}, mgr.GetCache()), &handler.Funcs{
			CreateFunc: r.handleCreateNamespaceEvent,
		}, builder.WithPredicates(cp)).
		Complete(r)
}
