// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package db

import (
	"context"

	"yunion.io/x/jsonutils"
	"yunion.io/x/log"
	"yunion.io/x/pkg/errors"
	"yunion.io/x/pkg/util/rbacscope"
	"yunion.io/x/sqlchemy"

	"yunion.io/x/onecloud/pkg/apis"
	"yunion.io/x/onecloud/pkg/cloudcommon/consts"
	"yunion.io/x/onecloud/pkg/cloudcommon/policy"
	"yunion.io/x/onecloud/pkg/httperrors"
	"yunion.io/x/onecloud/pkg/mcclient"
	"yunion.io/x/onecloud/pkg/util/logclient"
	"yunion.io/x/onecloud/pkg/util/stringutils2"
	"yunion.io/x/onecloud/pkg/util/tagutils"
)

type SSharableBaseResourceManager struct{}

func (manager *SSharableBaseResourceManager) ListItemFilter(
	ctx context.Context,
	q *sqlchemy.SQuery,
	userCred mcclient.TokenCredential,
	query apis.SharableResourceBaseListInput,
) (*sqlchemy.SQuery, error) {
	if query.IsPublic != nil {
		if *query.IsPublic == true {
			q = q.IsTrue("is_public")
		} else {
			q = q.IsFalse("is_public")
		}
	}
	if len(query.PublicScope) > 0 {
		q = q.Equals("public_scope", query.PublicScope)
	}
	return q, nil
}

func (manager *SSharableBaseResourceManager) FetchCustomizeColumns(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	query jsonutils.JSONObject,
	objs []interface{},
	fields stringutils2.SSortedStrings,
	isList bool,
) []apis.SharableResourceBaseInfo {
	rows := make([]apis.SharableResourceBaseInfo, len(objs))

	var resType string
	resIds := make([]string, len(rows))
	var resScope rbacscope.TRbacScope
	for i := range rows {
		if model, ok := objs[i].(ISharableBaseModel); ok {
			if len(resType) == 0 {
				resType = model.Keyword()
			}
			if len(resScope) == 0 {
				resScope = model.GetModelManager().ResourceScope()
			}
			resIds[i] = model.GetId()
		}
	}

	q := SharedResourceManager.Query()
	q = q.Equals("resource_type", resType)

	sharedResourceMap := make(map[string][]SSharedResource)
	err := FetchQueryObjectsByIds(q, "resource_id", resIds, &sharedResourceMap)
	if err != nil {
		log.Errorf("FetchQueryObjectsByIds for shared resource fail %s", err)
		return rows
	}

	targetTenantIds := stringutils2.NewSortedStrings([]string{})
	targetDomainIds := stringutils2.NewSortedStrings([]string{})

	for _, srs := range sharedResourceMap {
		for _, sr := range srs {
			switch sr.TargetType {
			case SharedTargetProject:
				targetTenantIds = stringutils2.Append(targetTenantIds, sr.TargetProjectId)
			case SharedTargetDomain:
				targetDomainIds = stringutils2.Append(targetDomainIds, sr.TargetProjectId)
			}
		}
	}

	var tenantMap map[string]STenant
	var domainMap map[string]STenant

	if len(targetTenantIds) > 0 {
		tenantMap = DefaultProjectsFetcher(ctx, targetTenantIds, false)
	}
	if len(targetDomainIds) > 0 {
		domainMap = DefaultProjectsFetcher(ctx, targetDomainIds, true)
	}

	for i := range rows {
		resId := resIds[i]
		if srs, ok := sharedResourceMap[resId]; ok {
			projects := make([]apis.SharedProject, 0)
			domains := make([]apis.SharedDomain, 0)
			for _, sr := range srs {
				switch sr.TargetType {
				case SharedTargetProject:
					project := apis.SharedProject{}
					project.Id = sr.TargetProjectId
					if tenant, ok := tenantMap[sr.TargetProjectId]; ok {
						project.Name = tenant.Name
						project.Domain = tenant.Domain
						project.DomainId = tenant.DomainId
					}
					projects = append(projects, project)
				case SharedTargetDomain:
					domain := apis.SharedDomain{}
					domain.Id = sr.TargetProjectId
					if tenant, ok := domainMap[sr.TargetProjectId]; ok {
						domain.Name = tenant.Name
					}
					domains = append(domains, domain)
				}
			}
			rows[i].SharedProjects = projects
			rows[i].SharedDomains = domains
		}
	}

	return rows
}

func SharableManagerValidateCreateData(
	manager IStandaloneModelManager,
	ctx context.Context,
	userCred mcclient.TokenCredential,
	ownerId mcclient.IIdentityProvider,
	query jsonutils.JSONObject,
	input apis.SharableResourceBaseCreateInput,
) (apis.SharableResourceBaseCreateInput, error) {
	resScope := manager.ResourceScope()
	reqScope := resScope
	isPublic := true
	switch resScope {
	case rbacscope.ScopeProject:
		if input.PublicScope == string(rbacscope.ScopeSystem) {
			input.IsPublic = &isPublic
			reqScope = rbacscope.ScopeSystem
		} else if input.PublicScope == string(rbacscope.ScopeDomain) {
			if consts.GetNonDefaultDomainProjects() {
				// only if non_default_domain_projects turned on, allow sharing to domain
				input.IsPublic = &isPublic
				reqScope = rbacscope.ScopeDomain
			} else {
				input.IsPublic = &isPublic
				reqScope = rbacscope.ScopeSystem
			}
		} else if input.IsPublic != nil && *input.IsPublic && len(input.PublicScope) == 0 {
			// backward compatible, if only is_public is true, make it share to system
			input.IsPublic = &isPublic
			input.PublicScope = string(rbacscope.ScopeSystem)
			reqScope = rbacscope.ScopeSystem
		} else {
			input.IsPublic = nil
			input.PublicScope = "" // string(rbacscope.ScopeNone)
		}
	case rbacscope.ScopeDomain:
		if consts.GetNonDefaultDomainProjects() {
			// only if non_default_domain_projects turned on, allow sharing domain resources
			if input.PublicScope == string(rbacscope.ScopeSystem) {
				input.IsPublic = &isPublic
				reqScope = rbacscope.ScopeSystem
			} else if input.IsPublic != nil && *input.IsPublic && len(input.PublicScope) == 0 {
				// backward compatible, if only is_public is true, make it share to system
				input.IsPublic = &isPublic
				input.PublicScope = string(rbacscope.ScopeSystem)
				reqScope = rbacscope.ScopeSystem
			} else {
				input.IsPublic = nil
				input.PublicScope = "" // string(rbacscope.ScopeNone)
			}
		} else {
			// if non_default_domain_projects turned off, all domain resources shared to system
			input.IsPublic = &isPublic
			input.PublicScope = string(rbacscope.ScopeSystem)
			reqScope = rbacscope.ScopeSystem
		}
	default:
		return input, errors.Wrap(httperrors.ErrInputParameter, "the resource is not sharable")
	}
	if input.IsPublic != nil && *input.IsPublic {
		// TODO: deal with policyTags
		allowScope, _ := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), manager.KeywordPlural(), policy.PolicyActionPerform, "public")
		if reqScope.HigherThan(allowScope) {
			return input, errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", reqScope, allowScope)
		}
	}
	return input, nil
}

func SharableManagerFilterByOwner(ctx context.Context, manager IStandaloneModelManager, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
	if owner != nil {
		resScope := manager.ResourceScope()
		if resScope == rbacscope.ScopeUser {
			targetProjectId := owner.GetProjectId()
			if len(targetProjectId) == 0 && userCred != nil {
				targetProjectId = userCred.GetProjectId()
			}
			subq := SharedResourceManager.Query("resource_id")
			subq = subq.Equals("resource_type", manager.Keyword())
			subq = subq.Equals("target_project_id", targetProjectId)
			subq = subq.Equals("target_type", SharedTargetProject)
			subq2 := SharedResourceManager.Query("resource_id")
			subq2 = subq2.Equals("resource_type", manager.Keyword())
			subq2 = subq2.Equals("target_project_id", owner.GetProjectDomainId())
			subq2 = subq2.Equals("target_type", SharedTargetDomain)
			filters := []sqlchemy.ICondition{
				sqlchemy.AND(
					sqlchemy.IsTrue(q.Field("is_public")),
					sqlchemy.Equals(q.Field("public_scope"), rbacscope.ScopeSystem),
				),
				sqlchemy.AND(
					sqlchemy.IsTrue(q.Field("is_public")),
					sqlchemy.Equals(q.Field("public_scope"), rbacscope.ScopeDomain),
					sqlchemy.OR(
						sqlchemy.In(q.Field("id"), subq2.SubQuery()),
					),
				),
				sqlchemy.In(q.Field("id"), subq.SubQuery()),
			}
			ownerUserId := owner.GetUserId()
			if len(ownerUserId) > 0 {
				filters = append(filters, sqlchemy.Equals(q.Field("owner_id"), ownerUserId))
			}
			q = q.Filter(sqlchemy.OR(filters...))
			if userCred != nil {
				result := policy.PolicyManager.Allow(scope, userCred, consts.GetServiceType(), manager.KeywordPlural(), policy.PolicyActionList)
				if !result.ObjectTags.IsEmpty() {
					policyTagFilters := tagutils.STagFilters{}
					policyTagFilters.AddFilters(result.ObjectTags)
					q = ObjectIdQueryWithTagFilters(ctx, q, "id", manager.Keyword(), policyTagFilters)
				}
			}
		} else if resScope == rbacscope.ScopeProject && scope == rbacscope.ScopeProject {
			ownerProjectId := owner.GetProjectId()
			if len(ownerProjectId) > 0 {
				subq := SharedResourceManager.Query("resource_id")
				subq = subq.Equals("resource_type", manager.Keyword())
				subq = subq.Equals("target_project_id", ownerProjectId)
				subq = subq.Equals("target_type", SharedTargetProject)
				subq2 := SharedResourceManager.Query("resource_id")
				subq2 = subq2.Equals("resource_type", manager.Keyword())
				subq2 = subq2.Equals("target_project_id", owner.GetProjectDomainId())
				subq2 = subq2.Equals("target_type", SharedTargetDomain)
				q = q.Filter(sqlchemy.OR(
					sqlchemy.Equals(q.Field("tenant_id"), ownerProjectId),
					sqlchemy.AND(
						sqlchemy.IsTrue(q.Field("is_public")),
						sqlchemy.Equals(q.Field("public_scope"), rbacscope.ScopeSystem),
					),
					sqlchemy.AND(
						sqlchemy.IsTrue(q.Field("is_public")),
						sqlchemy.Equals(q.Field("public_scope"), rbacscope.ScopeDomain),
						sqlchemy.OR(
							sqlchemy.Equals(q.Field("domain_id"), owner.GetProjectDomainId()),
							sqlchemy.In(q.Field("id"), subq2.SubQuery()),
						),
					),
					sqlchemy.In(q.Field("id"), subq.SubQuery()),
				))
				if userCred != nil {
					result := policy.PolicyManager.Allow(scope, userCred, consts.GetServiceType(), manager.KeywordPlural(), policy.PolicyActionList)
					if !result.ObjectTags.IsEmpty() {
						policyTagFilters := tagutils.STagFilters{}
						policyTagFilters.AddFilters(result.ObjectTags)
						q = ObjectIdQueryWithTagFilters(ctx, q, "id", manager.Keyword(), policyTagFilters)
					}
				}
			}
		} else if (resScope == rbacscope.ScopeDomain && (scope == rbacscope.ScopeProject || scope == rbacscope.ScopeDomain)) || (resScope == rbacscope.ScopeProject && scope == rbacscope.ScopeDomain) {
			// domain view
			ownerDomainId := owner.GetProjectDomainId()
			if len(ownerDomainId) > 0 {
				subq := SharedResourceManager.Query("resource_id")
				subq = subq.Equals("resource_type", manager.Keyword())
				subq = subq.Equals("target_project_id", ownerDomainId)
				subq = subq.Equals("target_type", SharedTargetDomain)
				q = q.Filter(sqlchemy.OR(
					sqlchemy.Equals(q.Field("domain_id"), ownerDomainId),
					sqlchemy.AND(
						sqlchemy.IsTrue(q.Field("is_public")),
						sqlchemy.Equals(q.Field("public_scope"), rbacscope.ScopeSystem),
					),
					sqlchemy.AND(
						sqlchemy.IsTrue(q.Field("is_public")),
						sqlchemy.In(q.Field("id"), subq.SubQuery()),
					),
				))
				if userCred != nil {
					result := policy.PolicyManager.Allow(scope, userCred, consts.GetServiceType(), manager.KeywordPlural(), policy.PolicyActionList)
					if !result.ProjectTags.IsEmpty() && resScope == rbacscope.ScopeProject {
						policyTagFilters := tagutils.STagFilters{}
						policyTagFilters.AddFilters(result.ProjectTags)
						q = ObjectIdQueryWithTagFilters(ctx, q, "tenant_id", "project", policyTagFilters)
					}
					if !result.ObjectTags.IsEmpty() {
						policyTagFilters := tagutils.STagFilters{}
						policyTagFilters.AddFilters(result.ObjectTags)
						q = ObjectIdQueryWithTagFilters(ctx, q, "id", manager.Keyword(), policyTagFilters)
					}
				}
			}
		} else {
			log.Debugf("res_scope: %s view_scope: %s", resScope, scope)
			// system view
			if userCred != nil {
				result := policy.PolicyManager.Allow(scope, userCred, consts.GetServiceType(), manager.KeywordPlural(), policy.PolicyActionList)
				log.Debugf("policy result: %s", jsonutils.Marshal(result))
				if !result.DomainTags.IsEmpty() && (resScope == rbacscope.ScopeDomain || resScope == rbacscope.ScopeProject) && scope == rbacscope.ScopeSystem {
					subq := manager.Query("id")
					policyTagFilters := tagutils.STagFilters{}
					policyTagFilters.AddFilters(result.DomainTags)
					subq = ObjectIdQueryWithTagFilters(ctx, subq, "domain_id", "domain", policyTagFilters)
					q = q.Filter(sqlchemy.OR(
						sqlchemy.In(q.Field("id"), subq.SubQuery()),
						sqlchemy.AND(
							sqlchemy.IsTrue(q.Field("is_public")),
							sqlchemy.Equals(q.Field("public_scope"), rbacscope.ScopeSystem),
						),
					))
				}
				if !result.ProjectTags.IsEmpty() && resScope == rbacscope.ScopeProject {
					subq := manager.Query("id")
					policyTagFilters := tagutils.STagFilters{}
					policyTagFilters.AddFilters(result.ProjectTags)
					subq = ObjectIdQueryWithTagFilters(ctx, subq, "tenant_id", "project", policyTagFilters)
					q = q.Filter(sqlchemy.OR(
						sqlchemy.In(q.Field("id"), subq.SubQuery()),
						sqlchemy.AND(
							sqlchemy.IsTrue(q.Field("is_public")),
							sqlchemy.Equals(q.Field("public_scope"), rbacscope.ScopeSystem),
						),
					))
				}
				if !result.ObjectTags.IsEmpty() {
					policyTagFilters := tagutils.STagFilters{}
					policyTagFilters.AddFilters(result.ObjectTags)
					q = ObjectIdQueryWithTagFilters(ctx, q, "id", manager.Keyword(), policyTagFilters)
				}
			}
		}
	}
	return q
}

type SSharableBaseResource struct {
	// 是否共享
	IsPublic bool `default:"false" nullable:"false" list:"user" create:"domain_optional"`
	// 默认共享范围
	PublicScope string `width:"16" charset:"ascii" nullable:"false" default:"system" list:"user" create:"domain_optional"`
	// 共享设置的来源, local: 本地设置, cloud: 从云上同步过来
	// example: local
	PublicSrc string `width:"10" charset:"ascii" nullable:"true" list:"user" json:"public_src"`
}

type ISharableBaseModel interface {
	IStandaloneModel
	ISharableBase
}

type ISharableBase interface {
	SetShare(scoe rbacscope.TRbacScope)
	GetIsPublic() bool
	GetPublicScope() rbacscope.TRbacScope
	GetSharableTargetDomainIds() []string
	GetRequiredSharedDomainIds() []string
	GetSharedDomains() []string
}

func ISharableChangeOwnerCandidateDomainIds(model ISharableBaseModel) []string {
	var candidates []string
	if model.GetIsPublic() {
		switch model.GetPublicScope() {
		case rbacscope.ScopeSystem:
			return candidates
		case rbacscope.ScopeDomain:
			candidates = model.GetSharedDomains()
		}
	}
	ownerId := model.GetOwnerId()
	if ownerId != nil && len(ownerId.GetProjectDomainId()) > 0 {
		candidates = append(candidates, ownerId.GetProjectDomainId())
	}
	return candidates
}

func ISharableMergeChangeOwnerCandidateDomainIds(model ISharableBaseModel, candidates ...[]string) []string {
	var ret stringutils2.SSortedStrings
	for i := range candidates {
		if len(candidates[i]) > 0 {
			cand := stringutils2.NewSortedStrings(candidates[i])
			ownerId := model.GetOwnerId()
			if ownerId != nil && len(ownerId.GetProjectDomainId()) > 0 && !cand.Contains(ownerId.GetProjectDomainId()) {
				cand = stringutils2.Append(cand, ownerId.GetProjectDomainId())
			}
			if len(ret) > 0 {
				ret = stringutils2.Intersect(ret, cand)
			} else {
				ret = stringutils2.NewSortedStrings(cand)
			}
		}
	}
	return ret
}

func ISharableMergeShareRequireDomainIds(requiredIds ...[]string) []string {
	var ret stringutils2.SSortedStrings
	for i := range requiredIds {
		if len(requiredIds[i]) > 0 {
			req := stringutils2.NewSortedStrings(requiredIds[i])
			if ret == nil {
				ret = req
			} else {
				ret = stringutils2.Merge(ret, req)
			}
		} else {
			return nil
		}
	}
	return ret
}

func SharableModelIsSharable(model ISharableBaseModel, reqUsrId mcclient.IIdentityProvider) bool {
	if model.GetIsPublic() && model.GetPublicScope() == rbacscope.ScopeSystem {
		return true
	}
	ownerId := model.GetOwnerId()
	if model.GetIsPublic() && model.GetPublicScope() == rbacscope.ScopeDomain {
		if ownerId != nil && ownerId.GetProjectDomainId() == reqUsrId.GetProjectDomainId() {
			return true
		}
		q := SharedResourceManager.Query().Equals("resource_id", model.GetId())
		q = q.Equals("resource_type", model.Keyword())
		q = q.Equals("target_project_id", reqUsrId.GetProjectDomainId())
		q = q.Equals("target_type", SharedTargetDomain)
		cnt, _ := q.CountWithError()
		if cnt > 0 {
			return true
		}
	}
	if model.GetPublicScope() == rbacscope.ScopeProject {
		if ownerId != nil && ownerId.GetProjectId() == reqUsrId.GetProjectId() {
			return true
		}
		q := SharedResourceManager.Query().Equals("resource_id", model.GetId())
		q = q.Equals("resource_type", model.Keyword())
		q = q.Equals("target_project_id", reqUsrId.GetProjectId())
		q = q.Equals("target_type", SharedTargetProject)
		cnt, _ := q.CountWithError()
		if cnt > 0 {
			return true
		}
	}
	return false
}

func (m *SSharableBaseResource) SetShare(scope rbacscope.TRbacScope) {
	pub := false
	if scope != rbacscope.ScopeNone {
		pub = true
	}
	m.IsPublic = pub
	m.PublicScope = string(scope)
	m.PublicSrc = string(apis.OWNER_SOURCE_LOCAL)
}

func (m SSharableBaseResource) GetIsPublic() bool {
	return m.IsPublic
}

func (m SSharableBaseResource) GetPublicScope() rbacscope.TRbacScope {
	return rbacscope.String2Scope(m.PublicScope)
}

func SharablePerformPublic(model ISharableBaseModel, ctx context.Context, userCred mcclient.TokenCredential, input apis.PerformPublicProjectInput) error {
	var err error

	resourceScope := model.GetModelManager().ResourceScope()
	targetScope := rbacscope.String2ScopeDefault(input.Scope, rbacscope.ScopeSystem)
	if resourceScope.HigherThan(targetScope) {
		return errors.Wrapf(httperrors.ErrNotSupported, "cannot share %s resource to %s", resourceScope, targetScope)
	}

	if len(input.SharedProjectIds) > 0 && len(input.SharedDomainIds) > 0 {
		return errors.Wrap(httperrors.ErrInputParameter, "cannot set shared_projects and shared_domains at the same time")
	} else if len(input.SharedProjectIds) > 0 && targetScope != rbacscope.ScopeProject {
		targetScope = rbacscope.ScopeProject
	} else if len(input.SharedDomainIds) > 0 && targetScope != rbacscope.ScopeDomain {
		targetScope = rbacscope.ScopeDomain
	}

	shareResult := apis.PerformPublicProjectInput{}
	shareResult.Scope = string(targetScope)

	candidateIds := model.GetSharableTargetDomainIds()
	requireIds := model.GetRequiredSharedDomainIds()

	switch targetScope {
	case rbacscope.ScopeProject:
		if len(requireIds) == 0 {
			return errors.Wrap(httperrors.ErrForbidden, "require to be shared to system")
		} else if len(requireIds) > 1 {
			return errors.Wrap(httperrors.ErrForbidden, "require to be shared to other domain")
		}
		// if len(input.SharedProjects) == 0 {
		//	return errors.Wrap(httperrors.ErrEmptyRequest, "empty shared target project list")
		// }
		shareResult.SharedProjectIds, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetProject, input.SharedProjectIds, nil, nil)
		if err != nil {
			return errors.Wrap(err, "shareToTarget")
		}
		if len(shareResult.SharedProjectIds) == 0 {
			targetScope = rbacscope.ScopeNone
		}
	case rbacscope.ScopeDomain:
		if !consts.GetNonDefaultDomainProjects() {
			return errors.Wrap(httperrors.ErrForbidden, "not allow to share to domain when non_default_domain_projects turned off")
		}
		if len(requireIds) == 0 {
			return errors.Wrap(httperrors.ErrForbidden, "require to be shared to system")
		}
		_, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetProject, nil, nil, nil)
		if err != nil {
			return errors.Wrap(err, "shareToTarget clean projects")
		}
		shareResult.SharedDomainIds, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetDomain, input.SharedDomainIds, candidateIds, requireIds)
		if err != nil {
			return errors.Wrap(err, "shareToTarget add domains")
		}
		if len(shareResult.SharedDomainIds) == 0 && resourceScope == rbacscope.ScopeDomain {
			targetScope = rbacscope.ScopeNone
		}
	case rbacscope.ScopeSystem:
		if len(candidateIds) > 0 {
			return httperrors.NewForbiddenError("sharing is limited to domains %s", jsonutils.Marshal(candidateIds))
		}
		_, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetProject, nil, nil, nil)
		if err != nil {
			return errors.Wrap(err, "shareToTarget clean projects")
		}
		_, err = SharedResourceManager.shareToTarget(ctx, userCred, model, SharedTargetDomain, nil, nil, nil)
		if err != nil {
			return errors.Wrap(err, "shareToTarget clean domainss")
		}
	}

	allowScope, policyTags := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), model.KeywordPlural(), policy.PolicyActionPerform, "public")
	if targetScope.HigherThan(allowScope) {
		return errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", targetScope, allowScope)
	}
	requireScope := model.GetPublicScope()
	if requireScope.HigherThan(allowScope) {
		return errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", requireScope, allowScope)
	}

	err = objectConfirmPolicyTags(ctx, model, policyTags)
	if err != nil {
		return errors.Wrap(err, "objectConfirmPolicyTags")
	}

	_, err = Update(model, func() error {
		model.SetShare(targetScope)
		return nil
	})

	if err != nil {
		return errors.Wrap(err, "Update")
	}

	if targetScope != rbacscope.ScopeNone {
		OpsLog.LogEvent(model, ACT_PUBLIC, shareResult, userCred)
		logclient.AddActionLogWithContext(ctx, model, logclient.ACT_PUBLIC, shareResult, userCred, true)
	}

	model.GetIStandaloneModel().ClearSchedDescCache()
	return nil
}

func SharablePerformPrivate(model ISharableBaseModel, ctx context.Context, userCred mcclient.TokenCredential) error {
	if !model.GetIsPublic() && model.GetPublicScope() == rbacscope.ScopeNone {
		return nil
	}

	resourceScope := model.GetModelManager().ResourceScope()
	if resourceScope == rbacscope.ScopeDomain && !consts.GetNonDefaultDomainProjects() {
		return errors.Wrap(httperrors.ErrForbidden, "not allow to private domain resource")
	}

	requireIds := model.GetRequiredSharedDomainIds()
	if len(requireIds) == 0 {
		return errors.Wrap(httperrors.ErrForbidden, "require to be shared to system")
	} else if len(requireIds) > 1 {
		return errors.Wrap(httperrors.ErrForbidden, "require to be shared to other domain")
	}

	requireScope := model.GetPublicScope()
	allowScope, policyTags := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), model.GetModelManager().KeywordPlural(), policy.PolicyActionPerform, "private")
	if requireScope.HigherThan(allowScope) {
		return errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", requireScope, allowScope)
	}

	err := objectConfirmPolicyTags(ctx, model, policyTags)
	if err != nil {
		return errors.Wrap(err, "objectConfirmPolicyTags")
	}

	err = SharedResourceManager.CleanModelShares(ctx, userCred, model)
	if err != nil {
		return errors.Wrap(err, "CleanModelShares")
	}

	diff, err := Update(model, func() error {
		model.SetShare(rbacscope.ScopeNone)
		return nil
	})

	if err != nil {
		return errors.Wrap(err, "Update")
	}

	OpsLog.LogEvent(model, ACT_PRIVATE, diff, userCred)
	logclient.AddActionLogWithContext(ctx, model, logclient.ACT_PRIVATE, diff, userCred, true)

	model.GetIStandaloneModel().ClearSchedDescCache()

	return nil
}

func SharableGetSharedProjects(model ISharableBaseModel, targetType string) []string {
	sharedResources := make([]SSharedResource, 0)
	q := SharedResourceManager.Query()
	q = q.Equals("resource_type", model.Keyword())
	q = q.Equals("resource_id", model.GetId())
	q = q.Equals("target_type", targetType)
	err := q.All(&sharedResources)
	if err != nil {
		return nil
	}
	res := make([]string, len(sharedResources))
	for i := range sharedResources {
		res[i] = sharedResources[i].TargetProjectId
	}
	return res
}

func SharableModelIsShared(model ISharableBaseModel) bool {
	q := SharedResourceManager.Query()
	q = q.Equals("resource_type", model.Keyword())
	q = q.Equals("resource_id", model.GetId())
	cnt, _ := q.CountWithError()
	if cnt > 0 {
		return true
	}
	switch model.GetPublicScope() {
	case rbacscope.ScopeSystem:
		if model.GetIsPublic() {
			return true
		}
	case rbacscope.ScopeDomain:
		if model.GetModelManager().ResourceScope() == rbacscope.ScopeProject {
			return true
		}
	}
	return false
}

func SharableModelCustomizeCreate(model ISharableBaseModel, ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
	if !data.Contains("public_scope") {
		resScope := model.GetModelManager().ResourceScope()
		if resScope == rbacscope.ScopeDomain && consts.GetNonDefaultDomainProjects() {
			// only if non_default_domain_projects turned on, do the following
			isManaged := false
			if managedModel, ok := model.(IManagedResourceBase); ok {
				isManaged = managedModel.IsManaged()
			}
			// log.Debugf("isManaged: %v IsAdminAllowPerform %v ownerId.GetProjectDomainId %s userCred.GetProjectDomainId %s", isManaged, IsAdminAllowPerform(ctx, userCred, model, "public"), ownerId.GetProjectDomainId(), userCred.GetProjectDomainId())
			if !isManaged && IsAdminAllowPerform(ctx, userCred, model, "public") && ownerId.GetProjectDomainId() == userCred.GetProjectDomainId() {
				model.SetShare(rbacscope.ScopeSystem)
				data.(*jsonutils.JSONDict).Set("public_scope", jsonutils.NewString(string(rbacscope.ScopeSystem)))
			}
		}
	}
	if !data.Contains("public_scope") {
		model.SetShare(rbacscope.ScopeNone)
	}
	return nil
}
