package action

import (
	"fmt"

	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/keyspacerole"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"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/grpc/codes"
	"google.golang.org/grpc/status"
)

const CreateKeyspaceRoleActionName = "create-keyspace-role"

type CreateKeyspaceRoleAction struct {
	sharedPoolRepo  repo.SharedPoolRepo
	tidbClusterRepo repo.TiDBClusterRepo
	client          dplanemgrv1.KeyspaceRoleServiceClient
}

func NewCreateKeyspaceRoleAction(
	sharedPoolRepo repo.SharedPoolRepo,
	tidbClusterRepo repo.TiDBClusterRepo,
	client dplanemgrv1.KeyspaceRoleServiceClient,
) actions.Action {
	return &CreateKeyspaceRoleAction{
		sharedPoolRepo:  sharedPoolRepo,
		tidbClusterRepo: tidbClusterRepo,
		client:          client,
	}
}

func (a *CreateKeyspaceRoleAction) Name() string {
	return CreateKeyspaceRoleActionName
}

func (a *CreateKeyspaceRoleAction) Invoke(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "create keyspace role action invoked")

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

	sharedPool, err := a.sharedPoolRepo.Load(ctx, sharedPoolID, false)
	if err != nil {
		tilog.Error(ctx, "failed to load sharedPool", zap.String("sharedPoolId", sharedPoolID), zap.Error(err))
		return fmt.Errorf("failed to load sharedPool: %w", err)
	}

	if keyspacerole.ShouldSkip(sharedPool) {
		tilog.Info(ctx, "skip create keyspace role for shared pool",
			zap.String("sharedPoolId", sharedPoolID),
			zap.Stringer("capacityMode", sharedPool.GetCapacityMode()),
			zap.Stringer("provider", sharedPool.GetProvider().GetType()),
		)
		return nil
	}

	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)
	}

	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)
	}

	if keyspacerole.ShouldSkipForCluster(sharedPool, tidbCluster) {
		tilog.Info(ctx, "skip create keyspace role for shared pool",
			zap.String("sharedPoolId", sharedPoolID),
			zap.Stringer("capacityMode", sharedPool.GetCapacityMode()),
			zap.Stringer("provider", sharedPool.GetProvider().GetType()),
		)
		return nil
	}

	dataplaneID, err := args.GetString(workflow.DataplaneIdKey)
	if err != nil {
		tilog.Warn(ctx, "failed to get dataplane id from action args, fallback to sharedPool", zap.Error(err))
	}

	params, err := keyspacerole.BuildParams(sharedPool, tidbCluster, dataplaneID)
	if err != nil {
		return fmt.Errorf("failed to build keyspace role params: %w", err)
	}

	if _, err := a.client.CreateKeyspaceRole(ctx, params.CreateRequest()); err != nil {
		if status.Code(err) == codes.AlreadyExists {
			tilog.Info(ctx, "keyspace role already exists",
				zap.String("dataplaneId", params.DataplaneID),
				zap.String("keyspaceName", params.KeyspaceName),
			)
			return nil
		}
		tilog.Error(ctx, "failed to create keyspace role",
			zap.String("dataplaneId", params.DataplaneID),
			zap.String("keyspaceName", params.KeyspaceName),
			zap.Error(err),
		)
		return fmt.Errorf("failed to create keyspace role: %w", err)
	}

	tilog.Info(ctx, "create keyspace role successfully",
		zap.String("dataplaneId", params.DataplaneID),
		zap.String("keyspaceName", params.KeyspaceName),
	)
	return nil
}
