package action

import (
	"context"
	"fmt"

	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"github.com/tidbcloud/cluster-service-ng/pkg/dataplane"
	"github.com/tidbcloud/cluster-service-ng/pkg/k8s"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/encoding/protojson"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type CreateTiDBAuthTokenSecretAction struct {
	dataplaneManager dataplane.Manager
	sharedPoolRepo   repo.SharedPoolRepo
}

const (
	CreateTiDBAuthTokenSecretActionName = "create-tidb-auth-token-secret"
	tiDBAuthTokenJWKSFilename           = "tidb_auth_token_jwks.json"
)

func NewCreateTiDBAuthTokenSecretAction(sharedPoolRepo repo.SharedPoolRepo, dataplaneManager dataplane.Manager) actions.Action {
	return &CreateTiDBAuthTokenSecretAction{
		sharedPoolRepo:   sharedPoolRepo,
		dataplaneManager: dataplaneManager,
	}
}

func (a *CreateTiDBAuthTokenSecretAction) Name() string {
	return CreateTiDBAuthTokenSecretActionName
}

func (a *CreateTiDBAuthTokenSecretAction) Invoke(args actions.ActionArgs) error {
	// generate context
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "create cluster auth token secret action invoked")

	tidbGroupStr, err := args.GetString(workflow.TiDBGroupKey)
	if err != nil {
		return fmt.Errorf("failed to get tidb group: %w", err)
	}

	var tidbGroup entity.TiDBGroup
	err = protojson.Unmarshal([]byte(tidbGroupStr), &tidbGroup)
	if err != nil {
		tilog.Error(ctx, "failed to unmarshal tidb group", zap.Error(err))
		return fmt.Errorf("failed to unmarshal tidb group: %w", err)
	}

	// load shared pool
	sharedPool, err := a.sharedPoolRepo.Load(ctx, tidbGroup.GetSharedPoolId(), false)
	if err != nil {
		tilog.Error(ctx, "failed to load shared pool", zap.Error(err))
		return fmt.Errorf("failed to load shared pool: %w", err)
	}

	remoteCli, err := a.dataplaneManager.GetClient(sharedPool.GetDataplaneName())
	if err != nil {
		tilog.Error(ctx, "failed to get remote client for shared pool", zap.String("dataplaneName", sharedPool.GetDataplaneName()), zap.Error(err))
		return fmt.Errorf("failed to get remote client for shared pool: %w", err)
	}

	secret := a.secret(ctx, &tidbGroup)
	if secret == nil {
		tilog.Warn(ctx, "no tidb component found, can't create tidb auth token secret")
		return fmt.Errorf("no tidb component found, can't create tidb auth token secret")
	}

	if err := k8s.CreateIfNotExist(ctx, remoteCli, secret); err != nil {
		tilog.Error(ctx, "failed to create or update secret",
			zap.String("secretName", secret.Name),
			zap.String("namespace", secret.Namespace),
			zap.Error(err),
		)
		return err
	}
	return nil
}

func (a *CreateTiDBAuthTokenSecretAction) secret(_ context.Context, tidbGroup *entity.TiDBGroup) *corev1.Secret {
	return tidbAuthTokenJwksSecret(
		tidbGroup.GetId(),
		entity.GetDataPlaneNamespace(tidbGroup.GetSharedPoolId()),
		tidbGroup.GetSecurityConfiguration().GetTidbAuthTokenJwks())
}

func tidbAuthTokenJwksSecret(tidbGroupId, namespace, tokenJwks string) *corev1.Secret {
	return &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("db-%s-auth-token-jwks-secret", tidbGroupId),
			Namespace: namespace,
		},
		Data: map[string][]byte{
			tiDBAuthTokenJWKSFilename: []byte(tokenJwks),
		},
	}
}
