package dbuser

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"sync"
	"time"

	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/internel/svc/authcenter"
	user2 "github.com/tidbcloud/account/internel/svc/user"

	apikeydao "github.com/tidbcloud/account/internel/dao/apikey"
	"github.com/tidbcloud/pkgv2/clusterproxy"

	"github.com/tidbcloud/tiinf/errs"

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

	"github.com/tidbcloud/account/internel/dao/role"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"

	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/account/internel/dao/dbuser"
	"github.com/tidbcloud/account/internel/dao/entity"
	"gorm.io/gorm"
)

const ORGKEY_NOT_EXIST = "the org key is not exist "
const CLUSTER_NOT_EXIST = "the cluster is not exist"

type DBUserIface interface {

	// OrgKey
	CreateOrgKey(ctx context.Context, orgId uint64, keyId string, status entity.OrgKeyStatus) error
	ListOrgKeys(ctx context.Context, filter dbuser.OrgKeyFilter) ([]entity.OrgKey, error)
	UpdateOrgKey(ctx context.Context, orgId uint64, keyId string, status entity.OrgKeyStatus) error
	DeleteOrgKey(ctx context.Context, orgId uint64) error
	GetPublicKeysByOrgId(ctx context.Context, orgId uint64) (string, error)
	GetClusterAuthPubKey(ctx context.Context, orgId, clusterId uint64, isDevTier bool) (string, error)

	// CommandOrgKey
	CreateCommandOrgKey(ctx context.Context, orgId uint64, status entity.CommandStatus, action entity.CommandAction) error
	CreateCommandOrgKeyIfNotExist(ctx context.Context, orgId uint64, status entity.CommandStatus, action entity.CommandAction) error
	ListCommandOrgKeys(ctx context.Context, filter dbuser.CommandOrgKeyFilter) ([]entity.CommandOrgKey, error)
	UpdateCommandOrgKey(ctx context.Context, orgId uint64, status entity.CommandStatus, action entity.CommandAction) error
	DeleteCommandOrgKey(ctx context.Context, orgId uint64) error

	// CommandClusterKey
	CreateCommandClusterKey(ctx context.Context, orgId uint64, clusterId uint64, status entity.CommandStatus, action entity.CommandAction) error
	ListCommandClusterKeys(ctx context.Context, filter dbuser.CommandClusterKeyFilter) ([]entity.CommandClusterKey, error)
	UpdateCommandClusterKey(ctx context.Context, e entity.CommandClusterKey) error
	DeleteCommandClusterKey(ctx context.Context, id uint64) error

	// CommandUserAction
	CreateCommandUserAction(ctx context.Context, operator uint64, userId uint64, email string, orgId uint64, projectId uint64, status entity.CommandStatus, action entity.CommandAction, targetType, operatorType entity.TargetType) error
	ListCommandUserActions(ctx context.Context, filter dbuser.CommandUserActionFilter) ([]entity.CommandUserAction, error)
	UpdateCommandUserAction(ctx context.Context, id uint64, operator uint64, userId uint64, orgId uint64, projectId uint64, status entity.CommandStatus, action entity.CommandAction) error

	// Command_DBUser_Cluster
	CreateCommandDBUserCluster(ctx context.Context, clusterId uint64, orgId uint64, operator uint64, userId uint64, dbUser string, status entity.CommandStatus, action entity.CommandAction, targetType entity.ClusterUserTargetType, dbRole string, operatorType entity.ClusterUserTargetType) error
	BatchCreateCommandDBUserCluster(ctx context.Context, objs []entity.CommandDBUserCluster) error
	CreateCommandDBUserClusterWithTime(ctx context.Context, clusterId uint64, orgId uint64, operator uint64, userId uint64, dbUser string, status entity.CommandStatus, action entity.CommandAction, targetType entity.ClusterUserTargetType, createTime time.Time, dbRole string, operatorType entity.ClusterUserTargetType) error
	ListCommandDBUserClusters(ctx context.Context, filter dbuser.CommandDBUserClusterFilter) ([]entity.CommandDBUserCluster, error)
	UpdateCommandDBUserCluster(ctx context.Context, c entity.CommandDBUserCluster) error
	UpdateCommandDBUserPushing(ctx context.Context, id uint64, to uint32) error
	UpdateCommandDBUserPushingByFrom(ctx context.Context, id uint64, from, to uint32) error

	// kms
	GetJwtToken(ctx context.Context, orgId uint64, email string, dbUser string, clusterId uint64, isDedicated bool, tokenExpiredDuration time.Duration) (string, error)
	CreateKmsKey(ctx context.Context, description string) (string, error)
	DeleteKmsKeyAndUpdateRecord(ctx context.Context, orgId uint64) error
	DeleteKmsKey(ctx context.Context, keyId string) error
	CreateKmsKeyAndUpdateRecord(ctx context.Context, orgKey entity.CommandOrgKey) error

	IsIgnoreProjectDbUser(ctx context.Context, targetID, projectID, orgID uint64) (bool, error)
	GetApiKeyOperatorDBuser(ctx context.Context, orgId uint64, cluster *core.Cluster, operatorId uint64) (*string, *string, error)
	GetOperatorDBuser(ctx context.Context, orgId uint64, cluster *core.Cluster, operatorId uint64) (*string, *string, error)
	GetCMOperator(ctx context.Context, req GetCMOperatorReq) (*GetCMOperatorResp, error)
	CheckAndCompensateDbUsers(ctx context.Context, targetType entity.TargetType, targetValue string)
	//GetExistDbUser(ctx context.Context, cluster *core.Cluster) (dbUser *string, jwtToken *string, err error)
}

var (
	dbUserSvcInitOnce sync.Once
	dbUserSvcInstance DBUserIface
)

func GetDBUserSvc() (DBUserIface, error) {
	dbUserSvcInitOnce.Do(func() {
		dbUserSvcInstance = NewDBUserSvc(database.DBConn.DB, dao.GetDaoHub())
	})
	return dbUserSvcInstance, nil
}

type DBUserSvc struct {
	db  *gorm.DB
	hub *dao.DaoHub
}

func NewDBUserSvc(db *gorm.DB, hub *dao.DaoHub) DBUserIface {

	return &DBUserSvc{
		db:  db,
		hub: hub,
	}
}

func (s *DBUserSvc) GetJwtToken(ctx context.Context, orgId uint64, email string, dbUser string, clusterId uint64, isDedicated bool, tokenExpiredDuration time.Duration) (string, error) {

	var expiredTime = config.JwtTokenExpiredTime
	if tokenExpiredDuration != 0 {
		expiredTime = tokenExpiredDuration
	}
	sub := dbUser
	tilog.Info(ctx, "GetJwtToken param", zap.Uint64("orgId", orgId),
		zap.Any("email", email),
		zap.Any("sub", sub),
		zap.Any("clusterId", clusterId),
		zap.Any("isDedicated", isDedicated))
	iss := config.Iss
	// not Dedicated user shareKey
	if !isDedicated {
		orgId = config.ShareKeyOrg
	}
	filter := dbuser.OrgKeyFilter{
		OrgIDs: []entity.ID{entity.ID(orgId)},
		Status: []entity.OrgKeyStatus{entity.OrgKeyStatusActive},
	}

	orgKeys, err := s.ListOrgKeys(ctx, filter)
	if err != nil {
		return "", err
	}
	if len(orgKeys) > 0 {
		orgKey := orgKeys[0]
		return utils.GetJwtToken(ctx, utils.GetKeyServiceSingleton(), orgKey.KeyID, email, sub, expiredTime, iss)
	}
	tilog.Info(ctx, ORGKEY_NOT_EXIST, zap.Any("orgId", orgId))
	return "", errors.New(ORGKEY_NOT_EXIST)
}

func (s *DBUserSvc) IsIgnoreProjectDbUser(ctx context.Context, targetID, projectID, orgID uint64) (bool, error) {
	bindings, err := s.hub.Role.ListRoleBindings(ctx, s.db, role.RoleBindingFilter{
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
		ScopeIDs:   []entity.ID{entity.ID(orgID)},
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		TargetIDs:  []entity.ID{entity.ID(targetID)},
		Status:     []string{entity.RoleBindingStatusActive},
		Roles:      []string{entity.RoleOrgOwner},
	})
	if err != nil {
		return false, err
	}
	if len(bindings) > 0 {
		return true, nil
	}
	return false, nil
}

func (s *DBUserSvc) GetApiKeyOperatorDBuser(ctx context.Context, orgId uint64, cluster *core.Cluster, operatorId uint64) (*string, *string, error) {

	operator, err := s.hub.ApiKey.GetApiKeyByID(ctx, s.db, operatorId)
	if err != nil {
		tilog.Info(ctx, "GetUserByID fail ,userId may not be correct", zap.Error(err), zap.Any("userId", operatorId))
		return nil, nil, err
	}
	isDedicated := !cluster.IsDevTier

	req := utils.GetDBUserReq{
		Email:           operator.AccessKey,
		IsDedicated:     &isDedicated,
		ServerlessToken: cluster.ServerlessToken,
		ClusterId:       &cluster.Id,
		TargetType:      entity.RoleBindingTargetAPIKey.String(),
	}

	operatorDBUser, err := utils.GetDBUser(ctx, req)
	tilog.Info(ctx, "utils.GetDBUser result", zap.Any("email", operator.AccessKey), zap.Any("DBUser", operatorDBUser), zap.Any("ClusterID", cluster.Id))
	if err != nil {
		return nil, nil, err
	}

	jwtToken, err := s.GetJwtToken(ctx, orgId, "", *operatorDBUser, cluster.Id, isDedicated, 0)
	if err != nil {

		tilog.Info(ctx, "dbUserSvc.GetJwtToken fail", zap.Any("email", operator.AccessKey), zap.Any("ClusterID", cluster.Id))
		return nil, nil, err
	}
	return operatorDBUser, &jwtToken, nil
}

func (s *DBUserSvc) GetOperatorDBuser(ctx context.Context, orgId uint64, cluster *core.Cluster, operatorId uint64) (*string, *string, error) {

	userSvc, _ := user2.GetUserSvc()
	operator, err := userSvc.GetUserByID(ctx, operatorId)
	if err != nil {
		tilog.Info(ctx, "GetUserByID fail ,userId may not be correct", zap.Error(err), zap.Any("userId", operatorId))
		return nil, nil, err
	}
	isDedicated := !cluster.IsDevTier
	req := utils.GetDBUserReq{
		Email:           operator.Email,
		IsDedicated:     &isDedicated,
		ServerlessToken: cluster.ServerlessToken,
		ClusterId:       &cluster.Id,
	}

	operatorDBUser, err := utils.GetDBUser(ctx, req)
	tilog.Info(ctx, "utils.GetDBUser result", zap.Any("email", operator.Email), zap.Any("DBUser", operatorDBUser), zap.Any("ClusterID", cluster.Id))
	if err != nil {
		return nil, nil, err
	}

	jwtToken, err := s.GetJwtToken(ctx, orgId, operator.Email, *operatorDBUser, cluster.Id, isDedicated, 0)
	if err != nil {

		tilog.Info(ctx, "dbUserSvc.GetJwtToken fail", zap.Any("email", operator.Email), zap.Any("ClusterID", cluster.Id))
		return nil, nil, err
	}
	return operatorDBUser, &jwtToken, nil
}
func (s *DBUserSvc) GetCMOperator(ctx context.Context, req GetCMOperatorReq) (*GetCMOperatorResp, error) {
	tilog.Info(ctx, "GetCMOperator", zap.Any(" req", req))
	userSvc, _ := user2.GetUserSvc()
	var dbuserEmail string
	jwtEmail := ""
	// user
	if req.OperatorType == entity.RoleBindingTargetUser {
		if req.Email != "" {
			dbuserEmail = req.Email
			jwtEmail = req.Email
		} else if req.Email == "" && req.UserId != 0 {
			user, err := userSvc.GetUserByID(ctx, req.UserId)
			if err != nil {
				tilog.Info(ctx, "GetUserByID fail ,userId may not be correct", zap.Error(err), zap.Any("userId", req.UserId))
				return nil, err
			}
			jwtEmail = user.Email
			dbuserEmail = user.Email
		} else {
			user, err := user2.GetUserFromContext(ctx)
			if err != nil {
				tilog.Error(ctx, "GetUserFromContext err", zap.Any("userId", req.UserId), zap.Error(err))
				return nil, err
			}
			dbuserEmail = user.Email
			jwtEmail = user.Email
		}
	} else if req.OperatorType == entity.RoleBindingTargetAPIKey && req.AccessKey != "" {
		dbuserEmail = req.AccessKey
	}

	var cluster *core.Cluster
	if req.Cluster == nil && req.ClusterId != 0 {
		c, err := infra.GetHandler().GetClusterById(ctx, req.ClusterId, true, infra.SupportDBUserStatus...)

		if err != nil {
			return nil, err
		}
		if c == nil {
			tilog.Info(ctx, "cluster is delete or not exist", zap.Any("clusterId", req.ClusterId))
			return nil, errs.NewBadRequest("cluster status is not normal", nil)
		}
		if !infra.IsSupportDBUser(ctx, c) {
			tilog.Info(ctx, "queryCluster status is error or cluster is delete", zap.Any("clusterId", req.ClusterId))
			return nil, errs.NewBadRequest("this cluster version not support SQL User", nil)
		}
		cluster = c
	} else if req.Cluster == nil && req.ClusterId == 0 {
		return nil, errs.NewBadRequest("Cluster and ClusterId cannot be all empty ", nil)
	}
	isDedicated := !cluster.IsDevTier
	getDBUserReq := utils.GetDBUserReq{
		Email:           dbuserEmail,
		IsDedicated:     &isDedicated,
		ServerlessToken: cluster.ServerlessToken,
		ClusterId:       &cluster.Id,
		TargetType:      req.OperatorType.String(),
	}

	operatorDBUser, err := utils.GetDBUser(ctx, getDBUserReq)
	tilog.Info(ctx, "utils.GetDBUser result", zap.Any("DBUser", operatorDBUser), zap.Any("getDBUserReq", getDBUserReq))
	if err != nil {
		return nil, err
	}

	jwtToken, err := s.GetJwtToken(ctx, req.OrgId, jwtEmail, *operatorDBUser, cluster.Id, isDedicated, 0)
	if err != nil {

		tilog.Info(ctx, "dbUserSvc.GetJwtToken fail", zap.Any("dbuserEmail", dbuserEmail), zap.Any("ClusterID", cluster.Id))
		return nil, err
	}

	cmOperator := &core.DBUserOperator{
		Username:   *operatorDBUser,
		AuthMethod: core.DBUser_AUTH_METHOD_TOKEN,
		Credential: jwtToken,
	}
	resp := &GetCMOperatorResp{
		DBUserOperator: cmOperator,
		Cluster:        cluster}
	return resp, nil
}

func (s *DBUserSvc) CheckAndCompensateDbUsers(ctx context.Context, targetType entity.TargetType, targetValue string) {
	bm := utils.Begin(ctx, "CheckAndCompensateDbUsers", zap.Any("targetValue", targetValue))
	defer bm.OnExit(ctx)

	if targetValue == "" {
		return
	}
	targetID, err := s.getTargetIdByTargetValue(ctx, targetType, targetValue)
	if err != nil {
		tilog.Warn(ctx, "CheckAndCompensateDbUsers getTargetIdByTargetValue error", zap.Any("err", err))
		return
	}
	toCompensateOrgIDs, err := s.getToCompensateOrgIDs(ctx, targetType, targetID)
	if err != nil {
		tilog.Warn(ctx, "CheckAndCompensateDbUsers getToCompensateOrgIDs error", zap.Any("err", err))
		return
	}
	if len(toCompensateOrgIDs) == 0 {
		return
	}

	checkTime := time.Now().Add(-time.Minute * 10)
	dbActions, err := s.hub.DBUser.ListCommandUserActions(ctx, s.db, dbuser.CommandUserActionFilter{
		UserIDs:      []entity.ID{targetID},
		OrgIDs:       toCompensateOrgIDs,
		Actions:      []entity.CommandAction{entity.CommandActionCheck},
		CreatedAtGte: &checkTime,
	})
	if err != nil {
		tilog.Warn(ctx, "CheckAndCompensateDbUsers ListCommandUserActions error", zap.Any("err", err))
		return
	}
	dbActionOrgIDMap := map[uint64]struct{}{}
	for _, a := range dbActions {
		dbActionOrgIDMap[a.OrgID.Uint64()] = struct{}{}
	}

	for _, orgID := range toCompensateOrgIDs {
		if _, ok := dbActionOrgIDMap[orgID.Uint64()]; ok {
			tilog.Info(ctx, "CheckAndCompensateDbUsers user actions existed before 10 mins, ignore", zap.Any("targetValue", targetValue), zap.Any("orgID", orgID))
			continue
		}

		err = s.hub.DBUser.CreateCommandUserAction(ctx, s.db, targetID.Uint64(), targetID.Uint64(), targetValue, orgID.Uint64(), 0,
			entity.CommandStatusWaiting, entity.CommandActionCheck, targetType, targetType)
		if err != nil {
			tilog.Warn(ctx, "CheckAndCompensateDbUsers CreateCommandUserAction fail", zap.Any("targetValue", targetValue), zap.Any("orgID", orgID), zap.Error(err))
			continue
		}
		tilog.Info(ctx, "CheckAndCompensateDbUsers success", zap.Any("targetValue", targetValue), zap.Any("orgID", orgID))

	}
}

func (s *DBUserSvc) getTargetIdByTargetValue(ctx context.Context, targetType entity.TargetType, targetValue string) (entity.ID, error) {
	if targetType == entity.RoleBindingTargetAPIKey {
		key, err := s.hub.ApiKey.GetApiKeyByAk(ctx, s.db, targetValue)
		if err != nil {
			return 0, err
		}
		if key == nil || key.Status != entity.Active {
			return 0, errors.New("key not exist")
		}
		return key.ID, nil
	}

	dbUser, err := s.hub.User.GetUserByEmail(ctx, s.db, targetValue)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			dbUser = nil
		} else {
			return 0, err
		}
	}
	if dbUser == nil || dbUser.Status != entity.UserStatusActive {
		return 0, errors.New("user not exist or status not active")
	}
	return dbUser.ID, nil
}

func (s *DBUserSvc) getToCompensateOrgIDs(ctx context.Context, targetType entity.TargetType, targetID entity.ID) ([]entity.ID, error) {
	var orgIDs []entity.ID
	roleBindings, err := s.hub.Role.ListRoleBindings(ctx, s.db, role.RoleBindingFilter{
		TargetType: []entity.TargetType{targetType},
		TargetIDs:  []entity.ID{targetID},
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
		Status:     []string{entity.RoleBindingStatusActive},
	})
	if err != nil {
		tilog.Warn(ctx, "CheckAndCompensateDbUsers ListOrgRoleBindings error", zap.Any("err", err))
		return orgIDs, err
	}
	if len(roleBindings) == 0 {
		return orgIDs, nil
	}

	for _, roleB := range roleBindings {
		orgIDs = append(orgIDs, roleB.ScopeID)
	}
	return orgIDs, nil
}

func (s *DBUserSvc) GetExistDbUser(ctx context.Context, cluster *core.Cluster) (dbUser *string, jwtToken *string, err error) {
	orgId := cluster.TenantId
	projectId := cluster.ProjectId

	roleBindings, err := s.hub.Role.ListRoleBindings(ctx, s.db, role.RoleBindingFilter{
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
		ScopeIDs:   []entity.ID{entity.ID(orgId)},
		Status:     []string{entity.RoleBindingStatusActive},
		Roles:      []string{entity.RoleOrgOwner},
	})
	if err != nil {
		tilog.Error(ctx, "GetExistDbUser ListRoleBindings error", zap.Any("clusterID", cluster.Id), zap.Error(err))
	}
	projectRoleBindings, err := s.hub.Role.ListRoleBindings(ctx, s.db, role.RoleBindingFilter{
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeProject},
		ScopeIDs:   []entity.ID{entity.ID(projectId)},
		Status:     []string{entity.RoleBindingStatusActive},
		Roles:      []string{entity.RoleProjectOwner},
	})
	if err != nil {
		tilog.Error(ctx, "GetExistDbUser ListProjectRoleBindings error", zap.Any("clusterID", cluster.Id), zap.Error(err))
	}
	// find open api key
	apiKeyRoleBindings, err := s.hub.Role.ListRoleBindings(ctx, s.db, role.RoleBindingFilter{
		TargetType: []entity.TargetType{entity.RoleBindingTargetAPIKey},
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
		ScopeIDs:   []entity.ID{entity.ID(orgId)},
		Status:     []string{entity.RoleBindingStatusActive},
		Roles:      []string{entity.RoleApiKeyOrgOwner, entity.RoleOrgOwner},
	})
	if err != nil {
		tilog.Error(ctx, "GetExistDbUser ListApiKeyRoleBindings error", zap.Any("clusterID", cluster.Id), zap.Error(err))
	}

	if len(roleBindings) == 0 && len(projectRoleBindings) == 0 && len(apiKeyRoleBindings) == 0 {
		return nil, nil, errors.New("not found org/project/apikey owner")
	}

	// find active user
	var orgOwnerIdMap = map[uint64]struct{}{}
	var ownerIds []uint64
	for _, r := range roleBindings {
		ownerIds = append(ownerIds, r.TargetID.Uint64())
		orgOwnerIdMap[r.TargetID.Uint64()] = struct{}{}
	}
	for _, r := range projectRoleBindings {
		if _, ok := orgOwnerIdMap[r.TargetID.Uint64()]; !ok {
			ownerIds = append(ownerIds, r.TargetID.Uint64())
		}
	}
	ownerUsers, err := s.hub.User.ListUsers(ctx, s.db, ownerIds, []entity.UserStatus{entity.UserStatusActive})
	if err != nil {
		tilog.Warn(ctx, "GetExistDbUser ListUsers error", zap.Any("clusterID", cluster.Id), zap.Error(err))
		return nil, nil, err
	}

	// find active api key
	var apikeyIds []uint64
	for _, r := range apiKeyRoleBindings {
		apikeyIds = append(apikeyIds, r.TargetID.Uint64())
	}
	var ownerApiKeys []entity.ApiKey
	if len(apikeyIds) > 0 {
		param := apikeydao.ListApiKeysParam{
			Filter: apikeydao.ApiKeyFilter{
				ID: apikeyIds,
			},
		}
		keys, err := s.hub.ApiKey.ListApiKeys(ctx, s.db, &param)
		if err != nil {
			tilog.Warn(ctx, "GetExistDbUser ListApiKeys error", zap.Any("clusterID", cluster.Id), zap.Error(err))
			return nil, nil, err
		}
		if keys != nil && len(keys.List) > 0 {
			ownerApiKeys = keys.List
		}
	}

	var operatorCandidates []OperateCandidate
	if len(ownerUsers) > 0 {
		for _, ou := range ownerUsers {
			candidate := OperateCandidate{
				TargetType: entity.RoleBindingTargetUser,
				TargetID:   ou.ID.Uint64(),
				TargetVal:  ou.Email,
			}
			operatorCandidates = append(operatorCandidates, candidate)
		}
	}
	if len(ownerApiKeys) > 0 {
		for _, ok := range ownerApiKeys {
			candidate := OperateCandidate{
				TargetType: entity.RoleBindingTargetAPIKey,
				TargetID:   ok.ID.Uint64(),
				TargetVal:  ok.AccessKey,
			}
			operatorCandidates = append(operatorCandidates, candidate)
		}
	}
	if len(operatorCandidates) == 0 {
		tilog.Info(ctx, "GetExistDbUser not found operatorCandidate", zap.Any("clusterID", cluster.Id))
		return nil, nil, errors.New("not found active owner")
	}

	//cloudAdmin, err := infra.GetClusterCloudAdmin(ctx, cluster)
	clusterUserSvc, _ := authcenter.GetClusterUserSvc()
	cloudAdmin, err := clusterUserSvc.GetClusterCloudAdmin(ctx, cluster.Id, 0)
	if err != nil {
		// check cluster not exist err and return dbuser.CLUSTER_NOT_EXIST error msg
		tilog.Error(ctx, "GetExistDbUser GetClusterCloudAdmin error", zap.Any("clusterID", cluster.Id), zap.Error(err))
		return nil, nil, err
	}

	var findExistingDbUsers []ExistingDBUser
	var findRoleAdminDbUser *ExistingDBUser
	var kerr error
	for _, u := range operatorCandidates {
		flag := !cluster.IsDevTier
		req := utils.GetDBUserReq{
			Email:           u.TargetVal,
			ClusterId:       &cluster.Id,
			IsDedicated:     &flag,
			ServerlessToken: cluster.ServerlessToken,
		}
		if u.TargetType == entity.RoleBindingTargetAPIKey {
			req.TargetType = entity.RoleBindingTargetAPIKey.String()
		}
		operatorDbUser, err := utils.GetDBUser(ctx, req)
		if err != nil {
			tilog.Warn(ctx, "GetExistDbUser GetDBUser error", zap.Any("target_val", u.TargetVal), zap.Any("err", err))
			kerr = err
			continue
		}
		existing, err := CheckDbUserExisting(ctx, cluster, cloudAdmin, *operatorDbUser)
		tilog.Info(ctx, "GetExistDbUser CheckDbUserExisting result", zap.Any("clusterID", cluster.Id), zap.Any("target_val", u.TargetVal), zap.Any("dbUser", *operatorDbUser), zap.Any("existing", existing), zap.Any("err", err))
		if err != nil {
			kerr = err
			continue
		}
		if !existing {
			continue
		}
		email := u.TargetVal
		if u.TargetType == entity.RoleBindingTargetAPIKey {
			email = ""
		}
		operatorJwtToken, err := s.GetJwtToken(ctx, orgId, email, *operatorDbUser, cluster.Id, flag, 0)
		if err != nil {
			tilog.Warn(ctx, "GetExistDbUser GetJwtToken error", zap.Any("email", email), zap.Any("dbUser", *operatorDbUser), zap.Any("err", err))
			kerr = err
			continue
		}
		findExistingDbUsers = append(findExistingDbUsers, ExistingDBUser{Email: u.TargetVal, Username: operatorDbUser, JwtToken: &operatorJwtToken})

		// check role is role_admin
		findRoles, err := GetExistingDbUserRole(ctx, cluster, cloudAdmin, *operatorDbUser)
		tilog.Info(ctx, "GetExistDbUser GetExistingDbUserRole result", zap.Any("clusterID", cluster.Id), zap.Any("target_val", u.TargetVal), zap.Any("dbUser", *operatorDbUser), zap.Any("findRoles", findRoles), zap.Any("err", err))
		if err != nil {
			kerr = err
			continue
		}
		var findRoleAdmin = false
		for _, fRole := range findRoles {
			if fRole == "role_admin" {
				findRoleAdmin = true
				break
			}
		}
		if findRoleAdmin {
			findRoleAdminDbUser = &ExistingDBUser{
				Email:    u.TargetVal,
				Username: operatorDbUser,
				JwtToken: &operatorJwtToken,
			}
			break
		}
	}

	if findRoleAdminDbUser != nil {
		tilog.Info(ctx, "GetExistDbUser find db user: role admin", zap.Any("clusterID", cluster.Id), zap.Any("dbUser", findRoleAdminDbUser.Username), zap.Any("email", findRoleAdminDbUser.Email))
		return findRoleAdminDbUser.Username, findRoleAdminDbUser.JwtToken, nil
	}
	dbUserCount := len(findExistingDbUsers)
	if dbUserCount == 0 {
		tilog.Info(ctx, "GetExistDbUser not find db user", zap.Any("clusterID", cluster.Id), zap.Any("kerr", kerr))
		if kerr != nil {
			return nil, nil, kerr
		}
		return nil, nil, errors.New("not found db user")
	}
	rand.Seed(time.Now().UnixNano())
	index := rand.Intn(dbUserCount)
	existingDBUser := findExistingDbUsers[index]
	tilog.Info(ctx, "GetExistDbUser find db user: not role admin", zap.Any("clusterID", cluster.Id), zap.Any("dbUser", existingDBUser.Username), zap.Any("email", existingDBUser.Email))
	return existingDBUser.Username, existingDBUser.JwtToken, nil
}

type ExistingDBUser struct {
	Email    string
	Username *string
	JwtToken *string
}

type OperateCandidate struct {
	TargetType entity.TargetType
	TargetID   uint64
	TargetVal  string
}

func CheckDbUserExisting(ctx context.Context, cluster *core.Cluster, operator *clusterproxy.ClusterOperator, dbUser string) (bool, error) {
	queryUserSql := fmt.Sprintf("SELECT USER FROM MYSQL.USER WHERE USER = '%s'", dbUser)
	result, err := infra.ExecQuerySql(ctx, cluster, operator, queryUserSql)
	if err != nil {
		return false, err
	}
	sqlResultMap := map[string][]string{}
	if err = json.Unmarshal([]byte(result), &sqlResultMap); err != nil {
		return false, err
	}
	usersArr := sqlResultMap["USER"]
	if len(usersArr) == 0 {
		return false, nil
	}
	return true, nil
}

func GetExistingDbUserRole(ctx context.Context, cluster *core.Cluster, operator *clusterproxy.ClusterOperator, dbUser string) ([]string, error) {
	queryRoleSql := fmt.Sprintf("SELECT FROM_USER FROM MYSQL.ROLE_EDGES WHERE TO_USER = '%s'", dbUser)
	result, err := infra.ExecQuerySql(ctx, cluster, operator, queryRoleSql)
	if err != nil {
		return []string{}, err
	}
	sqlResultMap := map[string][]string{}
	if err = json.Unmarshal([]byte(result), &sqlResultMap); err != nil {
		return []string{}, err
	}
	rolesArr := sqlResultMap["FROM_USER"]
	if len(rolesArr) == 0 {
		return []string{}, nil
	}
	var roles []string
	for _, role := range rolesArr {
		roleByte, err := base64.StdEncoding.DecodeString(role)
		if err != nil {
			tilog.Warn(ctx, "getExistingDbUserRole decode role error", zap.Any("role", role))
			continue
		}
		findRole := string(roleByte)
		roles = append(roles, findRole)
	}
	return roles, nil
}
