package action

import (
	"errors"
	"fmt"

	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"github.com/tidbcloud/cluster-service-ng/pkg/clustergateway"
	"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"
)

type UpdateResourceGroupAction struct {
	sharedPoolRepo  repo.SharedPoolRepo
	tidbClusterRepo repo.TiDBClusterRepo

	clusterGatewayCli clustergateway.Client
}

const UpdateResourceGroupActionName = "update-resource-group"

func NewUpdateResourceGroupAction(
	sharedPoolRepo repo.SharedPoolRepo,
	tidbClusterRepo repo.TiDBClusterRepo,
	clusterGatewayCli clustergateway.Client,
) actions.Action {
	return &UpdateResourceGroupAction{
		sharedPoolRepo:    sharedPoolRepo,
		tidbClusterRepo:   tidbClusterRepo,
		clusterGatewayCli: clusterGatewayCli,
	}
}

func (a *UpdateResourceGroupAction) Name() string {
	return UpdateResourceGroupActionName
}

func (a *UpdateResourceGroupAction) Invoke(args actions.ActionArgs) error {
	// generate context
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "save tidb group action invoked")

	tidbClusterId, err := args.GetString(workflow.TiDBClusterIdKey)
	if err != nil {
		tilog.Error(ctx, "failed to get tidb cluster id", zap.Error(err))
		return fmt.Errorf("failed to get tidb cluster id: %w", err)
	}

	// load tidb cluster
	tidbCluster, err := a.tidbClusterRepo.Load(ctx, tidbClusterId, false)
	if err != nil {
		tilog.Error(ctx, "failed to load tidb cluster", zap.String("tidbClusterId", tidbClusterId), zap.Error(err))
		return fmt.Errorf("failed to load tidb cluster: %w", err)
	}

	// get shared pool
	sharedPool, err := a.sharedPoolRepo.Load(ctx, tidbCluster.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)
	}

	err = a.clusterGatewayCli.SetKeySpaceResourceGroup(ctx, sharedPool, tidbCluster.GetId(), &clustergateway.RCURequest{
		ServiceLimit: float64(tidbCluster.GetRequestUnit().GetMax()),
	})
	if err != nil {
		tilog.Error(ctx, "failed to update resource group", zap.String("resource group", tidbCluster.GetId()), zap.Error(err))
		return fmt.Errorf("failed to update resource group: %w", err)
	}

	// Set RU version via the controller config API (decoupled from service-limit).
	if ruVersion := tidbCluster.GetRuVersion(); ruVersion > 0 {
		if err := a.clusterGatewayCli.SetKeySpaceRuVersion(ctx, sharedPool, tidbCluster.GetId(), &clustergateway.SetKeyspaceRuVersionRequest{
			RuVersion: ruVersion,
		}); err != nil {
			if errors.Is(err, clustergateway.ErrRuVersionNotSupported) {
				if ruVersion == 2 {
					tilog.Warn(ctx, "PD does not support ru_version API, RU version 2 not applied",
						zap.String("keyspace", tidbCluster.GetId()),
						zap.String("shared_pool", sharedPool.GetId()),
					)
					RuVersionUnsupportedTotal.WithLabelValues(sharedPool.GetId(), tidbCluster.GetId()).Inc()
				}
			} else {
				tilog.Error(ctx, "failed to set ru version", zap.String("keyspace", tidbCluster.GetId()), zap.Error(err))
				return fmt.Errorf("failed to set ru version: %w", err)
			}
		}
	}

	tilog.Info(ctx, "update resource group successfully",
		zap.String("resource group", tidbCluster.GetId()),
		zap.String("sharedPoolId", sharedPool.GetId()),
		zap.Uint32("service limit", tidbCluster.RequestUnit.Max),
		zap.Int32("ru version", tidbCluster.GetRuVersion()),
	)
	return nil
}
