package action

import (
	"context"
	"strconv"

	"github.com/tidbcloud/fastflow/pkg/entity/run"
	"github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	clusterrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster"
	bindingrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/clusterencryptionbinding"
	encryptionkeyrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/encryptionkey"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

const FinalizeEncryptionChange = "finalize_encryption_change"

type FinalizeEncryptionChangeAction struct {
	clusterRepo       clusterrepo.Repo
	bindingRepo       bindingrepo.Repo
	encryptionKeyRepo encryptionkeyrepo.Repo
	transaction       db.Transactioner
}

func NewFinalizeEncryptionChangeAction(
	clusterRepo clusterrepo.Repo,
	bindingRepo bindingrepo.Repo,
	encryptionKeyRepo encryptionkeyrepo.Repo,
	transaction db.Transactioner,
) *FinalizeEncryptionChangeAction {
	return &FinalizeEncryptionChangeAction{
		clusterRepo:       clusterRepo,
		bindingRepo:       bindingRepo,
		encryptionKeyRepo: encryptionKeyRepo,
		transaction:       transaction,
	}
}

func (a *FinalizeEncryptionChangeAction) Name() string {
	return FinalizeEncryptionChange
}

func (a *FinalizeEncryptionChangeAction) ParameterNew() any {
	return &common.ClusterIdParameter{}
}

func (a *FinalizeEncryptionChangeAction) Run(ctx run.ExecuteContext, params any) error {
	cctx, span := workflow.GenerateContext(ctx)
	defer span.End()

	clusterId, err := parseClusterId(ctx)
	if err != nil {
		tilog.Error(cctx, "failed to parse cluster_id", zap.Error(err))
		return err
	}

	encryptionKeyIdStr, ok := ctx.GetVar(common.KeyEncryptionKeyId)
	if !ok || encryptionKeyIdStr == "" {
		err = errMissingVar(common.KeyEncryptionKeyId)
		tilog.Error(cctx, "missing workflow variable", zap.String("key", common.KeyEncryptionKeyId), zap.Error(err))
		return err
	}
	encryptionKeyId, err := strconv.ParseUint(encryptionKeyIdStr, 10, 64)
	if err != nil {
		tilog.Error(cctx, "invalid encryption_key_id", zap.String("encryption_key_id", encryptionKeyIdStr), zap.Error(err))
		return err
	}

	transitionType, ok := ctx.GetVar(common.KeyTransitionType)
	if !ok || transitionType == "" {
		err = errMissingVar(common.KeyTransitionType)
		tilog.Error(cctx, "missing workflow variable", zap.String("key", common.KeyTransitionType), zap.Error(err))
		return err
	}

	encryptionKey, keyErr := a.encryptionKeyRepo.Load(cctx, encryptionKeyId, false)
	if keyErr != nil {
		tilog.Error(cctx, "failed to load encryption key",
			zap.Uint64("encryption_key_id", encryptionKeyId), zap.Error(keyErr))
		return keyErr
	}
	targetMasterKey := encryptionKey.MasterKey

	tErr := a.transaction.Transaction(cctx, func(ctx context.Context) error {
		if transitionType == "disable-cmek" {
			return a.finalizeDisable(ctx, clusterId)
		}
		return a.finalizeEnable(ctx, clusterId, encryptionKeyId, transitionType, targetMasterKey)
	})
	if tErr != nil {
		tilog.Error(cctx, "failed to finalize encryption change",
			zap.Uint64("cluster_id", clusterId),
			zap.String("transition_type", transitionType),
			zap.Error(tErr))
		return tErr
	}

	tilog.Info(cctx, "encryption change finalized",
		zap.Uint64("cluster_id", clusterId),
		zap.Uint64("encryption_key_id", encryptionKeyId),
		zap.String("transition_type", transitionType),
	)

	return nil
}

func (a *FinalizeEncryptionChangeAction) finalizeDisable(ctx context.Context, clusterId uint64) error {
	// 1. Delete binding
	if err := a.bindingRepo.Delete(ctx, clusterId); err != nil {
		tilog.Error(ctx, "failed to delete binding", zap.Uint64("cluster_id", clusterId), zap.Error(err))
		return err
	}

	// 2. Update cluster: clear encryption key
	cluster, err := a.clusterRepo.Get(ctx, clusterId, false)
	if err != nil {
		tilog.Error(ctx, "failed to load cluster", zap.Uint64("cluster_id", clusterId), zap.Error(err))
		return err
	}
	cluster.EncryptionKeyId = 0
	if saveErr := a.clusterRepo.Save(ctx, cluster); saveErr != nil {
		tilog.Error(ctx, "failed to save cluster", zap.Uint64("cluster_id", clusterId), zap.Error(saveErr))
		return saveErr
	}

	return nil
}

func (a *FinalizeEncryptionChangeAction) finalizeEnable(
	ctx context.Context,
	clusterId, encryptionKeyId uint64,
	transitionType, targetMasterKey string,
) error {
	// 1. Update binding → Enabled
	if err := a.bindingRepo.UpdateState(ctx, clusterId, entity.ClusterEncryptionBindingStateEnabled, "encryption change completed"); err != nil {
		tilog.Error(ctx, "failed to update binding state", zap.Uint64("cluster_id", clusterId), zap.Error(err))
		return err
	}

	// 2. Update encryption key's master key (only for CMEK transitions)
	if transitionType == "enable-cmek" {
		if err := a.encryptionKeyRepo.UpdateMasterKey(ctx, encryptionKeyId, targetMasterKey); err != nil {
			tilog.Error(ctx, "failed to update master key", zap.Uint64("encryption_key_id", encryptionKeyId), zap.Error(err))
			return err
		}
	}

	// 3. Update cluster's EncryptionKeyId
	cluster, clusterErr := a.clusterRepo.Get(ctx, clusterId, false)
	if clusterErr != nil {
		tilog.Error(ctx, "failed to load cluster", zap.Uint64("cluster_id", clusterId), zap.Error(clusterErr))
		return clusterErr
	}
	if cluster.EncryptionKeyId != encryptionKeyId {
		cluster.EncryptionKeyId = encryptionKeyId
		if saveErr := a.clusterRepo.Save(ctx, cluster); saveErr != nil {
			tilog.Error(ctx, "failed to save cluster",
				zap.Uint64("cluster_id", clusterId), zap.Error(saveErr))
			return saveErr
		}
	}

	return nil
}
