package pod

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/pkg/pingcap/label"
	"github.com/tidbcloud/shared-provider/pkg/manager"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
	evictpod "github.com/tidbcloud/shared-provider/pkg/utils/pod"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
)

const (
	MaxConcurrentReconciles = 3
)

// Reconciler reconciles a Pod object.
type Reconciler struct {
	logr.Logger

	Client     client.Client
	Cache      client.Reader
	InfraCache client.Reader

	KubeClusterName string
	EvictDuration   time.Duration
}

// 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)

	pod := &corev1.Pod{}
	if err := r.Cache.Get(ctx, req.NamespacedName, pod); err != nil {
		if apierrors.IsNotFound(err) {
			logger.Info("Pod not found in cache")
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}

	if !evictpod.EvictComponents.Find(pod.Labels[label.ComponentLabelKey]) {
		// Do not consider the other pods
		return ctrl.Result{}, nil
	}

	_, clusterNs, clusterName, _ := labels.GetOwnerFromLabels(pod.Labels)
	if clusterName == "" || clusterNs == "" {
		logger.Info("Cluster info empty", "clusterNs", clusterNs, "clusterName", clusterName)
		return ctrl.Result{}, nil
	}

	cluster := &cloudv1.Cluster{}
	if err := r.InfraCache.Get(ctx, client.ObjectKey{Name: clusterName, Namespace: clusterNs}, cluster); err != nil {
		if apierrors.IsNotFound(err) {
			logger.Info("Cluster not found", "clusterNs", clusterNs, "clusterName", clusterName)
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}
	if kubeCluster := labels.GetKubeClusterFromLabels(cluster.Labels); kubeCluster != r.KubeClusterName {
		logger.V(2).Info("Ignore non-local cluster managed pods", "cluster", kubeCluster)
		return ctrl.Result{}, nil
	}
	if cluster.DeletionTimestamp != nil {
		return ctrl.Result{}, nil
	}

	return r.handleSafeToEvictAnno(ctx, pod, cluster)
}

// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(mgr manager.Manager) error {
	r.Logger = mgr.GetLogger()
	r.Client = mgr.GetClient()
	r.Cache = mgr.GetCache()
	r.InfraCache = mgr.InfraCluster().GetCache()

	return ctrl.NewControllerManagedBy(mgr).For(&corev1.Pod{}).
		WithEventFilter(&predicate.Funcs{ // only watch Create
			UpdateFunc:  func(updateEvent event.UpdateEvent) bool { return false },
			DeleteFunc:  func(deleteEvent event.DeleteEvent) bool { return false },
			GenericFunc: func(genericEvent event.GenericEvent) bool { return false },
		}).
		WithOptions(controller.Options{MaxConcurrentReconciles: MaxConcurrentReconciles}).
		Complete(r)
}
