package secretsyncer

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/aws-shared-provider/pkg/manager"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
)

const (
	TiDBSessCertName = "tidb-session-certificate-secret"
)

type Reconciler struct {
	Logger     logr.Logger
	Client     client.Client
	TidbPools  []string
	Namespace  string
	SecretName string
}

func (r *Reconciler) newCopiedSecret(src *corev1.Secret, meta *metav1.ObjectMeta) *corev1.Secret {
	copied := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Namespace:   meta.Namespace,
			Name:        meta.Name,
			Annotations: meta.Annotations,
		},
		Immutable:  ptr.To(false),
		Data:       src.Data,
		StringData: src.StringData,
	}
	return copied
}

func (r *Reconciler) syncSecretByNamespace(ctx context.Context, src *corev1.Secret, targetNS string) error {
	var err error
	dst := &corev1.Secret{}
	err = r.Client.Get(
		ctx,
		client.ObjectKey{
			Name:      r.SecretName,
			Namespace: targetNS,
		},
		dst,
	)
	if err != nil {
		if apierrors.IsNotFound(err) {
			dst = r.newCopiedSecret(
				src, &metav1.ObjectMeta{
					Namespace: targetNS,
					Name:      r.SecretName,
				},
			)
			return r.Client.Create(ctx, dst)
		}
	} else {
		if !equality.Semantic.DeepEqual(src.Data, dst.Data) {
			dst.Data = src.Data
			return r.Client.Update(ctx, dst)
		}
	}
	return nil
}

func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)
	logger.V(2).Info("start reconcile tidb session cert")
	start := time.Now()
	defer func() {
		logger.V(2).Info("finish reconcile session cert secret", "time", time.Since(start))
	}()

	src := &corev1.Secret{}
	err := r.Client.Get(
		ctx,
		client.ObjectKey{
			Name:      r.SecretName,
			Namespace: r.Namespace,
		},
		src,
	)
	if err != nil {
		if !apierrors.IsNotFound(err) {
			return ctrl.Result{}, err
		}
	}

	for _, namespace := range r.TidbPools {
		err := r.syncSecretByNamespace(ctx, src, namespace)
		if 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()
	r.Client = mgr.GetClient()

	byNameFuncs := predicate.NewPredicateFuncs(
		func(object client.Object) bool {
			return object.GetName() == TiDBSessCertName && object.GetNamespace() == r.Namespace
		},
	)

	return ctrl.NewControllerManagedBy(mgr).For(&corev1.Secret{}).WithEventFilter(byNameFuncs).Complete(r)
}
