package zoneredundantevicter

import (
	"context"
	"time"

	"github.com/tidbcloud/aws-shared-provider/pkg/manager"
	"github.com/tidbcloud/aws-shared-provider/pkg/metrics"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbmanager"
	infraclient "github.com/tidbcloud/tidb-management-service/api/serverless_infra/client"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/record"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/handler"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
	"sigs.k8s.io/controller-runtime/pkg/source"
)

const (
	MainZoneConfigName = "main-zone-config"
)

// Reconciler reconciles a main-zone-config ConfigMap object.
type Reconciler struct {
	ProviderType    string
	Client          client.Client
	InfraClusterCli infraclient.Client
	InfraClient     client.Client
	Recorder        record.EventRecorder
	Namespace       string
	CheckInterval   time.Duration
	TriggerTimer    *time.Timer
	EventChannel    chan event.GenericEvent
	TidbManagers    map[string]tidbmanager.TidbManager
}

// 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) {
	r.TriggerStop()

	logger := log.FromContext(ctx)
	logger.V(2).Info("start reconcile zone redundant evicter")
	start := time.Now()
	defer func() {
		logger.V(2).Info("finish reconcile zone redundant evicter", "time", time.Since(start))
	}()

	mainZoneConfigCm := &corev1.ConfigMap{}
	if err := r.Client.Get(ctx, req.NamespacedName, mainZoneConfigCm); err != nil {
		metrics.ObserveReconcileFailure(req.Name, "ZoneRedundantEvicter")
		return ctrl.Result{}, err
	}
	mainZone := mainZoneConfigCm.Data["zone"]

	logger.V(2).Info("the main zone expected", "mainZone", mainZone)
	ctx = log.IntoContext(ctx, ctrl.Log.WithName("ZoneRedundantEvicter").WithValues("mainZone", mainZone))
	return r.ReconcileErr(r.evictZoneRedundant(ctx, mainZone))
}

// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(mgr manager.Manager) error {
	r.EventChannel = make(chan event.GenericEvent)
	r.Recorder = mgr.GetEventRecorderFor("ZoneRedundantEvicter")

	byFilterFuncs := predicate.NewPredicateFuncs(func(object client.Object) bool {
		return object.GetName() == MainZoneConfigName && object.GetNamespace() == r.Namespace
	})
	return ctrl.NewControllerManagedBy(mgr).Named("ZoneRedundantEvicter").
		For(&corev1.ConfigMap{}).
		WatchesRawSource(source.Channel(r.EventChannel, &handler.EnqueueRequestForObject{})).
		WithEventFilter(byFilterFuncs).
		Complete(r)
}

func (r *Reconciler) ReconcileErr(err error) (reconcile.Result, error) {
	if err == nil {
		r.TriggerAfter(r.CheckInterval)
		return reconcile.Result{}, nil
	} else if triggerAfter, ok := err.(*triggerAfterError); ok {
		r.TriggerAfter(triggerAfter.triggerAfter)
		return reconcile.Result{}, nil
	}
	metrics.ObserveReconcileFailure(MainZoneConfigName, "ZoneRedundantEvicter")
	return reconcile.Result{}, err
}

func (r *Reconciler) TriggerAfter(d time.Duration) {
	r.TriggerTimer = time.AfterFunc(d, func() {
		r.EventChannel <- event.GenericEvent{Object: &corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{Name: MainZoneConfigName, Namespace: r.Namespace},
		}}
	})
}

func (r *Reconciler) TriggerStop() {
	if r.TriggerTimer != nil {
		r.TriggerTimer.Stop()
	}
}
