package api

import (
	"bytes"
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"net/http"
	"sort"
	"strconv"
	"strings"

	"github.com/tidbcloud/account/internel/svc/comsvc"
	"github.com/tidbcloud/tiinf/errs"

	"github.com/gin-gonic/gin"
	pingcaperr "github.com/pingcap/errors"
	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/account/internel/dao/entity"
	apikey2 "github.com/tidbcloud/account/internel/svc/apikey"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

// create swag doc command: project root directory exec the command
// swag init -d central/api/ -g openapi_apikey.go --tags "API Key" --output central/docs/openapi/apikey

// @title Account System OPENAPI
// @version v1beta1
// @description This is an account system server.
// @host iam.tidbapi.com
// @BasePath /v1beta1
func RegisterApiKeyRouter(e gin.IRouter) {
	r := e.Group("/v1beta1/apikeys")

	r.Use(openApiRouteAuthNFunc)

	r.POST("", CreateApiKeyHandler)
	r.GET("/:accessKey", GetApiKeyHandler)
	r.GET("", ListApiKeysHandler)
	r.PATCH("/:accessKey", PatchApiKeyHandler)
	r.DELETE("/:accessKey", DeleteApiKeyHandler)
	// The first version does not expose the roles interface, set @Tags to API Key to expose the interface
	r.GET("/roles", ListApiKeyRolesHandler)
	// temporarily for qa use
	r.PUT("/:accessKey/roles", UpdateApiKeyRoleHandler)

}

// @Tags API Key
// @Summary Create a new API key
// @Description  This endpoint creates a new API key.
// @Schemes
// @Accept json
// @Produce json
// @Param apiKey body OpenApiCreateApiKeyReq true "create API key request"
// @Success 200 {object} OpenApiApiKey
// @Failure 400  {object}  OpenApiError
// @Router /apikeys [POST]
func CreateApiKeyHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "CreateApiKeyHandler")
	defer bm.OnExit(ctx)

	req := OpenApiCreateApiKeyReq{}
	if err := c.ShouldBindJSON(&req); err != nil {
		tilog.Info(ctx, "CreateApiKeyHandler ShouldBindJSON err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}
	tilog.Info(ctx, "CreateApiKeyHandler request", zap.Any("req", req))

	keyOperator, orgId, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "CreateApiKeyHandler getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	var scopeType = entity.RoleBindingScopeOrg.String()
	var resourceUri = createApiKeyResourceUri
	if req.ProjectId != 0 {
		scopeType = entity.RoleBindingScopeProject.String()
		resourceUri = createProjectApiKeyResourceUri
	}

	hasPermission, err := checkAccessKeyPermission(ctx, keyOperator.OpVal, resourceUri, orgId, req.ProjectId)
	if err != nil {
		tilog.Info(ctx, "CreateApiKeyHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "CreateApiKeyHandler ak has no permission", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}

	status, code, desc, err := checkApiKeyDisplayNameRole(ctx, req.DisplayName, req.Role, scopeType, true)
	if status != http.StatusOK {
		tilog.Info(ctx, "CreateApiKeyHandler checkApiKeyDisplayNameRole not pass", zap.Any("status", status), zap.Any("desc", desc), zap.Any("err", err))
		writeError(ctx, c.Writer, err, status, code, desc)
		return
	}

	attrs := &apikey2.ApiKeyAttr{
		Source:      "account_openapi",
		Type:        entity.ApiKeyTypeNormalKey,
		Description: req.DisplayName,
	}
	assignRoleParam := &comsvc.AssignIdentRolesParam{
		OrgID: orgId,
	}
	if req.ProjectId != 0 {
		var oRole = entity.RoleOrgMember
		assignRoleParam.ORole = &oRole
		assignRoleParam.PRoles = map[uint64]string{
			req.ProjectId: req.Role,
		}
	} else {
		assignRoleParam.ORole = &req.Role
	}
	param := &apikey2.CreateOneConsoleApiKeyParam{
		Attrs:           attrs,
		AssignRoleParam: assignRoleParam,
	}
	keySvc, _ := apikey2.GetApiKeySvc()
	key, err := keySvc.CreateOneConsoleApiKey(ctx, param, keyOperator)
	if err != nil {
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		tilog.Error(ctx, "CreateApiKeyHandler CreateApiKey error", zap.Any("param", param), zap.Any("err", err))
		return
	}

	keyRet := convOneConsoleKey2OpenApiKey(key, orgId, req.ProjectId, req.Role, false)
	tilog.Info(ctx, "CreateApiKeyHandler response", zap.Any("rsp", keyRet))
	openApiWriteResult(c.Writer, &keyRet)
}

func checkApiKeyDisplayNameRole(_ context.Context, displayName, role string, scopeType string, checkEmpty bool) (status int, code, desc string, err error) {
	if checkEmpty && displayName == "" {
		return http.StatusBadRequest, paramInvalidErrorCode, "display_name is empty", nil
	}
	if len(displayName) > 500 {
		return http.StatusBadRequest, paramInvalidErrorCode, "display_name's length exceeds the maximum length of 500 characters", nil
	}
	if checkEmpty && role == "" {
		return http.StatusBadRequest, paramInvalidErrorCode, "role is empty", nil
	}
	if role != "" {
		if !apikey2.IsValidApiKeyRole(scopeType, role) {
			return http.StatusBadRequest, paramInvalidErrorCode, "role invalid", nil
		}
	}
	return http.StatusOK, "", "", nil
}

// @Tags API Key
// @Summary Get a single API key
// @Description  This endpoint retrieves a single API key.
// @Schemes
// @Accept json
// @Produce json
// @Param accessKey path string true "The access key of the API key."
// @Success 200 {object} OpenApiApiKey
// @Failure 400  {object}  OpenApiError
// @Router /apikeys/{accessKey} [GET]
func GetApiKeyHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "GetApiKeyHandler")
	defer bm.OnExit(ctx)

	req := OpenApiAccessKey{}
	if err := c.ShouldBindUri(&req); err != nil {
		tilog.Info(ctx, "GetApiKeyHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}
	tilog.Info(ctx, "GetApiKeyHandler request", zap.Any("req", req))

	svc, _ := apikey2.GetApiKeySvc()
	key, err := svc.GetApiKeyByAccessKey(ctx, req.AccessKey)
	if err != nil {
		tilog.Error(ctx, "GetApiKeyHandler GetApiKeyByAccessKey error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, paramInvalidErrorCode, "internal server error")
		return
	}
	if key == nil {
		tilog.Warn(ctx, "GetApiKeyHandler GetApiKeyByAccessKey return nil")
		writeError(ctx, c.Writer, err, http.StatusNotFound, paramInvalidErrorCode, "key not found")
		return
	}

	keyOperator, _, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "GetApiKeyHandler getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, paramInvalidErrorCode, "internal server error")
		return
	}
	orgId, projectId := getApiKeyRefOrgIdProjectId(key.ResourceInfos)
	var resourceUri = getApiKeyResourceUri
	if projectId != 0 {
		resourceUri = getProjectApiKeyResourceUri
	}
	hasPermission, err := checkAccessKeyPermission(ctx, keyOperator.OpVal, resourceUri, orgId, projectId)
	if err != nil {
		tilog.Info(ctx, "GetApiKeyHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "GetApiKeyHandler ak has no permission", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}

	role := getApiKeyRole(key.ResourceInfos, orgId, projectId)
	if role == "" {
		tilog.Warn(ctx, "GetApiKeyHandler getApiKeyRole empty")
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "not found key in org")
		return
	}

	keyRet := convOpenApiKey(key, orgId, projectId, role, true)
	tilog.Info(ctx, "GetApiKeyHandler response", zap.Any("rsp", keyRet))
	openApiWriteResult(c.Writer, &keyRet)
}

func convOneConsoleKey2OpenApiKey(key *apikey2.OneConsoleApiKey, orgId, projectId uint64, role string, maskEncSecret bool) OpenApiApiKey {
	secretKey := key.SecretKey
	if maskEncSecret {
		// secret key is encrypt
		decryptKey, _ := apikey2.GetKeyUtil().DecryptKey(secretKey)
		secretKey = maskSecretKey(decryptKey)
	}
	if role == entity.RoleApiKeyOrgOwner {
		role = entity.RoleOrgOwner
	}
	var name = fmt.Sprintf("orgs/%d/apiKeys/%d", orgId, key.Id)
	if projectId != 0 {
		name = fmt.Sprintf("orgs/%d/projects/%d/apiKeys/%d", orgId, projectId, key.Id)
	}
	return OpenApiApiKey{
		Name:        name,
		AccessKey:   key.AccessKey,
		SecretKey:   secretKey,
		DisplayName: key.Description,
		Role:        role,
	}
}

func convOpenApiKey(key *apikey2.ApiKey, orgId, projectId uint64, role string, maskEncSecret bool) OpenApiApiKey {
	secretKey := key.SecretKey
	if maskEncSecret {
		// secret key is encrypt
		decryptKey, _ := apikey2.GetKeyUtil().DecryptKey(secretKey)
		secretKey = maskSecretKey(decryptKey)
	}
	if role == entity.RoleApiKeyOrgOwner {
		role = entity.RoleOrgOwner
	}
	var name = fmt.Sprintf("orgs/%d/apiKeys/%d", orgId, key.Id)
	if projectId != 0 {
		name = fmt.Sprintf("orgs/%d/projects/%d/apiKeys/%d", orgId, projectId, key.Id)
	}
	return OpenApiApiKey{
		Name:        name,
		AccessKey:   key.AccessKey,
		SecretKey:   secretKey,
		DisplayName: key.Description,
		Role:        role,
	}
}

func maskSecretKey(secret string) string {
	l := len(secret)
	if l <= 4 {
		return secret
	}
	var buffer bytes.Buffer
	for i := 0; i < l-4; i++ {
		buffer.WriteString("*")
	}
	buffer.WriteString(secret[l-4 : l])

	return buffer.String()
}

func getApiKeyRefOrgIdProjectId(resourceInfos []*apikey2.ApiKeyResourceInfo) (uint64, uint64) {
	if len(resourceInfos) == 0 {
		return 0, 0
	}
	var orgId, projectId uint64 = 0, 0
	for _, r := range resourceInfos {
		if r.ScopeType == entity.RoleBindingScopeOrg.String() {
			orgId = r.ResourceId
		}
		if r.ScopeType == entity.RoleBindingScopeProject.String() {
			projectId = r.ResourceId
		}
	}
	return orgId, projectId
}

func getApiKeyRole(resourceInfos []*apikey2.ApiKeyResourceInfo, orgId, projectId uint64) string {
	if len(resourceInfos) == 0 {
		return ""
	}
	for _, r := range resourceInfos {
		if projectId != 0 {
			if r.ScopeType == entity.RoleBindingScopeProject.String() && r.ResourceId == projectId {
				return r.Role
			}
		} else if r.ScopeType == entity.RoleBindingScopeOrg.String() && r.ResourceId == orgId {
			return r.Role
		}
	}
	return ""
}

// @Tags API Key
// @Summary Get a list of API keys
// @Description  This endpoint returns a list of API keys.
// @Schemes
// @Accept json
// @Produce json
// @Param projectId query int false "The project to which returned API key belongs. If not set, return the API keys belongs to org."
// @Param pageToken query string false "A page token received from a previous call. Provide this to retrieve the subsequent page."
// @Param pageSize query int false "The page size. If unspecified, at most 100 API keys will be returned in one page."
// @Success 200 {object} OpenApiListApiKeyRsp
// @Failure 400  {object}  OpenApiError
// @Router /apikeys [GET]
func ListApiKeysHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "ListApiKeysHandler")
	defer bm.OnExit(ctx)

	req := OpenApiListApiKeysPageReq{}
	if err := c.ShouldBindQuery(&req); err != nil {
		tilog.Info(ctx, "ListApiKeysHandler ShouldBindQuery err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}
	tilog.Info(ctx, "ListApiKeysHandler request", zap.Any("req", req))

	keyOperator, orgId, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "ListApiKeysHandler getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	var resourceUri = listApiKeyResourceUri
	if req.ProjectId != 0 {
		resourceUri = listProjectApiKeyResourceUri
	}
	hasPermission, err := checkAccessKeyPermission(ctx, keyOperator.OpVal, resourceUri, orgId, req.ProjectId)
	if err != nil {
		tilog.Info(ctx, "ListApiKeysHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "ListApiKeysHandler ak has no permission", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}

	pageSize, pageToken := getPageInfo(ctx, OpenApiPageReq{
		PageSize:           req.PageSize,
		PageSizeUnderline:  req.PageSizeUnderline,
		PageToken:          req.PageToken,
		PageTokenUnderline: req.PageTokenUnderline,
	})
	prevId, err := getPrevIdFromPageToken(pageToken)
	if err != nil {
		tilog.Info(ctx, "ListApiKeysHandler getPrevIdFromPageToken err", zap.Any("pageToken", pageToken), zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}

	var scopeType = entity.RoleBindingScopeOrg.String()
	var resourceId = orgId
	if req.ProjectId != 0 {
		scopeType = entity.RoleBindingScopeProject.String()
		resourceId = req.ProjectId
	}
	param := apikey2.ListKeysByResourceIDParam{
		ScopeType:  scopeType,
		ResourceId: resourceId,
	}
	svc, _ := apikey2.GetApiKeySvc()
	keys, err := svc.ListApiKeysByResourceID(ctx, &param)
	if err != nil {
		tilog.Error(ctx, "ListApiKeysHandler ListApiKeysByResourceID error", zap.Any("orgId", orgId), zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	items := keys.Items
	sort.Slice(items, func(i, j int) bool {
		return items[i].Id < items[j].Id
	})

	var retKeys []OpenApiApiKey
	var count int32 = 0
	var curId uint64 = 0
	for _, k := range items {
		// prevId == 0 means from start
		if prevId != 0 && k.Id <= prevId {
			continue
		}

		role := getApiKeyRole(k.ResourceInfos, orgId, req.ProjectId)
		if role == "" {
			tilog.Warn(ctx, "ListApiKeysHandler getApiKeyRole empty, ignore", zap.Any("key", k.AccessKey), zap.Any("orgId", orgId))
			continue
		}
		rk := convOpenApiKey(k, orgId, req.ProjectId, role, true)
		retKeys = append(retKeys, rk)

		curId = k.Id
		count++
		if pageSize != 0 && count >= pageSize {
			break
		}
	}

	nextPageToken := ""
	if curId != 0 && curId != items[len(items)-1].Id {
		nextPageToken = createNextPageToken(curId)
	}

	rsp := OpenApiListApiKeyRsp{
		ApiKeys:       retKeys,
		NextPageToken: nextPageToken,
	}
	tilog.Info(ctx, "ListApiKeysHandler response", zap.Any("rsp", rsp))
	openApiWriteResult(c.Writer, &rsp)
}

const (
	prev = "prev"
)

func createNextPageToken(id uint64) string {
	str := fmt.Sprintf("%s:%d", prev, id)
	return base64.StdEncoding.EncodeToString([]byte(str))
}

func getPrevIdFromPageToken(pageToken string) (uint64, error) {
	if pageToken == "" {
		return 0, nil
	}
	decoded, err := base64.StdEncoding.DecodeString(pageToken)
	if err != nil {
		return 0, err
	}
	parts := strings.Split(string(decoded), ":")
	if len(parts) != 2 || parts[0] != prev {
		return 0, errors.New("bad page token")
	}
	prevId, err := strconv.ParseUint(parts[1], 10, 64)
	if err != nil {
		return 0, err
	}
	return prevId, nil
}

// @Tags API Key
// @Summary Update an API key
// @Description  This endpoint updates an API key by overwriting the specified values.
// @Schemes
// @Accept json
// @Produce json
// @Param accessKey path string true "The access key of the API key to update."
// @Param apiKey body OpenApiUpdateApiKeyReq true "update api key"
// @Success 200
// @Failure 400  {object}  OpenApiError
// @Router /apikeys/{accessKey} [PATCH]
func PatchApiKeyHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "PatchApiKeyHandler")
	defer bm.OnExit(ctx)

	req := OpenApiUpdateApiKeyReq{}
	if err := c.ShouldBindJSON(&req); err != nil {
		tilog.Info(ctx, "PatchApiKeyHandler ShouldBindJSON err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}
	tilog.Info(ctx, "PatchApiKeyHandler request", zap.Any("req", req))
	keyReq := OpenApiAccessKey{}
	if err := c.ShouldBindUri(&keyReq); err != nil {
		tilog.Info(ctx, "PatchApiKeyHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}

	if req.DisplayName == "" && req.Role == "" {
		writeError(ctx, c.Writer, nil, http.StatusBadRequest, paramInvalidErrorCode, "at least one of display_name and role needs to be non-empty")
		return
	}
	var scopeType = entity.RoleBindingScopeOrg.String()
	if req.ProjectId != 0 {
		scopeType = entity.RoleBindingScopeProject.String()
	}
	status, code, desc, err := checkApiKeyDisplayNameRole(ctx, req.DisplayName, req.Role, scopeType, false)
	if status != http.StatusOK {
		tilog.Info(ctx, "PatchApiKeyHandler checkApiKeyDisplayNameRole not pass", zap.Any("status", status), zap.Any("desc", desc), zap.Any("err", err))
		writeError(ctx, c.Writer, err, status, code, desc)
		return
	}

	svc, _ := apikey2.GetApiKeySvc()
	key, err := svc.GetApiKeyByAccessKey(ctx, keyReq.AccessKey)
	if err != nil {
		tilog.Error(ctx, "PatchApiKeyHandler GetApiKeyByAccessKey err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	if key == nil {
		tilog.Warn(ctx, "PatchApiKeyHandler GetApiKeyByAccessKey return nil")
		writeError(ctx, c.Writer, err, http.StatusNotFound, paramInvalidErrorCode, "key not found")
		return
	}

	keyOperator, _, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "PatchApiKeyHandler getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	orgId, projectId := getApiKeyRefOrgIdProjectId(key.ResourceInfos)
	if req.ProjectId != 0 && req.ProjectId != projectId {
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "project_id invalid")
		return
	}
	var resourceUri = patchApiKeyResourceUri
	if req.ProjectId != 0 {
		resourceUri = patchProjectApiKeyResourceUri
	}
	hasPermission, err := checkAccessKeyPermission(ctx, keyOperator.OpVal, resourceUri, orgId, projectId)
	if err != nil {
		tilog.Info(ctx, "PatchApiKeyHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "PatchApiKeyHandler ak has no permission", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}

	param := &apikey2.UpdateApiKeyParam{
		ApiKeyId:     key.Id,
		Description:  req.DisplayName,
		Operator:     keyOperator.OpVal,
		OperatorType: entity.OperatorTypeApiKey,
	}
	if req.Role != "" {
		param.Role = req.Role
		if req.ProjectId != 0 {
			param.ScopeType = entity.RoleBindingScopeProject.String()
			param.ResourceIds = []uint64{req.ProjectId}
		} else {
			param.ScopeType = entity.RoleBindingScopeOrg.String()
			param.ResourceIds = []uint64{orgId}
		}
	}

	err = svc.UpdateApiKey(ctx, param)
	if err != nil {
		tilog.Error(ctx, "PatchApiKeyHandler UpdateApiKey err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}

	tilog.Info(ctx, "PatchApiKeyHandler UpdateApiKey success", zap.Any("req", req))

}

// @Tags API Key
// @Summary Delete an API key
// @Description This endpoint deletes an API key.
// @Schemes
// @Accept json
// @Produce json
// @Param accessKey path string true "The access key of the API key to delete."
// @Success 200
// @Failure 400  {object}  OpenApiError
// @Router /apikeys/{accessKey} [DELETE]
func DeleteApiKeyHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "DeleteApiKeyHandler")
	defer bm.OnExit(ctx)

	req := OpenApiAccessKey{}
	if err := c.ShouldBindUri(&req); err != nil {
		tilog.Info(ctx, "DeleteApiKeyHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}

	svc, _ := apikey2.GetApiKeySvc()
	key, err := svc.GetApiKeyByAccessKey(ctx, req.AccessKey)
	if err != nil {
		tilog.Error(ctx, "DeleteApiKeyHandler GetApiKeyByAccessKey error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	if key == nil {
		tilog.Warn(ctx, "DeleteApiKeyHandler GetApiKeyByAccessKey return nil")
		writeError(ctx, c.Writer, err, http.StatusNotFound, paramInvalidErrorCode, "key not found")
		return
	}

	keyOperator, _, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "DeleteApiKeyHandler getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	orgId, projectId := getApiKeyRefOrgIdProjectId(key.ResourceInfos)
	var resourceUri = deleteApiKeyResourceUri
	if projectId != 0 {
		resourceUri = deleteProjectApiKeyResourceUri
	}
	hasPermission, err := checkAccessKeyPermission(ctx, keyOperator.OpVal, resourceUri, orgId, projectId)
	if err != nil {
		tilog.Info(ctx, "DeleteApiKeyHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "DeleteApiKeyHandler ak has no permission", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}

	if keyOperator.OpVal == req.AccessKey {
		tilog.Info(ctx, "DeleteApiKeyHandler can not delete itself", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "the key can not delete itself")
		return
	}

	param := apikey2.DeleteApiKeyParam{
		ApiKeyId: key.Id,
	}
	err = svc.DeleteApiKey(ctx, &param, keyOperator)
	if err != nil {
		tilog.Error(ctx, "DeleteApiKeyHandler DeleteApiKey error", zap.Any("param", param), zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}

	tilog.Info(ctx, "DeleteApiKeyHandler DeleteApiKey success", zap.Any("req", req))
}

func (base OpenApiError) Error() string {
	if base.MsgPrefix == "" {
		return fmt.Sprintf("%v", base.error.Error())
	}
	return fmt.Sprintf("%v:%v", base.MsgPrefix, base.error.Error())
}

func WrapError(err error) error {
	if err == nil {
		err = errors.New("")
	}
	return pingcaperr.AddStack(err)
}

// @Tags
// @Summary Get a list of API key support roles
// @Description  This endpoint returns a list of API key support roles.
// @Schemes
// @Accept json
// @Produce json
// @Success 200 {object} OpenApiListApiKeyRolesRsp
// @Failure 400  {object}  OpenApiError
// @Router /apikeys/roles [GET]
func ListApiKeyRolesHandler(c *gin.Context) {
	ctx := c.Request.Context()
	bm := utils.Begin(ctx, "ListApiKeyRolesHandler")
	defer bm.OnExit(ctx)

	keyOperator, orgId, err := getAccessKeyAndOrgId(c)
	if err != nil {
		tilog.Error(ctx, "ListApiKeyRolesHandler getAccessKeyAndOrgId error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	hasPermission, err := checkAccessKeyPermission(ctx, keyOperator.OpVal, listApiKeyRolesResourceUri, orgId, 0)
	if err != nil {
		tilog.Info(ctx, "ListApiKeyRolesHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "ListApiKeyRolesHandler ak has no permission", zap.Any("accessKey", keyOperator.OpVal))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}
	var roles []string
	for r := range entity.ValidUserOrgRbacRoles {
		roles = append(roles, r)
	}
	var pRoles []string
	for r := range entity.ValidUserProjectRbacRoles {
		pRoles = append(pRoles, r)
	}
	rsp := OpenApiListApiKeyRolesRsp{
		OrgRoles:     roles,
		ProjectRoles: pRoles,
	}

	tilog.Info(ctx, "ListApiKeyRolesHandler response", zap.Any("rsp", rsp))
	openApiWriteResult(c.Writer, &rsp)
}

func UpdateApiKeyRoleHandler(c *gin.Context) {
	ctx := c.Request.Context()
	req := OpenApiUpdateApiKeyRoleReq{}
	if err := c.ShouldBindJSON(&req); err != nil {
		tilog.Info(ctx, "UpdateApiKeyRoleHandler ShouldBindJSON err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}
	tilog.Info(ctx, "UpdateApiKeyRoleHandler request", zap.Any("req", req))
	if req.OrgRole == "" {
		tilog.Info(ctx, "UpdateApiKeyRoleHandler org role is blank")
		writeError(ctx, c.Writer, errors.New("org role is blank"), http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}
	pRoles := map[uint64]string{}
	cRoles := map[uint64]string{}
	if len(req.ProjectRoles) > 0 {
		for _, pr := range req.ProjectRoles {
			if pr.ScopeId == 0 && pr.ScopeIdStr != "" {
				parseUint, err := strconv.ParseUint(pr.ScopeIdStr, 10, 64)
				if err != nil {
					writeError(ctx, c.Writer, errors.New("scopeIdStr is invalid"), http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
					return
				}
				pr.ScopeId = parseUint
			}
			if pr.ScopeId == 0 || pr.Role == "" {
				writeError(ctx, c.Writer, errors.New("project role is invalid"), http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
				return
			}
			if _, ok := pRoles[pr.ScopeId]; ok {
				writeError(ctx, c.Writer, fmt.Errorf("project %d repeated", pr.ScopeId), http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
				return
			}
			pRoles[pr.ScopeId] = pr.Role
		}
	}
	if len(req.ClusterRoles) > 0 {
		for _, cr := range req.ClusterRoles {
			if cr.ScopeId == 0 && cr.ScopeIdStr != "" {
				parseUint, err := strconv.ParseUint(cr.ScopeIdStr, 10, 64)
				if err != nil {
					writeError(ctx, c.Writer, errors.New("scopeIdStr is invalid"), http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
					return
				}
				cr.ScopeId = parseUint
			}
			if cr.ScopeId == 0 || cr.Role == "" {
				writeError(ctx, c.Writer, errors.New("cluster role is invalid"), http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
				return
			}
			if _, ok := cRoles[cr.ScopeId]; ok {
				writeError(ctx, c.Writer, fmt.Errorf("cluster %d repeated", cr.ScopeId), http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
				return
			}
			cRoles[cr.ScopeId] = cr.Role
		}
	}

	keyReq := OpenApiAccessKey{}
	if err := c.ShouldBindUri(&keyReq); err != nil {
		tilog.Info(ctx, "UpdateApiKeyRoleHandler ShouldBindUri err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		return
	}
	svc, _ := apikey2.GetApiKeySvc()
	key, err := svc.GetApiKeyByAccessKey(ctx, keyReq.AccessKey)
	if err != nil {
		tilog.Error(ctx, "UpdateApiKeyRoleHandler GetApiKeyByAccessKey err", zap.Error(err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, internalServerErrorCode, "internal server error")
		return
	}
	if key == nil {
		tilog.Warn(ctx, "UpdateApiKeyRoleHandler GetApiKeyByAccessKey return nil")
		writeError(ctx, c.Writer, err, http.StatusNotFound, paramInvalidErrorCode, "key not found")
		return
	}

	operatorRsp := getApiKeyFromGinContext(c)
	if operatorRsp == nil {
		tilog.Error(ctx, "UpdateApiKeyRoleHandler getApiKeyFromGinContext return nil")
		writeError(ctx, c.Writer, errors.New("not found auth info"), http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	operator := operatorRsp.ApiKey
	orgId, _ := getApiKeyRefOrgIdProjectId(key.ResourceInfos)

	hasPermission, err := checkAccessKeyPermission(ctx, operator.AccessKey, updateApiKeyRoleResourceUri, orgId, 0)
	if err != nil {
		tilog.Info(ctx, "CreateApiKeyHandler checkAccessKeyPermission error", zap.Any("err", err))
		writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		return
	}
	if !hasPermission {
		tilog.Info(ctx, "CreateApiKeyHandler ak has no permission", zap.Any("accessKey", operator.AccessKey))
		writeError(ctx, c.Writer, err, http.StatusForbidden, authorizeErrorCode, "access denied")
		return
	}

	param := &apikey2.UpdateOneConsoleApiKeyParam{
		KeyID: key.Id,
		Attrs: &apikey2.ApiKeyAttr{
			Description: req.Description,
		},
		AssignRoleParam: &comsvc.AssignIdentRolesParam{
			IdentTye: entity.RoleBindingTargetAPIKey,
			IdentID:  key.Id,
			IdentVal: key.AccessKey,
			OrgID:    orgId,
			ORole:    &req.OrgRole,
			PRoles:   pRoles,
			CRoles:   cRoles,
		},
	}
	oper := &comsvc.Operator{
		OpTye: entity.RoleBindingTargetAPIKey,
		OpID:  operator.Id,
		OpVal: operator.AccessKey,
	}
	err = svc.UpdateOneConsoleApiKey(ctx, param, oper)
	if err != nil {
		if errors.Is(err, errs.ErrBadRequest) {
			writeError(ctx, c.Writer, err, http.StatusBadRequest, paramInvalidErrorCode, "param invalid")
		} else {
			tilog.Error(ctx, "UpdateApiKeyRoleHandler UpdateConsoleApiKeyRole error", zap.Any("err", err))
			writeError(ctx, c.Writer, err, http.StatusInternalServerError, "InternalServerError", "internal server error")
		}
		return
	}

	tilog.Info(ctx, "UpdateApiKeyRoleHandler UpdateConsoleApiKeyRole success", zap.Any("req", req))
}
