package api

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	dbuser2 "github.com/tidbcloud/account/internel/svc/dbuser"
	"github.com/tidbcloud/account/internel/svc/sqluser"

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

	"github.com/duke-git/lancet/slice"
	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/account/idl/pbgen/proto/central"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/tiinf/errs"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auth"
	"go.uber.org/zap"
)

const (
	cloudRoleAdmin       = "role_admin"
	tokenPrefixSeparator = "."
)

// create swag doc command: make generate_openapi_docs

// @title Sql User OPENAPI
// @version v1beta1
// @description This is the sql user related API.
// @host iam.tidbapi.com

func RegisterSqlUserRouter(e gin.IRouter) {

	r := e.Group("/v1beta1/clusters/:clusterId/sqlUsers")

	r.Use(server.LocalTestIdentity)
	if !config.IsLocalEnv() {

		pathWhitelist := []string{}
		auth.InitAccountClientByEndpoint(config.Global().GrpcConfig.AccountSvcAddr, 0)

		r.Use(
			auth.GinGetIdentityInfoInterceptor(&auth.IdentityAuthOption{
				EnableLog: true,
			}),
			auth.GinPermissionCheckInterceptor(&auth.PermissionCheckerCommonParam{
				IdentityF:               auth.GinGetIdentityFunc,
				PathPattern2ResourceMap: pathPattern2ResourceMap,
				PathWhitelist:           pathWhitelist,
				GenAccessParam4GinF:     GenAccessParam4GinWithPrefixOrgIdProjectId,
				HttpReqCheckRetHandleF:  openApiPermissionCheckResultHandleFunc,
				EnableLog:               true,
			}),
		)
		r.Use(
			auth.GinGetIdentityInfoInterceptor(&auth.IdentityAuthOption{
				EnableLog: true,
			}),
		)

	}

	r.POST("", CreateSqlUserHandler)
	r.GET("", ListSqlUserHandler)
	r.GET("/:userName", QuerySqlUserHandler)
	r.PATCH("/:userName", UpdateSqlUserHandler)
	r.DELETE("/:userName", DeleteSqlUserHandler)

}

// https://iam.tidbapi.com/v1beta1/clusters/{clusterId}/sqlUsers
// @Tags Account
// @Summary Get all sql users
// @Description  This endpoint retrieves all sql users  in the cluster.
// @Schemes
// @Accept json
// @Produce json
// @Param clusterId path string true "The id of the cluster."
// @Param pageToken query string false "The page token of the next page."
// @Param pageSize query int false "The page size of the next page. If `pageSize` is set to 0, it returns 100 records in one page."
// @Success 200 {object} ListSqlUsersRsp
// @Failure 400  {object}  OpenApiError
// @Router /v1beta1/clusters/{clusterId}/sqlUsers [GET]
func ListSqlUserHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "ListSqlUserHandler")
	defer bm.OnExit(ctx)
	uriId := SqlUserUriReq{}
	if err := c.ShouldBindUri(&uriId); err != nil {
		tilog.Warn(ctx, "ListSqluserHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid: "+err.Error())
		return
	}

	pageReq := OpenApiPageReq{}
	if err := c.ShouldBindQuery(&pageReq); err != nil {
		tilog.Warn(ctx, "ListSqluserHandler ShouldBindQuery err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid: "+err.Error())
		return
	}
	tilog.Info(ctx, "pageReq", zap.Any("pageReq", pageReq))

	operator, _, err := getCMOperator(ctx, c, uriId.ClusterId)
	if err != nil {
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, err.Error())
		return
	}
	svc, _ := sqluser.GetSqlUserSvc()
	listSqlUsersResp, err := svc.ListSqlUserFormCM(ctx, uriId.ClusterId, *operator)

	if err != nil {
		tilog.Info(ctx, "ListSqluserHandler err", zap.Error(err))
		msg := fmt.Sprintf("QuerySqlUser fail, err %s", err.Error())
		doTiErr(ctx, c, err, msg)
		return
	}
	//users := listSqlUsersResp.Items
	var users []SqlUser
	for _, user := range listSqlUsersResp.Items {
		newUser := SqlUser{
			UserName:    user.UserName,
			BuiltinRole: user.BuiltinRole,
			CustomRoles: user.CustomRole,
			AuthMethod:  user.AuthMethod,
		}

		users = append(users, newUser)
	}

	err = slice.SortByField(users, "UserName", "asc")
	if err != nil {
		tilog.Error(ctx, "SortByField err", zap.Error(err))
		return
	}
	tilog.Info(ctx, "sort sqluser", zap.Any("users", users))

	pageSize, pageToken := getPageInfo(ctx, pageReq)

	curPageToken := utils.PageToken[string]{}

	nextPageToken := utils.PageToken[string]{}

	//count := 0

	var sqlUserResp []SqlUser
	if pageToken != "" {
		if err := curPageToken.DecodeFromString(pageToken); err != nil {
			tilog.PlainInfo("invalid page token")
			return
		}
		tilog.Info(ctx, "pageToken="+curPageToken.Prev)
		for i, user := range users {
			// find  the  offset
			if user.UserName > curPageToken.Prev && len(sqlUserResp) < int(pageSize) {
				////foundPre = true
				//continue

				//if user.UserName == token.Prev || len(sqlUserResp) > 0 {
				//  the next user is exits
				sqlUserResp = append(sqlUserResp, user)
				nextPageToken.Prev = user.UserName

				// the last page
				if i == len(users)-1 {
					nextPageToken.Prev = ""
				}

				if len(sqlUserResp) == int(pageSize) {
					break
				}
			}

		}
	} else {

		// first page
		// 1、only one page
		if pageSize >= int32(len(users)) {
			//endIndex = int32(total)
			sqlUserResp = users
			nextPageToken.Prev = ""
		} else {
			// 2、more than one page
			sqlUserResp = users[0:pageSize]
			nextPageToken.Prev = users[pageSize-1].UserName
		}
	}

	resp := ListSqlUsersRsp{
		SqlUsers: sqlUserResp,
	}
	if nextPageToken.Prev != "" {
		nextPageToken, err := nextPageToken.EncodeToString()
		if err != nil {
			tilog.Info(ctx, "next page curPageToken encode failed", zap.Error(err))
		} else {
			resp.NextPageToken = nextPageToken
		}
	}
	tilog.Info(ctx, "NextPageToken result", zap.Any("nextPageToken.Prev", nextPageToken.Prev), zap.Any("base64Encode", resp.NextPageToken))
	tilog.Info(ctx, "ListSqlUsers result", zap.Any("sqlusers", sqlUserResp))
	openApiWriteResult(c.Writer, resp)
}

// https://iam.tidbapi.com/v1beta1/clusters/{clusterId}/sqlUsers/{userName}
// @Tags Account
// @Summary Delete one sql user
// @Description  This endpoint delete the sql user by user name.
// @Schemes
// @Accept json
// @Produce json
// @Param clusterId path string true "The id of the cluster."
// @Param userName path string true "The name of the sql user."
// @Success  200  {object}  BasicResp
// @Failure 400  {object}  OpenApiError
// @Router /v1beta1/clusters/{clusterId}/sqlUsers/{userName} [DELETE]
func DeleteSqlUserHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "DeleteSqlUserHandler")

	defer bm.OnExit(ctx)
	req := SqlUserUriReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		tilog.Warn(ctx, "DeleteSqlUserHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}

	if req.UserName == "" {
		tilog.Warn(ctx, "DeleteSqlUserHandler param invalid ", zap.Any("cluster_id", req.ClusterId), zap.Any("UserName", req.UserName))
		errMsg := "userName param must not null"
		writeError(ctx, c.Writer, nil, http.StatusBadRequest, paramInvalidErrorCode, errMsg)
		return

	}
	tilog.Info(ctx, "DeleteSqlUserRep", zap.Any("req", req))

	operator, _, err := getCMOperator(ctx, c, req.ClusterId)
	if err != nil {
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, err.Error())
		return
	}

	svc, _ := sqluser.GetSqlUserSvc()
	err = svc.DeleteSqlUserToCM(ctx, req.ClusterId, req.UserName, *operator)
	if err != nil {
		tilog.Info(ctx, "DeleteSqlUserHandler err", zap.Error(err))
		msg := fmt.Sprintf("DeleteSqlUser [%s] fail, err %s", req.UserName, err.Error())
		doTiErr(ctx, c, err, msg)
		return
	}
	tilog.Info(ctx, "DeleteSqlUser result success")
	//obj := &emptypb.Empty{}
	openApiWriteResult(c.Writer, DefaultSuccessResp)
}

// https://iam.tidbapi.com/v1beta1/clusters/{clusterId}/sqlUsers
// @Tags Account
// @Summary Create one sql user
// @Description  This endpoint will create one sql user int the cluster.
// @Schemes
// @Accept json
// @Produce json
// @Param clusterId path string true "The id of the cluster."
// @Param sqlUser body CreateSqlUserReq true "create sql user request"
// @Success 200 {object} SqlUser
// @Failure 400  {object}  OpenApiError
// @Router /v1beta1/clusters/{clusterId}/sqlUsers [POST]
func CreateSqlUserHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "CreateSqlUserHandler")
	defer bm.OnExit(ctx)
	uriId := SqlUserUriReq{}
	if err := c.ShouldBindUri(&uriId); err != nil {
		tilog.Warn(ctx, "CreateSqlUserHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid: "+err.Error())
		return
	}

	req := CreateSqlUserReq{}
	if err := c.ShouldBindJSON(&req); err != nil {
		tilog.Warn(ctx, "SqlUser ShouldBindJSON err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid: "+err.Error())
		return
	}
	tilog.Info(ctx, "CreateSqlUserHandler req", zap.Any("req", req))
	errMsg, err := validateCreateReq(ctx, req)
	if err != nil {
		writeError(ctx, c.Writer, nil, http.StatusBadRequest, paramInvalidErrorCode, errMsg)
		return
	}

	operator, orgId, err := getCMOperator(ctx, c, uriId.ClusterId)
	if err != nil {
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, err.Error())
		return
	}

	if err := validPrefixToken(req, operator.Cluster); err != nil {
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid: "+err.Error())
		return
	}

	errMsg, err = checkRoleValid(ctx, uriId.ClusterId, operator, req.BuiltinRole, req.CustomRoles)
	if err != nil {
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, err.Error())
		return
	}
	if errMsg != "" {
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid: "+errMsg)
		return
	}

	username := req.UserName
	// serverless username must add prefix token
	if operator.Cluster.IsDevTier && req.AutoPrefix {
		username = fmt.Sprintf("%s.%s", *operator.Cluster.ServerlessToken, req.UserName)
	}

	builtinRole := req.BuiltinRole
	// serverless role must add prefix token, role_admin not included
	if (operator.Cluster.IsDevTier && req.AutoPrefix) && (req.BuiltinRole != "" && req.BuiltinRole != cloudRoleAdmin) {
		builtinRole = fmt.Sprintf("%s.%s", *operator.Cluster.ServerlessToken, req.BuiltinRole)
	}
	if len(username) >= 32 {
		tilog.Warn(ctx, "QuerySqlUserHandler param invalid ", zap.Any("UserName", username))
		writeError(ctx, c.Writer, nil, http.StatusBadRequest, paramInvalidErrorCode, fmt.Sprintf("UserName[%s] more than 32", username))
		return
	}
	createParam := &central.CreateSqlUserReq{
		UserName:    username,
		Password:    req.Password,
		BuiltinRole: builtinRole,
		AuthMethod:  req.AuthMethod,
		CustomRole:  req.CustomRoles,
		ClusterId:   uriId.ClusterId,
		OrgId:       orgId,
	}
	svc, _ := sqluser.GetSqlUserSvc()
	err = svc.CreateSqlUserToCm(ctx, createParam, *operator)
	if err != nil {
		tilog.Info(ctx, "CreateSqlUserHandler err", zap.Error(err))
		msg := fmt.Sprintf("CreateSqlUser [%s] fail, err %s", username, err.Error())
		doTiErr(ctx, c, err, msg)
		return
	}

	// query the sqluser
	param := &central.QuerySqlUserReq{
		ClusterId: uriId.ClusterId,
		Filter: &central.QuerySqlUserFilter{
			UserNames: []string{username},
			QueryType: "USER",
		},
	}

	querySqlUserResp, err := svc.QueryFormCM(ctx, param, *operator)
	if err != nil {
		tilog.Info(ctx, "CreateSqlUserHandler err", zap.Error(err))
		msg := fmt.Sprintf("CreateSqlUser [%s] fail, err %s", username, err.Error())
		doTiErr(ctx, c, err, msg)
		return
	}

	sqlUsers := convertSqlUsers(querySqlUserResp.SqlUser)
	if len(sqlUsers) == 1 {
		tilog.Info(ctx, "QuerySqlUser result success", zap.Any("sqlUser", sqlUsers[0]))
		openApiWriteResult(c.Writer, sqlUsers[0])
	}

	tilog.Info(ctx, "CreateSqlUserHandler result success")

}

func checkRoleValid(ctx context.Context, clusterId uint64, operator *dbuser2.GetCMOperatorResp, builtinRole string, customRoles []string) (string, error) {
	if builtinRole != "" {
		bRole := builtinRole
		index := strings.Index(builtinRole, tokenPrefixSeparator)
		if index != -1 {
			bRole = builtinRole[index+1:]
		}
		if bRole != entity.DbRoleAdmin && bRole != entity.DbRoleReadWrite && bRole != entity.DbRoleReadOnly {
			return fmt.Sprintf("builtin_role %s is invalid", builtinRole), nil
		}
	}
	if len(customRoles) > 0 {
		svc, _ := sqluser.GetSqlUserSvc()
		req := &central.QuerySqlUserReq{
			ClusterId: clusterId,
			Filter: &central.QuerySqlUserFilter{
				QueryType: "ROLE",
			},
		}
		cmRsp, err := svc.QueryFormCM(ctx, req, *operator)
		if err != nil {
			return "", err
		}
		for _, cr := range customRoles {
			if !slice.Contain(cmRsp.Roles, cr) {
				return fmt.Sprintf("custom_role %s is invalid", cr), nil
			}
		}
	}

	return "", nil
}

// https://iam.tidbapi.com/v1beta1/clusters/{clusterId}/sqlUsers
// @Tags Account
// @Summary Update one sql user
// @Description  This endpoint update one sql user in the cluster.
// @Schemes
// @Accept json
// @Produce json
// @Param clusterId path string true "The id of the cluster."
// @Param userName path string true "The name of the sql user."
// @Param sqlUser body UpdateSqlUserReq true "update sql user request"
// @Success 200 {object} SqlUser
// @Failure 400  {object}  OpenApiError
// @Router /v1beta1/clusters/{clusterId}/sqlUsers/{userName} [PATCH]
func UpdateSqlUserHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "UpdateSqlUserHandler")
	defer bm.OnExit(ctx)
	uriReq := SqlUserUriReq{}
	if err := c.ShouldBindUri(&uriReq); err != nil {
		tilog.Warn(ctx, "UpdateSqlUserHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}

	req := UpdateSqlUserReq{}
	if err := c.ShouldBindJSON(&req); err != nil {
		tilog.Warn(ctx, "UpdateSqlUserHandler SqlUser err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid: "+err.Error())
		return
	}
	tilog.Info(ctx, "SqlUser", zap.Any("req", req))
	if uriReq.UserName == "" || (req.BuiltinRole == "" && req.CustomRoles == nil && req.Password == "") {
		tilog.Warn(ctx, "UpdateSqlUserHandler param invalid ")
		writeError(ctx, c.Writer, nil, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return

	}

	//tilog.Info(ctx, "req", zap.Any("req", req))
	//
	//tilog.Info(ctx, "CustomRole", zap.Any("param.CustomRole == nil", param.CustomRole == nil))
	//
	//param.CustomRole = req.Aa
	//tilog.Info(ctx, "aa=", zap.Any("param.CustomRole == nil", param.CustomRole == nil))
	//
	//param.CustomRole = req.Bb
	//tilog.Info(ctx, "bb=", zap.Any("param.CustomRole == nil", param.CustomRole == nil))
	//
	//param.CustomRole = req.Cc
	//tilog.Info(ctx, "bb=", zap.Any("param.CustomRole == nil", param.CustomRole == nil))

	operator, orgId, err := getCMOperator(ctx, c, uriReq.ClusterId)
	if err != nil {
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, err.Error())
		return
	}

	errMsg, err := checkRoleValid(ctx, uriReq.ClusterId, operator, req.BuiltinRole, req.CustomRoles)
	if err != nil {
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, err.Error())
		return
	}
	if errMsg != "" {
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid: "+errMsg)
		return
	}

	param := &central.UpdateSqlUserReq{
		OrgId:       orgId,
		ProjectId:   uriReq.ClusterId,
		UserName:    uriReq.UserName,
		Password:    req.Password,
		BuiltinRole: req.BuiltinRole,
		CustomRole:  req.CustomRoles,
	}
	tilog.Info(ctx, "SqlUser", zap.Any("param", param))
	/*
			request :  CustomRole=[]
		    gin   (req.CustomRole == nil)   =  false
		    grpc   (req.CustomRole == nil)  =  true

		    request :  do not set the parameter   CustomRole  , CustomRole=nil
		    gin   (req.CustomRole == nil)   =  true
		    grpc   (req.CustomRole == nil)  =  true
	*/

	updateCustomRoleFlag := true
	//  request do not set the parameter   CustomRole  , CustomRole=nil
	if req.CustomRoles == nil {
		updateCustomRoleFlag = false
	}
	svc, _ := sqluser.GetSqlUserSvc()

	err = svc.UpdateSqlUserToCM(ctx, param, updateCustomRoleFlag, *operator)
	if err != nil {

		tilog.Info(ctx, "UpdateSqlUser err", zap.Error(err))
		msg := fmt.Sprintf("UpdateSqlUser [%s] fail, err %s", uriReq.UserName, err.Error())
		doTiErr(ctx, c, err, msg)
		return
	}
	queryParam := &central.QuerySqlUserReq{
		ClusterId: uriReq.ClusterId,
		Filter: &central.QuerySqlUserFilter{
			UserNames: []string{uriReq.UserName},
			QueryType: "USER",
		},
	}
	querySqlUserResp, err := svc.QueryFormCM(ctx, queryParam, *operator)
	if err != nil {
		tilog.Info(ctx, "QueryFormCM err", zap.Error(err))
		msg := fmt.Sprintf("UpdateSqlUser [%s] fail, err %s", uriReq.UserName, err.Error())
		doTiErr(ctx, c, err, msg)
		return
	}
	sqlUsers := convertSqlUsers(querySqlUserResp.SqlUser)
	if len(sqlUsers) == 1 {
		tilog.Info(ctx, "UpdateSqlUser result success", zap.Any("sqlUser", sqlUsers[0]))
		openApiWriteResult(c.Writer, sqlUsers[0])
	}

}

// https://iam.tidbapi.com/v1beta1//clusters/{clusterId}/sqlUsers/{userName}
// @Tags Account
// @Summary Query sql user
// @Description  This endpoint retrieves a sql user by user name.
// @Schemes
// @Accept json
// @Produce json
// @Param clusterId path string true "The id of the cluster."
// @Param userName path string true "The name of the sql user."
// @Success 200 {object} SqlUser
// @Failure 400  {object}  OpenApiError
// @Router /v1beta1/clusters/{clusterId}/sqlUsers/{userName} [GET]
func QuerySqlUserHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "QuerySqlUserHandler")
	defer bm.OnExit(ctx)
	req := SqlUserUriReq{}
	if err := c.ShouldBindUri(&req); err != nil {
		tilog.Warn(ctx, "QuerySqlUserHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid: "+err.Error())
		return
	}

	tilog.Info(ctx, "QuerySqlUserHandler req", zap.Any("req", req))
	if req.UserName == "" {
		tilog.Warn(ctx, "QuerySqlUserHandler param invalid ", zap.Any("UserName", req.UserName))
		writeError(ctx, c.Writer, nil, http.StatusBadRequest, paramInvalidErrorCode, "userName param must not null")

		return

	}

	operator, _, err := getCMOperator(ctx, c, req.ClusterId)
	if err != nil {
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, err.Error())
		return
	}

	param := &central.QuerySqlUserReq{
		ClusterId: req.ClusterId,
		Filter: &central.QuerySqlUserFilter{
			//AuthTypes: req.AuthMethods,
			UserNames: []string{req.UserName},
			QueryType: "USER",
		},
	}

	svc, _ := sqluser.GetSqlUserSvc()

	querySqlUserResp, err := svc.QueryFormCM(ctx, param, *operator)
	if err != nil {
		httpcode := http.StatusInternalServerError
		codeStr := internalServerErrorCode
		if te, ok := errs.AsTiError(err); ok {
			httpcode = te.HTTPCode()
			codeStr = paramInvalidErrorCode
		} else {
			tilog.Error(ctx, "QuerySqlUser err", zap.Any("req", req), zap.Error(err))
		}
		tilog.Info(ctx, "QuerySqlUser err", zap.Error(err))
		writeError(ctx, c.Writer, err, httpcode, codeStr, err.Error())
		return
	}

	sqlUsers := convertSqlUsers(querySqlUserResp.SqlUser)
	if len(sqlUsers) == 1 {
		tilog.Info(ctx, "QuerySqlUser result success", zap.Any("sqlUser", sqlUsers[0]))
		openApiWriteResult(c.Writer, sqlUsers[0])
	} else if len(sqlUsers) == 0 {
		msg := req.UserName + " is not exist"
		tilog.Info(ctx, "QuerySqlUser result "+msg)
		writeError(ctx, c.Writer, nil, http.StatusBadRequest, paramInvalidErrorCode, msg)
	}

}

func getCMOperator(ctx context.Context, c *gin.Context, clusterId uint64) (*dbuser2.GetCMOperatorResp, uint64, error) {
	var orgId uint64
	authInfo, err := auth.GetIdentityInfoFromGinContext(c)
	if err != nil {
		tilog.Error(ctx, "get auth Info failed %v", zap.Error(err))
		return nil, orgId, err
	}
	var getCMOperatorReq dbuser2.GetCMOperatorReq

	// 1、call by access token
	if authInfo.IdentityType == auth.AuthnIdentityTypeAccessToken {
		orgId = authInfo.AccessTokenRsp.AccessTokenIdentity.OrgId
		userId := authInfo.AccessTokenRsp.AccessTokenIdentity.UserId
		email := authInfo.AccessTokenRsp.AccessTokenIdentity.UserEmail
		tilog.Info(ctx, "get access token info", zap.Any("orgId", orgId), zap.Any("userId", userId), zap.Any("email", email))

		getCMOperatorReq = dbuser2.GetCMOperatorReq{
			OrgId:        orgId,
			ClusterId:    clusterId,
			Email:        email,
			OperatorType: entity.RoleBindingTargetUser,
		}
		// 2、call by apikey
	} else if authInfo.IdentityType == auth.AuthnIdentityTypeApiKeyAK {

		apiKeyRsp := authInfo.ApiKeyRsp
		orgId, err = auth.GetOrgIdFromApiKeyRsp(ctx, apiKeyRsp)
		if err != nil {
			tilog.Error(ctx, "GetOrgIdFromApiKeyRsp err", zap.Error(err))
			//return nil, err
		}

		getCMOperatorReq = dbuser2.GetCMOperatorReq{
			OrgId:        orgId,
			ClusterId:    clusterId,
			AccessKey:    apiKeyRsp.ApiKey.AccessKey,
			OperatorType: entity.RoleBindingTargetAPIKey,
		}

	}
	svc, _ := dbuser2.GetDBUserSvc()
	operator, err := svc.GetCMOperator(ctx, getCMOperatorReq)
	if err != nil {
		return nil, orgId, err
	}
	return operator, orgId, nil
}

func validateCreateReq(ctx context.Context, req CreateSqlUserReq) (string, error) {

	var errMsg string
	if req.UserName == "" {
		tilog.Info(ctx, "UserName param invalid ", zap.Any("UserName", req.UserName))
		errMsg = "userName param must not null"

	}

	if req.BuiltinRole == "" && len(req.CustomRoles) == 0 {
		tilog.Info(ctx, "BuiltinRole and CustomRoles param invalid", zap.Any("BuiltinRole", req.BuiltinRole), zap.Any("CustomRoles", req.CustomRoles))
		errMsg = "At least one of BuiltinRole and CustomRoles is needed."
	}
	if req.Password == "" {
		tilog.Info(ctx, "Password param invalid ", zap.Any("Password", req.Password))
		errMsg = "password param must not null"
	}

	if req.AuthMethod == "" {
		tilog.Info(ctx, "AuthMethod param invalid ", zap.Any("AuthMethod", req.AuthMethod))
		errMsg = "authMethod param must not null"
	}

	if errMsg != "" {
		return errMsg, errs.NewBadRequest(errMsg, nil)
	}
	return errMsg, nil

}
func validPrefixToken(req CreateSqlUserReq, cluster *core.Cluster) error {
	var errMsg string
	if cluster.IsDevTier && !req.AutoPrefix {
		if req.UserName != "" {
			usernameArr := strings.Split(req.UserName, tokenPrefixSeparator)
			if usernameArr[0] != *cluster.ServerlessToken {
				errMsg = fmt.Sprintf("When AutoPrefix is false, Username must start with `%s.` ", *cluster.ServerlessToken)
			}
		}
		if req.BuiltinRole != "" && req.BuiltinRole != cloudRoleAdmin {
			builtinRoleArr := strings.Split(req.BuiltinRole, tokenPrefixSeparator)
			if builtinRoleArr[0] != *cluster.ServerlessToken {
				errMsg = fmt.Sprintf("When AutoPrefix is false, BuiltinRole must start with `%s.` ", *cluster.ServerlessToken)
			}
		}
	}

	if errMsg != "" {
		return errs.NewBadRequest(errMsg, nil)
	}
	return nil

}

func convertSqlUsers(users []*central.SqlUser) []*SqlUser {
	var sqlusers []*SqlUser
	for _, user := range users {
		u := &SqlUser{
			UserName:    user.UserName,
			BuiltinRole: user.BuiltinRole,
			CustomRoles: user.CustomRole,
			AuthMethod:  user.AuthMethod,
		}
		sqlusers = append(sqlusers, u)
	}
	return sqlusers
}
