package apikey

import (
	"context"
	"fmt"
	"time"

	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/account/internel/dao/apikey"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/internel/dao/role"
	"github.com/tidbcloud/account/internel/svc/comsvc"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
)

func (s *ApiKeySvc) CreateOneConsoleApiKey(ctx context.Context, param *CreateOneConsoleApiKeyParam, opr *comsvc.Operator) (*OneConsoleApiKey, error) {
	tilog.Info(ctx, "CreateOneConsoleApiKey param", zap.Any("param", param), zap.Any("opr", opr))
	// param valid check
	if param == nil || param.Attrs == nil || opr == nil {
		return nil, errs.NewBadRequest("param invalid", nil)
	}
	attrs := param.Attrs
	assignRoleParam := param.AssignRoleParam

	if attrs.Type != entity.ApiKeyTypeNormalKey && attrs.Type != entity.ApiKeyTypeBearerToken {
		return nil, errs.NewBadRequest("type invalid", nil)
	}
	if assignRoleParam != nil {
		if assignRoleParam.OrgID == 0 || assignRoleParam.ORole == nil {
			return nil, errs.NewBadRequest("param invalid", nil)
		}
		if *assignRoleParam.ORole == entity.RoleApiKeyOrgOwner {
			// api:org:owner is deprecated, will use org:owner later
			tRole := entity.RoleOrgOwner
			assignRoleParam.ORole = &tRole
		}
		err := comsvc.CheckAssignScopeAndRoleValid(ctx, assignRoleParam.OrgID, assignRoleParam.ORole, assignRoleParam.PRoles, assignRoleParam.CRoles)
		if err != nil {
			tilog.Warn(ctx, "CreateOneConsoleApiKey CheckAssignScopeAndRoleValid error", zap.Any("err", err))
			return nil, err
		}
	}

	// create key
	keyEnt, sk, err := s.createApiKeyWithAkConflictRetry(ctx, attrs, opr.OpVal)
	if err != nil {
		tilog.Error(ctx, "CreateOneConsoleApiKey createApiKeyWithAkConflictRetry error", zap.Any("err", err))
		return nil, err
	}
	tilog.Info(ctx, "CreateOneConsoleApiKey create api key ent success", zap.Any("keyEnt", keyEnt))

	// assign role
	if assignRoleParam != nil {
		assignRoleParam.IdentTye = entity.RoleBindingTargetAPIKey
		assignRoleParam.IdentID = keyEnt.ID.Uint64()
		assignRoleParam.IdentVal = keyEnt.AccessKey
		assignRoleParam.Status = entity.RoleBindingStatusActive

		assignRet, err := comsvc.UpdateIdentityRoles(ctx, assignRoleParam)
		if err != nil {
			tilog.Error(ctx, "CreateOneConsoleApiKey UpdateIdentityRoles error", zap.Any("err", err))
			return nil, err
		}
		tilog.Info(ctx, "CreateOneConsoleApiKey update api key roles success", zap.Any("assignRet", assignRet))

		go func() {
			_ = comsvc.SendApiKeyRoleBindingChangeEvent(context.Background(), keyEnt.ID.Uint64(), assignRoleParam.OrgID, "OneConsole CreateApiKey")
		}()
	}

	// --- rec audit log start ---
	if param.RecAuditLog {
		if assignRoleParam != nil && assignRoleParam.OrgID != 0 {
			details := auditlog.Details{
				auditlog.KeyDescription: attrs.Description,
				auditlog.KeyError:       err,
			}
			if assignRoleParam.ORole != nil && *assignRoleParam.ORole != "" {
				details["org_role"] = entity.RoleBindingDecode(*assignRoleParam.ORole)
			}
			if len(assignRoleParam.PRoles) > 0 {
				rDetail := map[string]string{}
				for pid, r := range assignRoleParam.PRoles {
					rDetail[fmt.Sprintf("%d", pid)] = string(entity.RoleBindingDecode(r))
				}
				details["project_roles"] = rDetail
			}
			if len(assignRoleParam.CRoles) > 0 {
				rDetail := map[string]string{}
				for pid, r := range assignRoleParam.CRoles {
					rDetail[fmt.Sprintf("%d", pid)] = string(entity.RoleBindingDecode(r))
				}
				details["tidb_roles"] = rDetail
			}
			eBuilder := auditlog.NewEvent(ctx, auditlog.EventCreateAPIKey).SetResult(err == nil).SetOrgID(assignRoleParam.OrgID).SetDetails(details.Build())
			if opr.OpIP != "" {
				eBuilder.SetOperatorIP(opr.OpIP)
			}
			if opr.OpTye == entity.RoleBindingTargetAPIKey {
				eBuilder.SetOperatorType(auditlog.OperatorTypeAPIKey).SetAPIPubKey(opr.OpVal)
			} else {
				eBuilder.SetOperatorType(auditlog.OperatorTypeUser).SetOperatorID(opr.OpID).SetOperatorEmail(opr.OpVal)
			}
			_ = eBuilder.Submit()
		}
	}
	// --- rec audit log end---

	keyRet := &OneConsoleApiKey{
		Id:          keyEnt.ID.Uint64(),
		AccessKey:   keyEnt.AccessKey,
		SecretKey:   sk,
		Description: keyEnt.Description,
		CreatedBy:   keyEnt.CreatedBy,
		UpdatedBy:   keyEnt.UpdatedBy,
		CreatedAt:   &keyEnt.CreatedAt,
		UpdatedAt:   keyEnt.UpdatedAt,
	}
	return keyRet, nil
}

func genToUpdateApiKey(attrs *ApiKeyAttr, keyEnt *entity.ApiKey, operator *comsvc.Operator) *entity.ApiKey {
	now := time.Now()
	changed := false

	toUpdateKey := &entity.ApiKey{}
	toUpdateKey.ID = keyEnt.ID
	toUpdateKey.UpdatedBy = operator.OpVal
	toUpdateKey.UpdatedAt = &now

	if attrs.Description != "" && attrs.Description != keyEnt.Description {
		toUpdateKey.Description = attrs.Description
		changed = true
	}
	if attrs.RateLimitRpm != 0 && attrs.RateLimitRpm != keyEnt.RateLimitRpm {
		toUpdateKey.RateLimitRpm = attrs.RateLimitRpm
		changed = true
	}
	if attrs.ExpiredAt != nil {
		toUpdateKey.ExpiredAt = attrs.ExpiredAt
		changed = true
	}
	if attrs.BizAttr != "" && attrs.BizAttr != keyEnt.BizAttr {
		toUpdateKey.BizAttr = attrs.BizAttr
		changed = true
	}

	if changed {
		return toUpdateKey
	}
	return nil
}

func (s *ApiKeySvc) UpdateOneConsoleApiKey(ctx context.Context, param *UpdateOneConsoleApiKeyParam, opr *comsvc.Operator) error {
	tilog.Info(ctx, "UpdateOneConsoleApiKey param", zap.Any("param", param), zap.Any("opr", opr))
	// param valid check
	if param == nil || param.KeyID == 0 || opr == nil {
		return errs.NewBadRequest("param invalid", nil)
	}
	attrs := param.Attrs
	assignRoleParam := param.AssignRoleParam

	if assignRoleParam != nil {
		if assignRoleParam.OrgID == 0 {
			return errs.NewBadRequest("param invalid", nil)
		}
		if assignRoleParam.ORole != nil && *assignRoleParam.ORole == entity.RoleApiKeyOrgOwner {
			// api:org:owner is deprecated, will use org:owner later
			tRole := entity.RoleOrgOwner
			assignRoleParam.ORole = &tRole
		}
		err := comsvc.CheckAssignScopeAndRoleValid(ctx, assignRoleParam.OrgID, assignRoleParam.ORole, assignRoleParam.PRoles, assignRoleParam.CRoles)
		if err != nil {
			tilog.Warn(ctx, "UpdateOneConsoleApiKey CheckAssignScopeAndRoleValid error", zap.Any("err", err))
			return err
		}
	}

	// check key ent
	keyEnt, err := dao.GetDaoHub().ApiKey.GetApiKeyByID(ctx, database.DBConn.DB, param.KeyID)
	if err != nil {
		tilog.Error(ctx, "UpdateOneConsoleApiKey GetApiKeyByID error", zap.Any("keyId", param.KeyID), zap.Any("err", err))
		return errs.NewInternalUnknown("get key internal error", err)
	}
	if keyEnt == nil || keyEnt.Status == entity.Delete {
		tilog.Info(ctx, "UpdateOneConsoleApiKey not found key", zap.Any("key_id", assignRoleParam.IdentID))
		return errs.NewNotFound("key not found", nil)
	}

	// update key ent
	if attrs != nil {
		toUpdateKey := genToUpdateApiKey(attrs, keyEnt, opr)
		if toUpdateKey != nil {
			err = dao.GetDaoHub().ApiKey.UpdateApiKey(ctx, database.DBConn.DB, s.dynamodb, toUpdateKey)
			if err != nil {
				tilog.Error(ctx, "UpdateOneConsoleApiKey update key ent error", zap.Any("err", err))
				return err
			}
			_ = DeleteApiKeyEntFromCache(ctx, keyEnt.ID.Uint64(), keyEnt.AccessKey)
			tilog.Info(ctx, "UpdateOneConsoleApiKey update key ent success", zap.Any("toUpdateKey", toUpdateKey))
		} else {
			tilog.Info(ctx, "UpdateOneConsoleApiKey no attrs changed")
		}
	}

	// update roles
	var assignRoleRet *comsvc.AssignIdentRolesRet
	if assignRoleParam != nil {
		assignRoleParam.IdentTye = entity.RoleBindingTargetAPIKey
		assignRoleParam.IdentID = keyEnt.ID.Uint64()
		assignRoleParam.IdentVal = keyEnt.AccessKey
		assignRoleParam.Status = entity.RoleBindingStatusActive

		assignRoleRet, err = comsvc.UpdateIdentityRoles(ctx, assignRoleParam)
		if err != nil {
			tilog.Error(ctx, "UpdateOneConsoleApiKey UpdateIdentityRoles error", zap.Any("err", err))
			return err
		}
		tilog.Info(ctx, "UpdateOneConsoleApiKey update key roles success", zap.Any("assignRoleRet", assignRoleRet))

		go func() {
			_ = comsvc.SendApiKeyRoleBindingChangeEvent(context.Background(), keyEnt.ID.Uint64(), assignRoleParam.OrgID, "OneConsole UpdateApiKey")
		}()
	}

	// rec audit log
	if param.RecAuditLog && param.AuditLogOrgID != 0 {
		details := auditlog.Details{
			auditlog.KeyError: err,
		}
		if attrs != nil && attrs.Description != "" && attrs.Description != keyEnt.Description {
			details["old_description"] = keyEnt.Description
			details["new_description"] = attrs.Description
		}
		if assignRoleRet != nil {
			if assignRoleRet.UpdOBinding != nil {
				details["old_org_role"] = entity.RoleBindingDecode(assignRoleRet.UpdOBinding.FromRole)
				details["new_org_role"] = entity.RoleBindingDecode(assignRoleRet.UpdOBinding.ToRole)
			}
			if len(assignRoleRet.InsPBindings) > 0 {
				rDetail := map[string]string{}
				for _, b := range assignRoleRet.InsPBindings {
					rDetail[fmt.Sprintf("%d", b.ScopeId)] = string(entity.RoleBindingDecode(b.ToRole))
				}
				details["add_project_roles"] = rDetail
			}
			if len(assignRoleRet.DelPBindings) > 0 {
				rDetail := map[string]string{}
				for _, b := range assignRoleRet.DelPBindings {
					rDetail[fmt.Sprintf("%d", b.ScopeId)] = string(entity.RoleBindingDecode(b.FromRole))
				}
				details["delete_project_roles"] = rDetail
			}
			if len(assignRoleRet.UpdPBindings) > 0 {
				rDetail := map[string]string{}
				for _, b := range assignRoleRet.UpdPBindings {
					rDetail[fmt.Sprintf("%d", b.ScopeId)] = fmt.Sprintf("change project role from %s to %s", string(entity.RoleBindingDecode(b.FromRole)), string(entity.RoleBindingDecode(b.ToRole)))
				}
				details["update_project_roles"] = rDetail
			}
			if len(assignRoleRet.InsCBindings) > 0 {
				rDetail := map[string]string{}
				for _, b := range assignRoleRet.InsCBindings {
					rDetail[fmt.Sprintf("%d", b.ScopeId)] = string(entity.RoleBindingDecode(b.ToRole))
				}
				details["add_tidb_roles"] = rDetail
			}
			if len(assignRoleRet.DelCBindings) > 0 {
				rDetail := map[string]string{}
				for _, b := range assignRoleRet.DelCBindings {
					rDetail[fmt.Sprintf("%d", b.ScopeId)] = string(entity.RoleBindingDecode(b.FromRole))
				}
				details["delete_tidb_roles"] = rDetail
			}
			if len(assignRoleRet.UpdCBindings) > 0 {
				rDetail := map[string]string{}
				for _, b := range assignRoleRet.UpdPBindings {
					rDetail[fmt.Sprintf("%d", b.ScopeId)] = fmt.Sprintf("change tidb role from %s to %s", string(entity.RoleBindingDecode(b.FromRole)), string(entity.RoleBindingDecode(b.ToRole)))
				}
				details["update_tidb_roles"] = rDetail
			}
		}
		eBuilder := auditlog.NewEvent(ctx, auditlog.EventEditAPIKey).SetResult(err == nil).SetOrgID(param.AuditLogOrgID).SetDetails(details.Build())
		if opr.OpIP != "" {
			eBuilder.SetOperatorIP(opr.OpIP)
		}
		if opr.OpTye == entity.RoleBindingTargetAPIKey {
			eBuilder.SetOperatorType(auditlog.OperatorTypeAPIKey).SetAPIPubKey(opr.OpVal)
		} else {
			eBuilder.SetOperatorType(auditlog.OperatorTypeUser).SetOperatorID(opr.OpID).SetOperatorEmail(opr.OpVal)
		}
		_ = eBuilder.Submit()
	}

	return nil
}

func (s *ApiKeySvc) ListOneConsoleApiKeys(ctx context.Context, param *ListApiKeysParam) (int32, []OneConsoleApiKey, error) {
	tilog.Info(ctx, "ListOneConsoleApiKeys param", zap.Any("param", param))
	if param.OrgId == 0 {
		return 0, nil, errs.NewBadRequest("org_id is nil", nil)
	}
	if len(param.OrgRoles) > 0 {
		for _, or := range param.OrgRoles {
			if _, ok := entity.ValidUserOrgRbacRoles[or]; !ok {
				return 0, nil, errs.NewBadRequest("exist invalid org role", nil)
			}
		}
	}
	if param.ProjectId != 0 {
		p, err := dao.GetDaoHub().Project.GetProjectByID(ctx, database.DBConn.DB, param.ProjectId)
		if err != nil {
			tilog.Error(ctx, "ListOneConsoleApiKeys GetProjectByID error", zap.Any("err", err))
			return 0, nil, errs.NewInternalUnknown("get project by id internal error", err)
		}
		if p.TenantID.Uint64() != param.OrgId {
			return 0, nil, errs.NewInternalUnknown("invalid project", err)
		}
		if len(param.ProjectRoles) > 0 {
			for _, pr := range param.ProjectRoles {
				if _, ok := entity.ValidUserProjectRbacRoles[pr]; !ok {
					return 0, nil, errs.NewBadRequest("exist invalid project role", nil)
				}
			}
		}
	}
	if param.ClusterId != 0 {
		relation, err := dao.GetDaoHub().ResourceRelation.GetResourceRelation(ctx, database.DBConn.DB, entity.RoleBindingScopeTiDB.String(), param.ClusterId, entity.RoleBindingScopeOrg.String())
		if err != nil {
			tilog.Error(ctx, "ListOneConsoleApiKeys GetResourceRelation error", zap.Any("err", err))
			return 0, nil, errs.NewInternalUnknown("get cluster org relation internal error", err)
		}
		if relation == nil || relation.ParentID != param.OrgId {
			return 0, nil, errs.NewBadRequest("invalid cluster", err)
		}
		if len(param.ClusterRoles) > 0 {
			for _, cr := range param.ClusterRoles {
				if _, ok := entity.ValidClusterRbacRoles[cr]; !ok {
					return 0, nil, errs.NewBadRequest("invalid cluster role", nil)
				}
			}
		}
	}

	// 1. select api keys matched given org roles, using uni_api_keys join role_binding to filter api keys
	// 2. select api keys matched given project\cluster role
	// 3. take intersection of api keys obtained in step 1 and 2.
	// 4. page
	// 5. set project/cluster roles
	oKeys, err := dao.GetDaoHub().ApiKey.ListOrgApiKeys(ctx, database.DBConn.DB, &apikey.OrgApiKeyFilter{
		OrgID:    param.OrgId,
		OrgRoles: param.OrgRoles,
		Type:     param.Type,
		KeyLike:  param.KeyLike,
	})
	if err != nil {
		tilog.Error(ctx, "ListOneConsoleApiKeys list org api keys error", zap.Any("err", err))
		return 0, nil, errs.NewInternalUnknown("list org api keys internal error", err)
	}
	if len(oKeys) == 0 {
		tilog.Info(ctx, "ListOneConsoleApiKeys matched org api keys is empty")
		return 0, []OneConsoleApiKey{}, nil
	}

	var kId2PBindings map[uint64]entity.RoleBinding = nil
	if param.ProjectId != 0 {
		kId2PBindings = map[uint64]entity.RoleBinding{}
		pBindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, role.RoleBindingFilter{
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeProject},
			ScopeIDs:   []entity.ID{entity.ID(param.ProjectId)},
			TargetType: []entity.TargetType{entity.RoleBindingTargetAPIKey},
			Roles:      param.ProjectRoles,
			Status:     []string{entity.RoleBindingStatusActive},
		})
		if err != nil {
			tilog.Error(ctx, "ListOneConsoleApiKeys list project api keys error", zap.Any("err", err))
			return 0, nil, errs.NewInternalUnknown("list project api keys internal error", err)
		}
		for _, pb := range pBindings {
			kId2PBindings[pb.TargetID.Uint64()] = pb
		}
	}
	if kId2PBindings != nil && len(kId2PBindings) == 0 {
		tilog.Info(ctx, "ListOneConsoleApiKeys matched project api keys is empty")
		return 0, []OneConsoleApiKey{}, nil
	}

	var kId2CBindings map[uint64]entity.RoleBinding = nil
	if param.ClusterId != 0 {
		kId2CBindings = map[uint64]entity.RoleBinding{}
		cBindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, role.RoleBindingFilter{
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeTiDB},
			ScopeIDs:   []entity.ID{entity.ID(param.ClusterId)},
			TargetType: []entity.TargetType{entity.RoleBindingTargetAPIKey},
			Roles:      param.ClusterRoles,
			Status:     []string{entity.RoleBindingStatusActive},
		})
		if err != nil {
			tilog.Error(ctx, "ListOneConsoleApiKeys list cluster api keys error", zap.Any("err", err))
			return 0, nil, errs.NewInternalUnknown("list cluster api keys internal error", err)
		}
		for _, cb := range cBindings {
			kId2CBindings[cb.TargetID.Uint64()] = cb
		}
	}
	if kId2CBindings != nil && len(kId2CBindings) == 0 {
		tilog.Info(ctx, "ListOneConsoleApiKeys matched cluster api keys is empty")
		return 0, []OneConsoleApiKey{}, nil
	}

	var rKeys []*apikey.OrgApiKey
	var rKIds []entity.ID
	for _, k := range oKeys {
		// match project filter
		if kId2PBindings != nil {
			_, ok := kId2PBindings[k.ID.Uint64()]
			if !ok {
				continue
			}
		}
		// match cluster filter
		if kId2CBindings != nil {
			_, ok := kId2CBindings[k.ID.Uint64()]
			if !ok {
				continue
			}
		}
		rKeys = append(rKeys, &k)
		rKIds = append(rKIds, k.ID)
	}

	total := len(rKeys)
	// page
	if param.Page > 0 && param.PerPage > 0 {
		start := int((param.Page - 1) * param.PerPage)
		if start < 0 {
			start = 0
		}
		end := start + int(param.PerPage)
		if end > total {
			end = total
		}
		if start > end {
			start = end
		}
		rKeys = rKeys[start:end]
	}

	if len(rKeys) == 0 {
		tilog.Info(ctx, "ListOneConsoleApiKeys matched paged api keys is empty")
		return int32(total), []OneConsoleApiKey{}, nil
	}

	// get project role for rKeys
	aps, err := dao.GetDaoHub().Project.ListActiveProjectsByOrgID(ctx, database.DBConn.DB, param.OrgId)
	if err != nil {
		return 0, nil, errs.NewInternalUnknown("list org active projects internal error", err)
	}
	var aPIds []entity.ID
	for _, ap := range aps {
		aPIds = append(aPIds, ap.ID)
	}
	kInPRoles, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, role.RoleBindingFilter{
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeProject},
		ScopeIDs:   aPIds,
		TargetType: []entity.TargetType{entity.RoleBindingTargetAPIKey},
		TargetIDs:  rKIds,
		Status:     []string{entity.RoleBindingStatusActive},
	})
	if err != nil {
		tilog.Error(ctx, "ListOneConsoleApiKeys list api keys in active project's role error", zap.Any("err", err))
		return 0, nil, errs.NewInternalUnknown("list api keys in active project's role internal error", err)
	}
	var kId2PRs = map[uint64][]entity.RoleBinding{}
	for _, rb := range kInPRoles {
		kId := rb.TargetID.Uint64()
		bindings, ok := kId2PRs[kId]
		if !ok {
			bindings = []entity.RoleBinding{}
		}
		bindings = append(bindings, rb)
		kId2PRs[kId] = bindings
	}

	ksRoles, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, role.RoleBindingFilter{
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeTiDB},
		TargetType: []entity.TargetType{entity.RoleBindingTargetAPIKey},
		TargetIDs:  rKIds,
		Status:     []string{entity.RoleBindingStatusActive},
	})
	if err != nil {
		tilog.Error(ctx, "ListOneConsoleApiKeys list cluster role bindings error", zap.Any("err", err))
		return 0, nil, errs.NewInternalUnknown("list cluster role bindings internal error", err)
	}

	var kId2CRs = map[uint64][]entity.RoleBinding{}

	if len(ksRoles) > 0 {
		hasRoleAllClusterIDs := make([]uint64, 0, len(ksRoles))
		seen := make(map[uint64]struct{}, len(ksRoles))
		for _, rb := range ksRoles {
			scopeID := rb.ScopeID.Uint64()
			if _, ok := seen[scopeID]; ok {
				continue
			}
			seen[scopeID] = struct{}{}
			hasRoleAllClusterIDs = append(hasRoleAllClusterIDs, scopeID)
		}
		// get cluster role for rKeys
		hasRoleInOClusterIds, err := dao.GetDaoHub().ResourceRelation.GetResourceIDsByParentAndResourceIDs(ctx, database.DBConn.DB, entity.RoleBindingScopeOrg.String(), param.OrgId, entity.RoleBindingScopeTiDB.String(), hasRoleAllClusterIDs)
		if err != nil {
			tilog.Error(ctx, "ListOneConsoleApiKeys list org cluster relations error", zap.Any("err", err))
			return 0, nil, errs.NewInternalUnknown("get cluster relations internal error", err)
		}
		var cIdsInO = map[uint64]struct{}{}
		for _, rr := range hasRoleInOClusterIds {
			cIdsInO[rr] = struct{}{}
		}

		for _, rb := range ksRoles {
			if _, ok := cIdsInO[rb.ScopeID.Uint64()]; !ok {
				continue
			}
			kId := rb.TargetID.Uint64()
			bindings, ok := kId2CRs[kId]
			if !ok {
				bindings = []entity.RoleBinding{}
			}
			bindings = append(bindings, rb)
			kId2CRs[kId] = bindings
		}
	}

	var fKeys []OneConsoleApiKey
	for _, k := range rKeys {
		var pRoles []ApiKeyResourceInfo
		prs := kId2PRs[k.ID.Uint64()]
		for _, pr := range prs {
			inf := ApiKeyResourceInfo{
				ResourceId: pr.ScopeID.Uint64(),
				Role:       pr.Role,
				RoleId:     pr.RoleID.Uint64(),
			}
			pRoles = append(pRoles, inf)
		}
		var cRoles []ApiKeyResourceInfo
		crs := kId2CRs[k.ID.Uint64()]
		for _, cr := range crs {
			inf := ApiKeyResourceInfo{
				ResourceId: cr.ScopeID.Uint64(),
				Role:       cr.Role,
				RoleId:     cr.RoleID.Uint64(),
			}
			cRoles = append(cRoles, inf)
		}
		vk := OneConsoleApiKey{
			Id:          k.ID.Uint64(),
			AccessKey:   k.AccessKey,
			SecretKey:   k.SecretKey,
			Description: k.Description,
			CreatedBy:   k.CreatedBy,
			UpdatedBy:   k.UpdatedBy,
			CreatedAt:   &k.CreatedAt,
			UpdatedAt:   k.UpdatedAt,
			OrgRole: &ApiKeyResourceInfo{
				ResourceId: param.OrgId,
				Role:       k.Role,
				RoleId:     k.RoleID,
			},
			ProjectRoles: pRoles,
			ClusterRoles: cRoles,
		}
		fKeys = append(fKeys, vk)
	}

	return int32(total), fKeys, nil
}
