package dbuser

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/account/thirdparty/mgmt"
	commonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"

	"github.com/spf13/viper"

	authcenter2 "github.com/tidbcloud/account/internel/svc/authcenter"
	"github.com/tidbcloud/account/internel/svc/dbuser"
	"github.com/tidbcloud/account/internel/svc/user"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/pkgv2/clusterproxy"

	"github.com/duke-git/lancet/v2/maputil"

	"github.com/duke-git/lancet/v2/slice"
	"go.opentelemetry.io/otel"

	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"

	"github.com/tidbcloud/account/commons/config"

	dbUserDao "github.com/tidbcloud/account/internel/dao/dbuser"

	"github.com/tidbcloud/account/thirdparty/infra"

	"github.com/tidbcloud/account/central/utils"

	"go.uber.org/zap"

	"github.com/tidbcloud/tiinf/monitor/tilog"

	"github.com/tidbcloud/account/internel/dao/entity"
)

// RunDBUserClusterJob
//
//	@Description:create/delete  dbUser
//	@Author wangletian 2022-12-17 23:07:51
//	@param ctx
//	@param dbUserSrv
//	@param userSrv
//	@return error
func RunDBUserClusterJob(c context.Context, dbUserSrv dbuser.DBUserIface, userSrv user.UserSvcIface) error {
	ctx, span := otel.Tracer(config.TraceComponentNameWorker).Start(context.Background(), "RunDBUserClusterJob")
	defer span.End()
	t := time.Now().Add(time.Hour * -config.CommandIgnoreTime)

	filter := dbUserDao.CommandDBUserClusterFilter{
		Status:       []entity.CommandStatus{entity.CommandStatusWaiting, entity.CommandStatusFail},
		CreatedAtGte: &t,
		Actions:      []entity.CommandAction{entity.CommandActionCreate, entity.CommandActionDelete},
		//IDs: []entity.ID{2868630, 2868631, 2868632, 2868633, 2868634, 2868635, 2868638, 2868636, 2868637, 2868638, 2868639},
	}
	commands, err := dbUserSrv.ListCommandDBUserClusters(ctx, filter)
	bm := utils.Begin(ctx, "RunDBUserClusterJob", zap.Any("command size", len(commands)))
	defer bm.OnExit(ctx)
	if err != nil {
		return err
	}
	tilog.Info(ctx, "RunDBUserClusterJob Command "+fmt.Sprintf("size=%d", len(commands)))

	//isEven := func(i int, b entity.CommandDBUserCluster) bool {
	//	return b.Action == entity.CommandActionCheck
	//}
	//// find the action=check
	//checkCommand := slice.Filter(commands, isEven)
	//
	//// find the action in (create , delete)
	//otherCommand := slice.Difference(commands, checkCommand)
	//
	//var ckIds []uint64
	//for _, c := range checkCommand {
	//	ckIds = append(ckIds, c.ID.Uint64())
	//}
	var otherIds []uint64
	for _, c := range commands {
		otherIds = append(otherIds, c.ID.Uint64())
	}

	//tilog.Info(ctx, "check command "+fmt.Sprintf("size=%d", len(checkCommand)), zap.Any("id", ckIds))
	tilog.Info(ctx, "dbuser command "+fmt.Sprintf("size=%d", len(commands)), zap.Any("id", otherIds))
	//go runCommand(commands, context.Background(), dbUserSrv, userSrv)

	// map[cluster_id][]command
	clusterCommandMap := slice.GroupWith(commands, func(b entity.CommandDBUserCluster) uint64 {
		return b.ClusterID.Uint64()
	})
	keys := maputil.Keys(clusterCommandMap)
	for _, key := range keys {
		commands := clusterCommandMap[key]

		go runCommand(commands, context.Background(), dbUserSrv, userSrv)
	}
	tilog.Info(ctx, "group by dbuser", zap.Any("map", clusterCommandMap))

	return nil

}

const (
	pushing uint32 = 1
	noPush  uint32 = 0

	timeoutErrMsg           = "context deadline exceeded"
	connectionRefusedErrMsg = "connection refused"

	createDbUserOrgBlacklistKey = "dbUser.createDbUserOrgBlacklist"
)

func runCommand(commands []entity.CommandDBUserCluster, ct context.Context, dbUserSrv dbuser.DBUserIface, userSrv user.UserSvcIface) {
	var ids []uint64
	orgBlacklist := viper.GetStringSlice(createDbUserOrgBlacklistKey)
	tilog.Info(context.Background(), "get create db user org blacklist", zap.Any("orgBlacklist", orgBlacklist))

	for _, lc := range commands {
		ctx, span := otel.Tracer(config.TraceComponentNameWorker).Start(context.Background(), "runCommand")
		ids = append(ids, lc.ID.Uint64())

		// may be a long time has passed, get the latest pushing
		cs, err := dbUserSrv.ListCommandDBUserClusters(ctx, dbUserDao.CommandDBUserClusterFilter{
			IDs: []entity.ID{lc.ID},
		})
		if err != nil || len(cs) == 0 {
			tilog.Error(ctx, "run this dbUserCommand get latest command error or return empty", zap.Any("id", lc.ID), zap.Any("err", err))
			continue
		}
		c := cs[0]

		// check if ignore to run
		ignore := isIgnoreToRunDBUserClusterCommand(ctx, c)
		if ignore {
			msg := fmt.Sprintf("id=%d, IsIgnore=%t, {cluster=%d, dbuser=%s, failNum=%d}", c.ID.Uint64(), ignore, c.ClusterID, c.DbUser, c.FailNum)
			tilog.Info(ctx, "run this dbUserCommand "+msg, zap.Any("command", c))
			continue
		}

		// by set pushing to get the priv to run
		oldPushing := c.Pushing
		if oldPushing == pushing {
			err = dbUserSrv.UpdateCommandDBUserPushing(ctx, c.ID.Uint64(), pushing)
		} else {
			err = dbUserSrv.UpdateCommandDBUserPushingByFrom(ctx, c.ID.Uint64(), noPush, pushing)
		}
		if err != nil {
			tilog.Warn(ctx, "run this dbUserCommand update pushing to get the run priv error", zap.Any("id", lc.ID), zap.Any("origin_pushing", oldPushing), zap.Any("err", err))
			continue
		}

		msg := fmt.Sprintf("id=%d, IsIgnore=%t, {clusetr=%d,dbuser=%s}", c.ID.Uint64(), ignore, c.ClusterID, c.DbUser)
		tilog.Info(ctx, "run this dbUserCommand "+msg, zap.Any("command", c))

		var status entity.CommandStatus
		if slice.Contain(orgBlacklist, fmt.Sprintf("%d", c.OrgID.Uint64())) {
			tilog.Info(ctx, "command relate org in create db user org blacklist, ignore", zap.Any("orgId", c.OrgID))
			status = entity.CommandStatusIgnore
		} else {
			status = RunCommandInCM(ctx, dbUserSrv, &c, userSrv)
		}

		if err = dbUserSrv.UpdateCommandDBUserPushing(ctx, c.ID.Uint64(), noPush); err != nil {
			tilog.Info(ctx, "run this dbUserCommand over update pushing to no_push error", zap.Any("id", c.ID), zap.Any("err", err))
		}

		updateEntity := entity.CommandDBUserCluster{
			Updatable: c.Updatable,
			Status:    status,
		}
		now := time.Now()
		updateEntity.UpdatedAt = &now
		if status == entity.CommandStatusFail {
			updateEntity.FailNum = c.FailNum + 1
			c.FailNum = c.FailNum + 1
		}
		if err := dbUserSrv.UpdateCommandDBUserCluster(ctx, updateEntity); err != nil {
			tilog.Error(ctx, "UpdateCommandDBUserCluster error", zap.Error(err))
		}
		tilog.Info(ctx, "run this dbUserCommand result="+fmt.Sprintf("%s, id=%d, {cluster=%d, dbuser=%s, action=%s, FailNum=%d}",
			status, c.ID.Uint64(), c.ClusterID, c.DbUser, c.Action, c.FailNum))

		span.End()

	}
	tilog.Info(ct, "runCommand end "+fmt.Sprintf("size=%d", len(commands)), zap.Any("id", ids))
}

// executeInCM
//
//	@Description: call cm to create/delete dbUser
//	@Author wangletian 2022-12-25 23:07:08
//	@param ctx
//	@param dbUserSrv
//	@param c
//	@param userSrv
//	@return entity.CommandStatus
func RunCommandInCM(ctx context.Context, dbUserSvc dbuser.DBUserIface, c *entity.CommandDBUserCluster, userSvc user.UserSvcIface) entity.CommandStatus {

	userEmail := ""
	if (c.TargetType == entity.ClusterUserTargetUser || c.TargetType == entity.ClusterUserTargetCloudUser) && c.UserID != 0 {
		user, err := userSvc.GetUserByID(ctx, c.UserID.Uint64())
		if err != nil {
			tilog.Info(ctx, "executeInCM GetUserByID fail ,userId may not be correct", zap.Error(err), zap.Any("userId", c.UserID))
			return entity.CommandStatusFail
		}
		userEmail = user.Email
	}

	dbUser := c.DbUser

	// check if cluster is premium, ignore
	clustersRsp, err := mgmt.GeClient().ListClusters(ctx, &mgmtv1.ListClustersRequest{
		Filter: &mgmtv1.ClusterFilter{
			ClusterIds: []uint64{c.ClusterID.Uint64()},
		},
	})
	if err != nil {
		tilog.Info(ctx, "executeInCM call mgmt get cluster by id error", zap.Any("cluster_id", c.ClusterID), zap.Any("dbUser", dbUser), zap.Error(err))
		return entity.CommandStatusFail
	}
	if clustersRsp == nil || len(clustersRsp.Clusters) == 0 {
		tilog.Info(ctx, "executeInCM call mgmt queryCluster cluster is delete or not exist ,ignore", zap.Any("clusterId", c.ClusterID.Uint64()), zap.Any("dbUser", dbUser))
		return entity.CommandStatusIgnore
	}
	if clustersRsp.Clusters[0].DeployType == mgmtv1.DeployType_DEPLOY_TYPE_NEXTGEN &&
		clustersRsp.Clusters[0].GetNextGenCluster() != nil &&
		clustersRsp.Clusters[0].GetNextGenCluster().ServicePlan == commonv1.ServicePlan_Premium {
		tilog.Info(ctx, "executeInCM cluster deploy type is next gen and service plan is premium, ignore", zap.Any("clusterId", c.ClusterID.Uint64()), zap.Any("dbUser", dbUser))
		return entity.CommandStatusIgnore
	}

	cluster, err := infra.GetHandler().GetClusterById(ctx, c.ClusterID.Uint64(), true)
	if err != nil {
		return infra.GetCommandStatus(ctx, err)
	}
	if cluster == nil {
		tilog.Info(ctx, "executeInCM (create/delete DBUser) queryCluster  cluster is delete or not exist ,ignore", zap.Any("clusterId", c.ClusterID.Uint64()), zap.Any("dbUser", dbUser))
		return entity.CommandStatusIgnore
	}

	if !infra.IsSupportDBUser(ctx, cluster) {
		// There will be changes in cluster feature before the cluster status normal
		if !slice.Contain(infra.CacheClusterStatus, cluster.Status) {
			tilog.Info(ctx, "executeInCM (create/delete DBUser) queryCluster status is normal ,not support dbuser", zap.Any("clusterId", c.ClusterID.Uint64()), zap.Any("dbUser", dbUser))
			return entity.CommandStatusFail
		}
		tilog.Info(ctx, "executeInCM (create/delete DBUser) queryCluster  status is not support dbuser ,ignore", zap.Any("clusterId", c.ClusterID.Uint64()), zap.Any("dbUser", dbUser))
		return entity.CommandStatusIgnore
	}

	operator, err := getClusterOperator(ctx, dbUserSvc, c, cluster)
	if err != nil {
		msg := err.Error()
		if msg == infra.ClusterRestoringMsg {
			tilog.Info(ctx, "executeInCM getClusterOperator fail: cluster is restoring, fail the command", zap.Any("clusterId", c.ClusterID.Uint64()))
			return entity.CommandStatusFail
		}
		if msg == authcenter2.ErrMsgClusterNotExist {
			tilog.Info(ctx, "executeInCM getClusterOperator fail: cluster is not exist, ignore the command", zap.Any("clusterId", c.ClusterID.Uint64()))
			return entity.CommandStatusIgnore
		}
		if msg == authcenter2.ErrMsgNotFoundRoleAdminDbUser {
			if c.FailNum > 10 {
				tilog.Warn(ctx, fmt.Sprintf("executeInCM getClusterOperator fail: not found role admin dbuser and has retry 10 count, ignore the command, command_id: %d, cluster_id: %d", c.ID.Uint64(), c.ClusterID.Uint64()))
				return entity.CommandStatusIgnore
			}
			tilog.Info(ctx, "executeInCM getClusterOperator fail: not found role admin dbuser, fail the command", zap.Any("clusterId", c.ClusterID.Uint64()))
			return entity.CommandStatusFail
		}
		if strings.Contains(msg, timeoutErrMsg) {
			tilog.Info(ctx, "executeInCM getClusterOperator fail: call proxy timeout, fail the command", zap.Any("clusterId", c.ClusterID.Uint64()), zap.Any("err", err))
			return entity.CommandStatusFail
		}
		if strings.Contains(msg, connectionRefusedErrMsg) {
			tilog.Info(ctx, "executeInCM getClusterOperator fail: call proxy connection refused, fail the command", zap.Any("clusterId", c.ClusterID.Uint64()), zap.Any("err", err))
			return entity.CommandStatusFail
		}
		if c.FailNum > 40 && c.FailNum%5 == 0 {
			tilog.Warn(ctx, fmt.Sprintf("executeInCM getClusterOperator error: %s", msg), zap.Any("command", c), zap.Any("err", err))
		} else {
			tilog.Info(ctx, "executeInCM getClusterOperator error", zap.Any("command", c), zap.Any("err", err))
		}
		return entity.CommandStatusFail
	}

	clusterUserSvc, _ := authcenter2.GetClusterUserSvc()
	var m string
	if c.Action == entity.CommandActionCreate {
		err = createDBUser(ctx, clusterUserSvc, c, cluster, operator, userEmail)
		m = "CreateDBUser"

	} else if c.Action == entity.CommandActionDelete {
		err = infra.DeleteUser(ctx, &infra.DeleteUserParams{
			Cluster:  cluster,
			Operator: operator,
			Username: c.DbUser,
		})
		m = "DeleteDBUser"

	} else if c.Action == entity.CommandActionCheck {
		err = createDBUser(ctx, clusterUserSvc, c, cluster, operator, userEmail)
		m = "CreateDBUser"
	}
	if err != nil {
		if strings.Contains(err.Error(), timeoutErrMsg) {
			tilog.Warn(ctx, fmt.Sprintf("%s error", m), zap.Any("clusterId", cluster.Id), zap.Any("operator", operator.Username), zap.Any("command", c), zap.Any("err", err))
			return entity.CommandStatusFail
		}
		if printErrLog(c) {
			tilog.Error(ctx, fmt.Sprintf("%s error, command_id: %d, cluster_id: %d, error: %s", m, c.ID, cluster.Id, err.Error()), zap.Any("operator", operator.Username), zap.Any("command", c), zap.Any("err", err))
		} else {
			tilog.Warn(ctx, fmt.Sprintf("%s error", m), zap.Any("clusterId", cluster.Id), zap.Any("operator", operator.Username), zap.Any("command", c), zap.Any("err", err))
		}
		return entity.CommandStatusFail
	}

	tilog.Info(ctx, fmt.Sprintf("%s success", m), zap.Any("clusterId", cluster.Id), zap.Any("operator", operator.Username), zap.Any("username", c.DbUser))
	return entity.CommandStatusComplete
}

func printErrLog(c *entity.CommandDBUserCluster) bool {
	failNum := c.FailNum
	if failNum >= 20 && failNum%20 == 0 {
		return true
	}
	return false
}

func createDBUser(ctx context.Context, clusterUserSvc authcenter2.ClusterUserSvcIface, c *entity.CommandDBUserCluster, cluster *core.Cluster, operator *clusterproxy.ClusterOperator, userEmail string) error {
	var builtinRole string
	var customRoles []string
	var grants string
	if c.Priv != "" {
		var priv authcenter2.ClusterUserPriv
		err := json.Unmarshal([]byte(c.Priv), &priv)
		if err != nil {
			return err
		}
		builtinRole = priv.BuiltinRole
		customRoles = priv.CustomRoles
		grants = priv.Grants
	}
	// DbRole has the higher priority
	if c.DbRole != "" {
		dbRole := c.DbRole
		if dbRole == entity.DbRoleAdmin {
			builtinRole = infra.BuiltinRoleNameAdmin
		} else {
			var findCr = false
			for _, cr := range customRoles {
				if cr == dbRole {
					findCr = true
					break
				}
			}
			if !findCr {
				customRoles = append(customRoles, dbRole)
			}
		}
	}
	if len(customRoles) > 0 {
		for _, cr := range customRoles {
			err := infra.CheckAndCreateCustomRoleWithProxy(ctx, cluster, operator, cr)
			if err != nil {
				return err
			}
		}
	}

	credentialType := clusterproxy.AuthMethodToken
	credential := ""
	if _, ok := authcenter2.SupportClusterUserTargetType[c.TargetType]; ok {
		username := c.DbUser
		if cluster.ServerlessToken != nil && *cluster.ServerlessToken != "" {
			pre := fmt.Sprintf("%s.", *cluster.ServerlessToken)
			if strings.HasPrefix(username, pre) {
				username = strings.TrimLeft(username, pre)
			}
		}
		clusterUser, err := clusterUserSvc.GetClusterUser(ctx, c.ClusterID.Uint64(), username, 0)
		if err != nil {
			return err
		}
		if clusterUser.CredentialType == entity.CredentialTypePassword {
			credentialType = clusterproxy.AuthMethodPassword
			encryptCredential, err := authcenter2.EncryptCredential(ctx, clusterUser.Credential)
			if err != nil {
				return err
			}
			credential = encryptCredential
		}
	}

	createUserInfo := &infra.CreateUserInfo{
		//UserType: DBUserDelegate,
		UserName: c.DbUser,
		UserAttribute: map[string]string{
			"email": userEmail,
		},
		AuthMethod:        credentialType,
		EncryptCredential: credential,
		BuiltinRole:       &builtinRole,
		CustomRoles:       customRoles,
		Grants:            grants,
	}
	params := &infra.CreateUserParams{
		Cluster:        cluster,
		Operator:       operator,
		Settings:       &core.CreateDBUserReq_Settings{IgnoreExists: true},
		CreateUserInfo: createUserInfo,
	}
	return infra.CreateUser(ctx, params)
}

func getClusterOperator(ctx context.Context, dbUserSvc dbuser.DBUserIface, c *entity.CommandDBUserCluster, cluster *core.Cluster) (*clusterproxy.ClusterOperator, error) {
	clusterUserSvc, _ := authcenter2.GetClusterUserSvc()
	if c.Operator == 0 || c.Operator.Uint64() == c.UserID.Uint64() || c.FailNum > 0 {
		return clusterUserSvc.GetExistDBUser(ctx, cluster.Id)
	}

	if c.OperatorType == entity.ClusterUserTargetApiKey || c.OperatorType == entity.ClusterUserTargetUser {
		operator, err := getAndCheckCommandOperator(ctx, dbUserSvc, c, cluster)
		if err == nil {
			return operator, nil
		}
		tilog.Warn(ctx, "getClusterOperator getAndCheckCommandOperator error, go default operator select logic", zap.Any("err", err))
	}

	return clusterUserSvc.GetExistDBUser(ctx, cluster.Id)
}

func getAndCheckCommandOperator(ctx context.Context, dbUserSvc dbuser.DBUserIface, c *entity.CommandDBUserCluster, cluster *core.Cluster) (*clusterproxy.ClusterOperator, error) {
	var username, jwt *string
	var err error
	if c.OperatorType == entity.ClusterUserTargetApiKey {
		username, jwt, err = dbUserSvc.GetApiKeyOperatorDBuser(ctx, c.OrgID.Uint64(), cluster, c.Operator.Uint64())
	} else {
		username, jwt, err = dbUserSvc.GetOperatorDBuser(ctx, c.OrgID.Uint64(), cluster, c.Operator.Uint64())
	}
	if err != nil {
		return nil, err
	}
	clusterUserSvc, _ := authcenter2.GetClusterUserSvc()
	cloudAdmin, err := clusterUserSvc.GetClusterCloudAdmin(ctx, cluster.Id, 0)
	if err != nil {
		return nil, err
	}
	existing, err := dbuser.CheckDbUserExisting(ctx, cluster, cloudAdmin, *username)
	if err != nil {
		return nil, err
	}
	if !existing {
		msg := fmt.Sprintf("operator %s not exist in kernel", *username)
		return nil, errors.New(msg)
	}
	findRoles, err := dbuser.GetExistingDbUserRole(ctx, cluster, cloudAdmin, *username)
	if err != nil {
		return nil, err
	}
	var findRoleAdmin = false
	for _, fRole := range findRoles {
		if fRole == "role_admin" {
			findRoleAdmin = true
			break
		}
	}
	if !findRoleAdmin {
		msg := fmt.Sprintf("operator %s relate role %s is not role_admin", *username, findRoles)
		return nil, errors.New(msg)
	}
	return &clusterproxy.ClusterOperator{
		Username:   *username,
		AuthMethod: clusterproxy.AuthMethodToken,
		Credential: *jwt,
	}, nil
}

/*
	func checkDBUser(ctx context.Context, dbUserSrv dbuser.DBUserIface, c *entity.CommandDBUserCluster, userSrv user.UserSvcIface, userEmail string, dbUser string, operatorDBUser *string, jwtToken *string, cluster *core.Cluster) entity.CommandStatus {
		//  check dbuser
		tilog.Info(ctx, "check dbuser new thread ", zap.Any("userEmail", userEmail), zap.Any("dbUser", dbUser), zap.Any("cluster_id", c.ClusterID.Uint64()))
		status := runCheckCommand(ctx, dbUserSrv, c, userSrv, userEmail, dbUser, operatorDBUser, jwtToken, cluster)
		tilog.Info(ctx, "checkDBUser result", zap.Any("status", status), zap.Any("command", c))
		return status

		if err := dbUserSrv.UpdateCommandDBUserCluster(ctx, c.ID.Uint64(), c.ClusterID.Uint64(), c.OrgId.Uint64(), c.Operator.Uint64(), c.UserID.Uint64(), status, c.Action); err != nil {
			tilog.Error(ctx, "UpdateCommandDBUserCluster error", zap.Error(err))
		}

}
*/
/*func runCheckCommand(ctx context.Context, dbUserSrv dbuser.DBUserIface, c *entity.CommandDBUserCluster, userSrv user.UserSvcIface, userEmail string, dbUser string, operatorDBUser *string, jwtToken *string, cluster *core.Cluster) entity.CommandStatus {
	bm := utils.Begin(ctx, "runCheckCommand"+fmt.Sprintf(" [commandId=%d,dbuser=%s] ", c.ID, dbUser))
	defer bm.OnExit(ctx)
	//  check dbuser
	tilog.Info(ctx, "check dbuser", zap.Any("userEmail", userEmail), zap.Any("dbUser", dbUser), zap.Any("cluster_id", c.ClusterID.Uint64()))
	// check operator is exits
	//flag, checkOperatorStatus := infra.GetHandler().DBUserISExisting(ctx, c.ClusterID.Uint64(), *operatorDBUser, *jwtToken, *operatorDBUser)
	//if status != entity.CommandStatusComplete {
	//	tilog.Info(ctx, "IsDBUserExisting fail", zap.Any("operator", c.Operator.Uint64()), zap.Any("operatorDBUser", operatorDBUser), zap.Any("cluster_id", c.ClusterID.Uint64()))
	//	return status
	//}
	cloudAdmin, err := infra.GetClusterCloudAdmin(ctx, cluster)
	if err != nil {
		tilog.Warn(ctx, "GetClusterCloudAdmin error", zap.Any("clusterID", cluster.Id), zap.Error(err))
		return entity.CommandStatusFail
	}
	flag, err := dbuser.CheckDbUserExisting(ctx, cluster, cloudAdmin, *operatorDBUser)
	tilog.Info(ctx, "runCheckCommand checkDbUserExisting result", zap.Any("clusterID", cluster.Id), zap.Any("ownerEmail", userEmail), zap.Any("operatorDbUser", *operatorDBUser), zap.Any("existing", flag), zap.Any("err", err))
	if err != nil {
		tilog.Warn(ctx, "runCheckCommand checkDbUserExisting error", zap.Any("clusterID", cluster.Id), zap.Any("ownerEmail", userEmail), zap.Any("operatorDbUser", *operatorDBUser), zap.Any("err", err))
		return entity.CommandStatusFail
	}
	if flag {
		tilog.Info(ctx, "operator  exist ", zap.Any("operator", c.Operator.Uint64()), zap.Any("operatorDBUser", operatorDBUser), zap.Any("cluster_id", c.ClusterID.Uint64()))
		return infra.GetHandler().CreateDBUser(ctx, c.ClusterID.Uint64(), *operatorDBUser, *jwtToken, userEmail, dbUser, true, c, c.DbRole)

		//  the operator is not exits
	} else if !flag {
		tilog.Info(ctx, "operator not  exist ", zap.Any("operator", c.Operator.Uint64()), zap.Any("operatorDBUser", operatorDBUser), zap.Any("cluster_id", c.ClusterID.Uint64()))
		// operatorDBUser is not exist,find exist dbuser
		projectOwner, projectOwnerDBuser, ownerJwt, err := getExistDBuser(ctx, dbUserSrv, c.OrgID.Uint64(), cluster)
		if err != nil {
			tilog.Info(ctx, "getExistDBuser  fail ", zap.Any("check userEmail", userEmail), zap.Any("dbUser", dbUser), zap.Any("cluster_id", c.ClusterID.Uint64()))
			return entity.CommandStatusFail
		}
		if projectOwner == nil {
			tilog.Info(ctx, "getExistDBuser projectOwner nil, this command ignore", zap.Any("commandID", c.ID), zap.Any("check userEmail", userEmail), zap.Any("dbUser", dbUser), zap.Any("cluster_id", c.ClusterID.Uint64()))
			return entity.CommandStatusIgnore
		}
		// check dbuser for operatorDBUser who not exist
		notExistOperator, _ := userSrv.GetUserByID(ctx, c.Operator.Uint64())
		tilog.Info(ctx, "operator not exist, check operator ", zap.Any("check userEmail", notExistOperator.Email), zap.Any("orgId", c.OrgID.Uint64()), zap.Any("cluster_id", c.ClusterID.Uint64()))
		if err := dbUserSrv.CreateCommandUserAction(ctx, projectOwner.ID.Uint64(), c.Operator.Uint64(), notExistOperator.Email, c.OrgID.Uint64(), 0, entity.CommandStatusWaiting, entity.CommandActionCheck, entity.RoleBindingTargetUser, entity.RoleBindingTargetUser); err != nil {
			tilog.Error(ctx, "CreateCommandUserAction error", zap.Error(err))
		}
		return infra.GetHandler().CreateDBUser(ctx, c.ClusterID.Uint64(), *projectOwnerDBuser, *ownerJwt, userEmail, dbUser, true, c, c.DbRole)

	}
	return entity.CommandStatusFail
}*/

//func getExistDBuser(ctx context.Context, dbUserSrv dbuser.DBUserIface, orgId uint64, cluster *core.Cluster) (*entity.User, *string, *string, error) {
//	svc, _ := deps.GetRbacRoleBindingSvc()
//	userSvc, _ := deps.LoadUserSvc()
//
//	ownerIds, err := svc.ListUserIdByRoles(ctx, entity.RoleBindingScopeProject, cluster.ProjectId, []string{entity.RoleProjectOwner})
//
//	if err != nil {
//		tilog.Info(ctx, "checkDBUsers invoke ListUserIdByRoles fail", zap.Error(err))
//		return nil, nil, nil, err
//	}
//
//	for _, ownerId := range ownerIds {
//		//if ownerId.Uint64() != user.ID.Uint64() {
//		u, err := userSvc.GetUserByID(ctx, ownerId.Uint64())
//		if err != nil {
//			tilog.Info(ctx, "GetUserByID fail ", zap.Error(err))
//			return nil, nil, nil, err
//		}
//		if u.Status == entity.UserStatusActive {
//			operatorDBUser, jwtToken, err := dbUserSrv.GetOperatorDBuser(ctx, orgId, cluster, u.ID.Uint64())
//			if err != nil {
//				return nil, nil, nil, err
//			}
//			flag, stauts := infra.GetHandler().DBUserISExisting(ctx, cluster.Id, *operatorDBUser, *jwtToken, *operatorDBUser)
//			if stauts == entity.CommandStatusComplete && flag {
//				tilog.Info(ctx, "get exist dbuser", zap.Any("email", u.Email), zap.Any("ClusterID", cluster.Id), zap.Any("DBUser", operatorDBUser))
//				//id := ownerId.Uint64()
//				return u, operatorDBUser, jwtToken, nil
//			}
//			tilog.Info(ctx, "the dbuser is not exist", zap.Any("email", u.Email), zap.Any("dbuser", operatorDBUser), zap.Any("ClusterID", cluster.Id))
//		}
//	}
//	str := fmt.Sprintf("getExistDBuser by project, project:owner is nil, org.id=%d, cluster.id=%d,status=%s", orgId, cluster.Id, cluster.Status)
//	//}
//	tilog.Error(ctx, str)
//	return nil, nil, nil, nil
//}

// IsIgnore
//
//	@Description: ignore: this command  status = fail, and createAt >  4 hours
//	@Author wangletian 2022-12-25 23:08:32
//	@param ctx
//	@param status
//	@param createdAt
//	@param command
//	@return bool
func IsIgnore(ctx context.Context, status entity.CommandStatus, createdAt time.Time, command interface{}) bool {
	var failNum uint32
	var id uint64

	switch c := command.(type) {
	case entity.CommandDBUserCluster:
		failNum = c.FailNum
		id = c.ID.Uint64()
	default:
		failNum = 0
	}
	subMinute := int(time.Since(createdAt).Minutes())
	subSecond := int(time.Since(createdAt).Seconds())
	subHours := int(time.Since(createdAt).Hours())
	if status == entity.CommandStatusFail && subHours > config.CommandIgnoreTime {

		tilog.Info(ctx, fmt.Sprintf("this command  ignore, id=%d   status = fail, and createAt >  2 hours ，please check", id), zap.Any("subSecond", subSecond), zap.Any("subMinute", subMinute), zap.Any("id", id))
		return true
		// fail ,run every minute
	} else if status == entity.CommandStatusFail && (subMinute >= 1 && subSecond%config.FailCommandIgnoreTime != 0) {

		tilog.Info(ctx, "this command  ignore,   status = fail, and createdAt subMinute%60 == 0, "+fmt.Sprintf("id=%d", id), zap.Any("subSecond", subSecond), zap.Any("failNum", failNum),
			zap.Any("subMinute", subMinute), zap.Any("subSecond%config.FailCommandIgnoreTime", subSecond%config.FailCommandIgnoreTime != 0))

		return true
	}
	//tilog.Info(ctx, "this command  will run,"+fmt.Sprintf("command=%#v", command), zap.Any("subSecond", subSecond), zap.Any("failNum", failNum),
	//	zap.Any("subMinute", subMinute), zap.Any("flag", subSecond%config.FailCommandIgnoreTime != 0), zap.Any("command", command))
	return false
}

func isIgnoreToRunDBUserClusterCommand(ctx context.Context, command entity.CommandDBUserCluster) bool {
	if command.Status != entity.CommandStatusFail {
		tilog.Info(ctx, fmt.Sprintf("this command is not ignore, id=%d, status is not fail, direct to run", command.ID))
		return false
	}

	subHours := int(time.Since(command.CreatedAt).Hours())
	if subHours > config.CommandIgnoreTime {
		tilog.Info(ctx, fmt.Sprintf("this command is ignore, id=%d, %d hours has passed since created_at，please check", command.ID, subHours), zap.Any("created_at", command.CreatedAt))
		return true
	}
	subSeconds := int(time.Since(*command.UpdatedAt).Seconds())
	// check other server pushing
	if command.Pushing == pushing {
		if subSeconds > 300 {
			tilog.Info(ctx, fmt.Sprintf("this command is not ignore, id=%d, other gorouint has run over 300s", command.ID))
			return false
		}
		tilog.Info(ctx, fmt.Sprintf("this command is ignore, id=%d, other goroutine is pushing", command.ID), zap.Any("subSeconds", subSeconds))
		return true
	}
	if command.FailNum < 3 {
		tilog.Info(ctx, fmt.Sprintf("this command is not ignore, id=%d, fail_num is less then 3", command.ID))
		return false
	}
	if subSeconds < 60 {
		tilog.Info(ctx, fmt.Sprintf("this command is ignore, id=%d, the duration %d is less than 60 seconds since the last updated_at", command.ID, subSeconds), zap.Any("updated_at", command.UpdatedAt))
		return true
	}
	tilog.Info(ctx, fmt.Sprintf("this command is not ignore, id=%d, %d seconds has passed since the last updated_at", command.ID, subSeconds), zap.Any("updated_at", command.UpdatedAt))
	return false
}
