package common

import (
	"context"
	"fmt"
	"time"

	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

type Secret struct {
	*task.BaseTask
}

func NewSecret(ctx *task.Context) *Secret {
	return &Secret{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

func (t *Secret) Name() string {
	return "Secret"
}

func (t *Secret) secrets(ctx context.Context) (secrets []*corev1.Secret, _ error) {
	// sync jwks secrets from infra-api into shoot
	authTokenJwksSecretInLocal := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.Namer.TidbAuthTokenJwksSecretInInfraAPI(),
			Namespace: t.Cluster.Namespace,
		},
	}
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(authTokenJwksSecretInLocal), authTokenJwksSecretInLocal); err != nil {
		if !apierrors.IsNotFound(err) {
			return nil, err
		}
	}
	// NOTE: An empty secret would be created into shoot, if the 'authTokenJwksSecret' secret not exist.
	secrets = append(secrets, TidbAuthTokenJwksSecret(t.Namer, authTokenJwksSecretInLocal))

	return secrets, nil
}

func (t *Secret) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	cli := t.EnvProvider.GetClient()

	secrets, err := t.secrets(ctx)
	if err != nil {
		return deploy.Error(err)
	}
	for _, s := range secrets {
		obj := &corev1.Secret{}
		obj.Annotations = map[string]string{}
		obj.Namespace = s.Namespace
		obj.Name = s.Name
		_, err = controllerutil.CreateOrUpdate(ctx, cli, obj, func() error {
			obj.Data = s.Data
			return nil
		})
		if err != nil {
			return deploy.ErrorOrDone(err)
		}
	}
	return deploy.Done()
}

func (t *Secret) Delete(ctx context.Context) deploy.Result {
	cli := t.EnvProvider.GetClient()

	// Remove the secrets only if the TiDBCluster is removed already
	if !t.TCGetter.IsDeleted(ctx, cli, consts.ClusterPrefix, t.Namer.GetTiDBNamespace()) {
		return deploy.Error(fmt.Errorf("tc is not deleted"))
	}

	secrets, err := t.secrets(ctx)
	if err != nil {
		return deploy.Error(err)
	}
	for _, s := range secrets {
		if err := k8s.DeleteObject(ctx, cli, s); err != nil {
			return deploy.Error(err)
		}
	}

	return deploy.Done()
}

func (t *Secret) PostDelete(ctx context.Context) deploy.Result {
	cli := t.EnvProvider.GetClient()

	secrets, err := t.secrets(ctx)
	if err != nil {
		return deploy.Error(err)
	}
	for _, s := range secrets {
		if err := k8s.GetDeletedObject(ctx, cli, k8s.GetNamespacedName(s), &corev1.Secret{}); err != nil {
			return deploy.Error(err)
		}
	}

	return deploy.Done()
}

func TidbAuthTokenJwksSecret(cluster *namer.Namer, src *corev1.Secret) *corev1.Secret {
	return &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      cluster.TidbAuthTokenJwksSecret(),
			Namespace: cluster.GetTiDBNamespace(),
		},
		Data: src.Data,
	}
}
