package org

import (
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/pingcap-inc/clinic/pkg/api/upload"
	"github.com/pingcap-inc/clinic/pkg/datasource"
	task_manager "github.com/pingcap-inc/clinic/pkg/task_manager"
	"github.com/pingcap-inc/clinic/pkg/upload_file/decrypt"

	"github.com/pingcap-inc/clinic/pkg/api/common"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/config"
	"github.com/pingcap-inc/clinic/pkg/es"
	"github.com/pingcap-inc/clinic/pkg/tsdb"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/db"
	model2 "github.com/pingcap-inc/clinic/pkg/db/model"
	"github.com/pingcap-inc/clinic/pkg/rbac"
	"github.com/pingcap-inc/clinic/pkg/resource_limit"
	"github.com/pingcap-inc/clinic/pkg/stream"
)

type OrganizationService struct {
	decrypt *decrypt.Decrypt
	cfg     *config.Config
	rbac    *rbac.AuthCasbin
	// quotalimiter *resource_limit.QuotaLimiter
	quotalimiter resource_limit.IQuotaLimiter
	// sessionManager *session.SessionManager
	taskManagerCli *task_manager.TaskManagerClient
}

func NewOrganizationService(cfg *config.Config, decrypt *decrypt.Decrypt, rbac *rbac.AuthCasbin, quotalimit resource_limit.IQuotaLimiter, taskManagerCli *task_manager.TaskManagerClient) *OrganizationService {
	return &OrganizationService{
		cfg:          cfg,
		decrypt:      decrypt,
		rbac:         rbac,
		quotalimiter: quotalimit,
		// sessionManager: sessionManager,
		taskManagerCli: taskManagerCli,
	}
}

// @Summary		Get the organization
// @Tags			Organization
// @Description	get organization
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Success		200				{object}	Organization
// @Router			/orgs/{oid} [get]
func (o *OrganizationService) Get(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil {
		_ = c.Error(error2.ErrNotFound.New("the user is not found"))
		return
	}

	u, org, ok, err := o.canReadOrganization(c)
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("%s", err.Error()))
		return
	}

	if !ok {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}
	// TODO: rbac remove?
	// the returned roles does not contain pingcaper role
	roles, err := model2.GetRolesByIDs(o.rbac.GetRolesOfUserInOrg(u.UserID, org.ID), org.ID)
	if err != nil {
		_ = c.Error(error2.ErrInternal.New("find user roles failed"))
		return
	}

	roleNames := make([]string, 0)
	stream.OfSlice(roles).Map(func(r db.Role) string {
		rolename, _ := common.InternalRoleToResponseRole(r.RoleName)
		return rolename
	}).CollectEmptySlice(&roleNames)

	clusterCount, lastUpdated, err := datasource.LastUpdatedTime(u, org.ID)
	if err != nil {
		log.Error("get org LastUpdatedTime failed", zap.Error(err))
		// _ = c.Error(error2.ErrNotFound.New("find org failed"))
		// return
	}

	orgResp := Organization{
		ID:               strconv.FormatUint(uint64(org.ID), 10),
		OrganizationType: org.OrgType,
		Name:             org.Name,
		Description:      org.Description,
		ClusterCount:     clusterCount,
		LastUpdated:      lastUpdated,
		RoleNames:        roleNames,
		ExternalID:       strconv.FormatUint(uint64(org.ID), 10),
		EmailDomain:      org.EmailDomain,
	}

	if org.OrgType != "cloud" {
		user, err := model2.GetUserByID(org.CreatedBy)
		if err != nil {
			log.Error("get user failed", zap.Uint("user", org.CreatedBy), zap.Error(err))
		} else {
			orgResp.Owner = user.UserName
		}
	} else {
		orgResp.ExternalID = strconv.FormatUint(uint64(org.DbaasOrgID), 10)
	}
	c.JSON(http.StatusOK, orgResp)
}

// @Summary		Get the organization by external id
// @Tags			Organization
// @Description	get organization by external id
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			eid				path		string	true	"external id"
// @Success		200				{object}	Organization
// @Router			/orgs/external/{eid} [get]
func (o *OrganizationService) GetByExternalID(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil {
		_ = c.Error(error2.ErrNotFound.New("the user is not found"))
		return
	}

	if !u.Internal {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	externalID, err := strconv.ParseUint(c.Param("eid"), 10, 64)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid external id"))
		return
	}

	org, err := datasource.GetOrganizationByCloudID(u, uint(externalID))
	if err != nil {
		log.Error("get organization failed", zap.String("external", c.Param("eid")))
		_ = c.Error(error2.ErrBadRequest.New("get org failed"))
		return
	}

	c.JSON(http.StatusOK, Organization{
		ID:               strconv.FormatUint(uint64(org.ID), 10),
		OrganizationType: org.OrgType,
		Name:             org.Name,
		Description:      org.Description,
	})
}

// @Summary		Update the organization
// @Tags			Organization
// @Description	update organization
// @Accept			json
// @Param			x-csrf-token	header		string					true	"get value from login.ValidationResp response"
// @Param			oid				path		string					true	"organization id"
// @Param			organization	body		UpdateOrganizationReq	true	"organization"
// @Success		200				{object}	Organization
// @Router			/orgs/{oid} [put]
func (o *OrganizationService) Update(c *gin.Context) {
	var val UpdateOrganizationReq
	if err := c.BindJSON(&val); err != nil {
		_ = c.Error(error2.ErrNotFound.New("body is required"))
		return
	}

	if len(val.Name) == 0 {
		_ = c.Error(error2.ErrBadRequest.New("invalid name"))
		return
	}

	if len(val.Name) > 100 {
		_ = c.Error(error2.ErrBadRequest.New("the name is too long"))
		return
	}

	if len(val.Description) > 100 {
		_ = c.Error(error2.ErrBadRequest.New("the description is too long"))
		return
	}

	u, org, ok, err := o.canWriteOrganization(c)
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("%s", err.Error()))
		return
	}

	if !ok {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	if err := model2.UpdateOrganization(org.ID, val.Name, val.Description); err != nil {
		log.Error("update org failed", zap.Uint("user", u.UserID), zap.Uint("org", org.ID), zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("update organization failed"))
		return
	}

	c.JSON(http.StatusOK, common.SuccessResponse())
}

// @Summary		Create the organization
// @Tags			Organization
// @Description	create organization
// @Accept			json
// @Param			x-csrf-token	header		string			true	"get value from login.ValidationResp response"
// @Param			organization	body		Organization	true	"organization"
// @Success		200				{object}	Organization
// @Router			/orgs [post]
func (o *OrganizationService) Create(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil {
		_ = c.Error(error2.ErrNotFound.New("the user is not found"))
		return
	}

	var orgReq Organization
	if err := c.BindJSON(&orgReq); err != nil {
		_ = c.Error(error2.ErrBadRequest.New("body is required"))
		return
	}

	if len(orgReq.Name) == 0 {
		_ = c.Error(error2.ErrBadRequest.New("invalid name"))
		return
	}

	if len(orgReq.Name) > 100 {
		_ = c.Error(error2.ErrBadRequest.New("the name is too long"))
		return
	}

	if len(orgReq.Description) > 100 {
		_ = c.Error(error2.ErrBadRequest.New("the description is too long"))
		return
	}

	if !u.Internal && !o.quotalimiter.AskForResource(resource_limit.OrgCntQuota, 1, u.UserID) {
		_ = c.Error(error2.ErrPreConditionFailed.New("%s", resource_limit.OrgCntQuota.Reason()))
		return
	}

	org, err := model2.CreateOrganization(orgReq.Name, orgReq.Description, u.UserID, func(orgID uint) error {
		// TODO: remove rbac
		if err := o.rbac.InitOrgRBAC(orgID); err != nil {
			log.Error("apply rbac for org failed", zap.Uint("org", orgID), zap.Error(err))
			return err
		}

		role, err := model2.GetRoleByName(rbac.Admin, orgID)
		if err != nil || role == nil {
			log.Error("get admin role failed", zap.Error(err))
			return err
		}
		// TODO: remove rbac
		if err := o.rbac.AttachRoleForUser(orgID, u.UserID, role.ID); err != nil {
			log.Error("attach role failed", zap.Error(err))
			return err
		}

		err = es.CreateOrg(orgID, o.cfg.SinkConfig.LogConfig)
		if err != nil {
			log.Error("create org in kibana failed", zap.Uint("org", orgID), zap.Error(err))
		}

		return err
	})
	if err != nil {
		if model2.DuplicateEntity(err) {
			_ = c.Error(error2.ErrBadRequest.New("the organization is existed"))
			return
		}

		_ = c.Error(error2.ErrInternal.New("create organization failed"))
		return
	}

	c.JSON(http.StatusOK, Organization{
		ID:           strconv.FormatUint(uint64(org.ID), 10),
		Name:         org.Name,
		Description:  org.Description,
		ClusterCount: 0,
		LastUpdated:  time.Now().Unix(),
	})
}

// @Summary		List the organizations
// @Tags			Organization
// @Description	list organization
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			query			query		string	false	"the organization name"
// @Param			filter			query		string	false	"filter condition, can be all,starred,mine"
// @Param			limit			query		int		false	"the count per page"
// @Param			page			query		int		false	"the page index"
// @Success		200				{object}	ListOrganizationsResp
// @Router			/orgs [get]
func (o *OrganizationService) List(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil {
		_ = c.Error(error2.ErrNotFound.New("the user is not found"))
		return
	}
	var orgIDs []uint
	var dbaasOrgIDs []uint
	if !u.Internal {
		// list op orgs
		orgIDs = o.rbac.GetUserAccessOrgs(u.UserID)

		if o.cfg.DBaasConfig.AccountSvcAddr != "" {
			// list cloud orgs
			var err error
			tmpDbaasOrgs, err := datasource.ListOrganizationIDsByDBaasUser(u)
			if err != nil {
				log.Error("list orgs failed", zap.Uint("user", u.UserID), zap.Error(err))
				_ = c.Error(error2.ErrInternal.New("query organization failed"))
				return
			}
			dbaasOrgIDs = make([]uint, 0)
			for _, dbaasOrg := range tmpDbaasOrgs {
				dbaasOrgID := dbaasOrg.DbaasOrgID
				if dbaasOrg.SupportPlan == "Enterprise" || dbaasOrg.SupportPlan == "Premium" ||
					o.cfg.ExternalFeatureGates.FeatureEnabled(config.FeatureGateOrgs, uint64(dbaasOrgID)) {
					dbaasOrgIDs = append(dbaasOrgIDs, dbaasOrgID)
				}
			}
		}

		if len(orgIDs) == 0 && len(dbaasOrgIDs) == 0 {
			c.JSON(http.StatusOK, ListOrganizationsResp{
				Total: 0,
				Items: make([]Organization, 0),
			})
			return
		}
	}

	pq := common.ParsePaginationQuery(c.Request.URL.RequestURI())
	cond := &model2.ListOrganizationsCondition{
		Name:        c.Query("query"),
		IDs:         orgIDs,
		DbaasOrgIDs: dbaasOrgIDs,
		Page:        pq.Page,
		PerPage:     pq.Limit,
		ViewBy:      u.UserID,
	}
	switch c.Query("filter") {
	case "mine":
		cond.Owner = u.UserID
	case "starred":
		cond.StarBy = u.UserID
	}
	log.Info("list orgs cond", zap.Any("cond", cond))
	orgs, total, err := datasource.ListOrganizations(u, cond)
	if err != nil {
		log.Error("list orgs failed", zap.Uint("user", u.UserID), zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("query organization failed"))
		return
	}
	log.Info("list orgs", zap.Any("orgs", orgs))

	orgResp := make([]Organization, len(orgs))
	stream.OfSlice(orgs).Map(func(o db.Organization) Organization {
		clusterCount, lastUpdated, err := datasource.LastUpdatedTime(u, o.ID)
		if err != nil {
			log.Error("get updated time failed", zap.Uint("org", o.ID), zap.Error(err))
		}
		return Organization{
			ID:               strconv.FormatUint(uint64(o.ID), 10),
			Name:             o.Name,
			OrganizationType: o.OrgType,
			LastUpdated:      lastUpdated,
			ClusterCount:     clusterCount,
			Description:      o.Description,
			IsStarred:        o.IsStarredByViewer(),
		}
	}).Collect(&orgResp)
	c.JSON(http.StatusOK, ListOrganizationsResp{
		Total: total,
		Items: orgResp,
	})
}

// @Summary		delete Organization
// @Tags			Organization
// @Description	delete Organization and data
// @Acce			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Success		200				{string}	success
// @Router			/orgs/{oid} [delete]
func (o *OrganizationService) Delete(c *gin.Context) {
	_, org, ok, err := o.canDeleteOrganization(c)
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("%s", err.Error()))
		return
	}
	if !ok {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}
	go func() {
		files, err := model2.DeleteOrg(org.ID)
		if err != nil {
			_ = c.Error(error2.ErrInternal.New("%s", err.Error()))
			return
		}
		logIndex := fmt.Sprintf("org%d-log", org.ID)
		if err := es.DeleteIndex(logIndex, o.cfg.SinkConfig.LogConfig); err != nil {
			_ = c.Error(error2.ErrInternal.New("%s", err.Error()))
			return
		}
		slowlogIndex := fmt.Sprintf("org%d-slowlog", org.ID)
		if err := es.DeleteIndex(slowlogIndex, o.cfg.SinkConfig.LogConfig); err != nil {
			_ = c.Error(error2.ErrInternal.New("%s", err.Error()))
			return
		}
		if err := tsdb.CleanVm(files, org.ID, o.cfg.SinkConfig.MetricConfig); err != nil {
			_ = c.Error(error2.ErrInternal.New("%s", err.Error()))
			return
		}
		// TODO: remove rbac
		if err := o.rbac.DeleteOrg(org.ID); err != nil {
			_ = c.Error(error2.ErrInternal.New("%s", err.Error()))
			return
		}

		es.DeleteOrg(org.ID, o.cfg.SinkConfig.LogConfig)
	}()
	c.JSON(http.StatusAccepted, common.SuccessResponse())
}

// @Summary		List the organizations roles
// @Tags			Organization
// @Description	list organization roles
// @Accept			json
// @Param			x-csrf-token	header	string	true	"get value from login.ValidationResp response"
// @Param			oid				path	string	true	"organization id"
// @Success		200				{array}	Role
// @Router			/orgs/{oid}/roles [get]
func (o *OrganizationService) ListRoles(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil {
		_ = c.Error(error2.ErrNotFound.New("the user is not found"))
		return
	}

	oid, err := common.GetOrgIDFromAPIPath(c)
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("unknown organization id"))
		return
	}

	ok, err := o.rbac.Auth(u.UserID, u.Internal, rbac.User(), rbac.ReadActing(), oid)
	if err != nil {
		log.Error("check rbac failed", zap.Uint("user", u.UserID), zap.Uint("org", oid), zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("check permission failed"))
		return
	}

	if !ok {
		_ = c.Error(error2.ErrForbidden.New("permission deny"))
		return
	}

	roles, err := model2.ListRoles(oid)
	if err != nil {
		log.Error("list roles failed", zap.Uint("user", u.UserID), zap.Uint("org", oid), zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("list roles failed"))
		return
	}

	apiRoles := make([]*Role, len(roles))
	stream.OfSlice(roles).Map(func(r db.Role) *Role {
		rolename, _ := common.InternalRoleToResponseRole(r.RoleName)
		return &Role{
			Name: rolename,
			ID:   r.ID,
		}
	}).Collect(&apiRoles)

	c.JSON(http.StatusOK, apiRoles)
}

// @Summary		Precreate
// @Tags			Upload
// @Description	precreate
// @Accept			multipart/form-data
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Param			uuid			query		string	true	"a custom hash combining package’s filename, size and modify time"
// @Param			length			query		int		true	"the size of the file"
// @Param			alias			query		string	false	"an optional alias of filename"
// @Param			filename		query		string	true	"actual name of the file"
// @Param			file			formData	file	true	"file"
// @Success		200				{object}	common.PreCreateResponse
// @Router			/orgs/{oid}/diag/precreate [post]
func (o *OrganizationService) Precreate(c *gin.Context) {
	user := session.GetUser(c)
	if user == nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid user"))
		return
	}
	oid, err := common.GetOrgIDFromAPIPath(c)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid organization id"))
		return
	}

	user.OrgIDInApiToken = oid

	upload.Precreate(c, user, o.decrypt, o.quotalimiter, o.cfg, o.rbac)
}

// @Summary		Flush
// @Tags			Upload
// @Description	call flush api after all parts of file are uploaded
// @Accept			multipart/form-data
// @Param			oid				path		string	true	"organization id"
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			uuid			query		string	true	"a custom hash combining package’s filename, size and modify time"
// @Param			issue			query		string	false	"an optional issue Jira issue ID this package should be connected to"
// @Success		200				{object}	common.FlushResponse
// @Router			/orgs/{oid}/diag/flush [post]
func (o *OrganizationService) Flush(c *gin.Context) {
	user := session.GetUser(c)
	if user == nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid user"))
		return
	}
	oid, err := common.GetOrgIDFromAPIPath(c)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid organization id"))
		return
	}

	user.OrgIDInApiToken = oid

	upload.Flush(c, user, o.cfg)
}

// @Summary		Upload
// @Tags			Upload
// @Description	upload file part api
// @Accept			application/octet-stream
// @Param			oid				path		string	true	"organization id"
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			uuid			query		string	true	"a custom hash combining package’s filename, size and modify time"
// @Param			sequence		query		int		true	"the serial number of the file part"
// @Param			length			query		int		true	"the length of the file part"
// @Success		200				{string}	string
// @Router			/orgs/{oid}/diag/upload [post]
func (o *OrganizationService) Upload(c *gin.Context) {
	user := session.GetUser(c)
	if user == nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid user"))
		return
	}

	oid, err := common.GetOrgIDFromAPIPath(c)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid organization id"))
		return
	}

	user.OrgIDInApiToken = oid

	upload.Upload(c, user, o.cfg)
}

// @Summary		Star the organization
// @Tags			Organization
// @Description	star organization
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Success		200				{object}	common.SuccessResp
// @Router			/orgs/{oid}/star [post]
func (o *OrganizationService) Star(c *gin.Context) {
	user := session.GetUser(c)
	if user == nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid user"))
		return
	}

	oid, err := common.GetOrgIDFromAPIPath(c)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid organization id"))
		return
	}
	if err := model2.CreateStar(c, user.UserID, oid); err != nil {
		log.Error("create star failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("%s", err.Error()))
		return
	}
	c.JSON(http.StatusOK, common.SuccessResponse())
}

// @Summary		UnStar the organization
// @Tags			Organization
// @Description	unstar organization
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			oid				path		string	true	"organization id"
// @Success		200				{object}	common.SuccessResp
// @Router			/orgs/{oid}/star [delete]
func (o *OrganizationService) UnStar(c *gin.Context) {
	user := session.GetUser(c)
	if user == nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid user"))
		return
	}

	oid, err := common.GetOrgIDFromAPIPath(c)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid organization id"))
		return
	}
	if err := model2.DeleteStar(c, user.UserID, oid); err != nil {
		log.Error("delete star failed", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("%s", err.Error()))
		return
	}
	c.JSON(http.StatusOK, common.SuccessResponse())
}

func (o *OrganizationService) canReadOrganization(c *gin.Context) (*session.CtxUser, *db.Organization, bool, error) {
	u, org, err := common.GetOrgFromAPIPath(c)
	if err != nil {
		return nil, nil, false, err
	}

	// check external user access cloud org
	if !u.Internal && org.OrgType == "cloud" {
		if org.SupportPlan != "Enterprise" && org.SupportPlan != "Premium" {
			openToExternal := o.cfg.ExternalFeatureGates.FeatureEnabled(config.FeatureGateOrgs, uint64(org.DbaasOrgID))
			if !openToExternal {
				return nil, nil, false, fmt.Errorf("cloud org %d not open to external", org.ID)
			}
		}
		ok, err := datasource.HasPermissionForCloudUser(u, org.DbaasOrgID, 0)
		if err != nil {
			log.Error("access cluster resource failed", zap.Uint("user", u.UserID), zap.Uint("org", org.ID))
			return nil, nil, false, errors.New("permission deny")
		}
		return u, org, ok, nil
	}

	ok, err := o.rbac.Auth(u.UserID, u.Internal, rbac.Cluster(), rbac.ReadActing(), org.ID)
	if err != nil {
		log.Error("access cluster resource failed", zap.Uint("user", u.UserID), zap.Uint("org", org.ID))
		return nil, nil, false, errors.New("permission deny")
	}

	return u, org, ok, nil
}

func (o *OrganizationService) canWriteOrganization(c *gin.Context) (*session.CtxUser, *db.Organization, bool, error) {
	u, org, err := common.GetOrgFromAPIPath(c)
	if err != nil {
		return nil, nil, false, err
	}

	ok, err := o.rbac.Auth(u.UserID, u.Internal, rbac.TenantResource(), rbac.WriteActing(), org.ID)
	if err != nil {
		log.Error("access cluster resource failed", zap.Uint("user", u.UserID), zap.Uint("org", org.ID))
		return nil, nil, false, errors.New("permission deny")
	}

	return u, org, ok, nil
}

func (o *OrganizationService) canDeleteOrganization(c *gin.Context) (*session.CtxUser, *db.Organization, bool, error) {
	u, org, err := common.GetOrgFromAPIPath(c)
	if err != nil {
		return nil, nil, false, err
	}

	ok, err := o.rbac.Auth(u.UserID, u.Internal, rbac.TenantResource(), rbac.DeleteActing(), org.ID)
	if err != nil {
		log.Error("access cluster resource failed", zap.Uint("user", u.UserID), zap.Uint("org", org.ID))
		return nil, nil, false, errors.New("permission deny")
	}

	return u, org, ok, nil
}
