package project

import (
	"context"
	"errors"
	"sync"
	"time"

	"github.com/tidbcloud/account/thirdparty/mgmt"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"

	"github.com/tidbcloud/account/thirdparty/billing2"

	"github.com/tidbcloud/account/internel/dao/role"

	"github.com/tidbcloud/account/internel/dto"

	"github.com/tidbcloud/account/central/utils"

	"github.com/tidbcloud/account/internel/dao/comdao"

	"github.com/tidbcloud/account/commons/config"

	userDao "github.com/tidbcloud/account/internel/dao/user"

	"github.com/pingcap/errcode"

	"github.com/tidbcloud/account/commons/database"

	"github.com/duke-git/lancet/v2/slice"
	"github.com/tidbcloud/account/idl/pbgen/proto/central"
	"github.com/tidbcloud/tiinf/errs"

	"github.com/tidbcloud/account/thirdparty/infra"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/internel/dao/project"
	"gorm.io/gorm"
)

const (
	projectCountUpperLimit            = 50
	tidbxHiddenProjectCountUpperLimit = 1
)

type ProjectSvcIface interface {
	GetProjectByID(ctx context.Context, projectID uint64) (*entity.Project, error)
	ListProjects(ctx context.Context, orgIDs []uint64, projectIDs []uint64, status []string) ([]entity.Project, error)
	ListActiveProjectsByOrgID(ctx context.Context, orgID uint64) ([]entity.Project, error)
	ListActiveProjectsByOrgIDs(ctx context.Context, orgIDs []uint64) ([]OrgProjects, error)
	AdvancedListProjectsByPage(ctx context.Context, pageParams dto.PageParams) (*utils.Paginator, error)
	ListUserVisibleProjects(ctx context.Context, userID uint64, orgID []uint64) ([]entity.Project, error)
	ListUserVisibleProjectIds(ctx context.Context, userID uint64, orgID []uint64) ([]uint64, error)
	// get user' org ,user's project  and org role ,project role
	GetUserOrgsProjects(ctx context.Context, userId uint64) ([]*central.OrgInfo, []*central.ProjectInfo, error)
	CreateProject(ctx context.Context, req project.CreateProjectReq) (*entity.Project, error)
	DeleteProject(ctx context.Context, projectId uint64) error
	UpdateProjectName(ctx context.Context, projectId uint64, projectName string) error
}

var (
	projectSvcInitOnce sync.Once
	projectSvcInstance ProjectSvcIface
)

func GetProjectSvc() (ProjectSvcIface, error) {
	projectSvcInitOnce.Do(func() {
		projectSvcInstance = NewProjectSvc(database.DBConn.DB, dao.GetDaoHub())

	})
	return projectSvcInstance, nil
}

type ProjectSvc struct {
	db  *gorm.DB
	hub *dao.DaoHub
}

func NewProjectSvc(db *gorm.DB, hub *dao.DaoHub) ProjectSvcIface {
	return &ProjectSvc{
		db:  db,
		hub: hub,
	}
}

func (s *ProjectSvc) GetProjectByID(ctx context.Context, projectID uint64) (*entity.Project, error) {
	tilog.Info(ctx, "GetProjectByID", zap.Uint64("project_id", projectID))
	return s.hub.Project.GetProjectByID(ctx, s.db.WithContext(ctx), projectID)
}

func (s *ProjectSvc) ListProjects(ctx context.Context, orgIDs []uint64, projectIDs []uint64, status []string) ([]entity.Project, error) {
	return s.hub.Project.ListProjects(ctx, s.db.WithContext(ctx), orgIDs, projectIDs, status)
}

func (s *ProjectSvc) ListActiveProjectsByOrgID(ctx context.Context, orgID uint64) ([]entity.Project, error) {
	return s.hub.Project.ListActiveProjectsByOrgID(ctx, s.db.WithContext(ctx), orgID)
}
func (s *ProjectSvc) GetActiveProjectIds(ctx context.Context, orgID uint64) ([]uint64, error) {
	var pids []uint64
	params := dto.ListParams{
		Filters: map[string]interface{}{
			"tenant_id": orgID,
			"status":    entity.ProjectStatusActive,
		},
		SortBy: "id",
		Order:  "asc",
	}
	objs, err := comdao.ListEntities(ctx, s.db.WithContext(ctx), params, &entity.Project{})
	if err != nil {

		return pids, err
	}
	projects := objs.([]entity.Project)

	for _, p := range projects {
		pids = append(pids, p.ID.Uint64())
	}
	tilog.Info(ctx, "GetActiveProjectIds", zap.Any("orgID", orgID), zap.Any("projectIds", pids))
	return pids, err
}

func (s *ProjectSvc) ListActiveProjectsByOrgIDs(ctx context.Context, orgIDs []uint64) ([]OrgProjects, error) {
	results := make([]OrgProjects, len(orgIDs))

	var projects []entity.Project
	var err error
	err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		projects, err = s.hub.Project.ListProjects(ctx, s.db.WithContext(ctx), orgIDs, []uint64{}, []string{string(entity.ProjectStatusActive)})
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	var orgId2ProjectsMap = map[uint64][]entity.Project{}
	for _, p := range projects {
		orgid := p.TenantID.Uint64()
		ps := orgId2ProjectsMap[orgid]
		ps = append(ps, p)
		orgId2ProjectsMap[orgid] = ps
	}

	i := 0
	for orgid, ps := range orgId2ProjectsMap {
		results[i] = OrgProjects{
			OrgID:    orgid,
			Projects: ps,
		}
		i++
	}

	return results, nil
}

// ListUserVisibleProjects
//
//	@Description:
//	@Author wangletian 2023-09-20 16:34:04
//	@receiver s
//	@param ctx
//	@param userID
//	@param orgIDs
//	@return []entity.Project
//	@return error
func (s *ProjectSvc) ListUserVisibleProjects(ctx context.Context, userID uint64, orgIDs []uint64) ([]entity.Project, error) {
	var projects []entity.Project

	allProjects, err := dao.GetDaoHub().Project.ListProjects(ctx, database.DBConn.DB, orgIDs, nil, []string{string(entity.ProjectStatusActive)})
	if err != nil {
		tilog.Error(ctx, "ListUserVisibleProjects error", zap.Any("userID", userID), zap.Any("orgIDs", orgIDs), zap.Any("err", err))
		return nil, err
	}
	if len(allProjects) == 0 {
		tilog.Info(ctx, "ListUserVisibleProjects not found any active projects", zap.Any("orgIDs", orgIDs))
		return projects, nil
	}

	var orgOwnerProjects []entity.Project
	var nonOrgOwnerProjectIds []uint64
	var nonOrgOwnerProjectId2ProjectMap = map[uint64]entity.Project{}

	orgRoleBindings, err := dao.GetDaoHub().Role.ListUserRolesByUserIDAndOrgs(ctx, database.DBConn.DB, userID, orgIDs, []string{entity.RoleBindingStatusActive})
	if err != nil {
		tilog.Error(ctx, "ListUserVisibleProjects list user in org roles error", zap.Any("userID", userID), zap.Any("orgIDs", orgIDs), zap.Any("err", err))
		return nil, err
	}
	// find  ownerOrgIds  in  req.orgIDS
	var ownerOrgIds = map[uint64]struct{}{}
	for _, orgRoleBinding := range orgRoleBindings {
		if orgRoleBinding.Role == entity.RoleOrgOwner {
			ownerOrgIds[orgRoleBinding.ScopeID.Uint64()] = struct{}{}
		}
	}
	for _, p := range allProjects {
		if _, ok := ownerOrgIds[p.TenantID.Uint64()]; ok {
			orgOwnerProjects = append(orgOwnerProjects, p)
		} else {
			nonOrgOwnerProjectIds = append(nonOrgOwnerProjectIds, p.ID.Uint64())
			nonOrgOwnerProjectId2ProjectMap[p.ID.Uint64()] = p
		}
	}

	var hasPermissionProjects []entity.Project
	if len(nonOrgOwnerProjectIds) > 0 {
		roleBindings, err := dao.GetDaoHub().Role.ListUserRolesByProjects(ctx, database.DBConn.DB, userID, nonOrgOwnerProjectIds, []string{entity.RoleBindingStatusActive})
		if err != nil {
			tilog.Error(ctx, "ListUserVisibleProjects list user in project roles error", zap.Any("userID", userID), zap.Any("projectIds", nonOrgOwnerProjectIds), zap.Any("err", err))
			return nil, err
		}
		if len(roleBindings) > 0 {
			for _, rb := range roleBindings {
				hasPermissionPid := rb.ScopeID.Uint64()
				hasPermissionP := nonOrgOwnerProjectId2ProjectMap[hasPermissionPid]
				hasPermissionProjects = append(hasPermissionProjects, hasPermissionP)
			}
		}
	}

	if len(orgOwnerProjects) > 0 {
		projects = append(projects, orgOwnerProjects...)
	}
	if len(hasPermissionProjects) > 0 {
		projects = append(projects, hasPermissionProjects...)
	}

	return projects, nil
}

func (s *ProjectSvc) ListUserVisibleProjectIds(ctx context.Context, userId uint64, orgIds []uint64) ([]uint64, error) {
	var pids []uint64
	projects, err := s.ListUserVisibleProjects(ctx, userId, orgIds)
	if err != nil {
		return nil, err
	}
	for _, p := range projects {
		pids = append(pids, p.ID.Uint64())
	}
	return pids, nil
}

func isValidProjectType(tpe string) bool {
	if tpe == entity.ProjectTypeDedicated ||
		tpe == entity.ProjectTypeTiDBX ||
		tpe == entity.ProjectTypeTiDBXVirtual {
		return true
	}
	return false
}

func (s *ProjectSvc) CreateProject(ctx context.Context, req project.CreateProjectReq) (*entity.Project, error) {
	tilog.Info(ctx, "CreateProject param", zap.Any("param", req))
	// param check
	if req.OrgId == 0 || req.Name == "" || req.Type == "" {
		return nil, errs.NewBadRequest("param invalid", nil)
	}
	// check project type
	if !isValidProjectType(req.Type) {
		tilog.Warn(ctx, "CreateProject fail: project type is invalid", zap.Any("type", req.Type))
		return nil, errs.NewBadRequest("project_type is invalid", nil)
	}
	// check operator
	var operatorType entity.TargetType
	if req.OperatorID != 0 {
		operatorType = entity.ParseTargetType(req.OperatorType)
		if operatorType == "" {
			return nil, errs.NewBadRequest("operator_type is invalid", nil)
		}
	}
	// check org status
	org, err := dao.GetDaoHub().Org.GetOrgByOrgID(ctx, database.DBConn.DB, req.OrgId)
	if err != nil {
		tilog.Error(ctx, "CreateProject GetOrgByOrgID error", zap.Any("org_id", req.OrgId), zap.Any("err", err))
		return nil, err
	}
	if org == nil || org.Status != entity.TenantStatusActive {
		tilog.Info(ctx, "CreateProject fail: org status is not active", zap.Any("org_id", req.OrgId))
		return nil, errs.NewBadRequest("The org status is not active.", nil)
	}
	// check project name and project count
	err = checkProjectNameAndCount(ctx, req)
	if err != nil {
		return nil, err
	}
	var sourceProjectRoleBindings []entity.RoleBinding
	// check source project info
	if req.SourceProjectId != 0 {
		sourceProject, err := dao.GetDaoHub().Project.GetProjectByID(ctx, database.DBConn.DB, req.SourceProjectId)
		if err != nil {
			tilog.Error(ctx, "CreateProject get source project error", zap.Any("project_id", req.SourceProjectId), zap.Any("err", err))
			return nil, errs.NewInternalUnknown("get source project error", err)
		}
		if sourceProject.TenantID.Uint64() != req.OrgId {
			tilog.Info(ctx, "CreateProject call: source project and to created project are not in the same org", zap.Any("source_project_ref_org_id", sourceProject.TenantID))
			return nil, errs.NewBadRequest("copy roles only support to do on the same org", nil)
		}
		sourceProjectRoleBindings, err = dao.GetDaoHub().Role.ListRolesByScope(ctx, database.DBConn.DB, entity.RoleBindingScopeProject, req.SourceProjectId, []string{entity.RoleBindingStatusActive})
		if err != nil {
			tilog.Error(ctx, "CreateProject get source project role bindings error", zap.Any("project_id", req.SourceProjectId), zap.Any("err", err))
			return nil, errs.NewInternalUnknown("get source project role bindings error", err)
		}
	}

	// create project and assign role
	var retproject *entity.Project
	err = s.db.Transaction(func(tx *gorm.DB) error {
		// 1. create project
		retproject, err = dao.GetDaoHub().Project.CreateProject(ctx, tx, req)
		if err != nil {
			return err
		}
		// 2. assign role
		if req.SourceProjectId != 0 {
			if len(sourceProjectRoleBindings) > 0 {
				var copyRoleBindings []*entity.RoleBinding
				for _, binding := range sourceProjectRoleBindings {
					copyRoleBindings = append(copyRoleBindings, &entity.RoleBinding{
						TargetType: binding.TargetType,
						TargetID:   binding.TargetID,
						ScopeType:  entity.RoleBindingScopeProject,
						ScopeID:    retproject.ID,
						Role:       binding.Role,
						RoleID:     binding.RoleID,
						Status:     binding.Status,
					})
				}
				err = dao.GetDaoHub().Role.BatchCreateRoleBindings(ctx, tx, copyRoleBindings)
			}
		} else if req.OperatorID != 0 {
			_, err = dao.GetDaoHub().Role.CreateRole(ctx, tx, operatorType, req.OperatorID, entity.RoleBindingScopeProject, retproject.ID.Uint64(), entity.RoleProjectOwner, entity.RoleBindingStatusActive)
		}
		return err
	})
	if err != nil {
		tilog.Error(ctx, "CreateProject error", zap.Any("req", req), zap.Error(err))
		return nil, err
	}
	tilog.Info(ctx, "CreateProject success", zap.Any("project", retproject))
	return retproject, err
}

func allowedProjectMaxCount() int {
	if config.IsProdEnv() {
		return projectCountUpperLimit
	}
	return 150
}

func checkProjectNameAndCount(ctx context.Context, req project.CreateProjectReq) error {
	activeProjects, err := dao.GetDaoHub().Project.ListActiveProjectsByOrgID(ctx, database.DBConn.DB, req.OrgId)
	if err != nil {
		tilog.Error(ctx, "CreateProject ListActiveProjectsByOrgID error", zap.Any("org_id", req.OrgId), zap.Any("err", err))
		return err
	}
	if len(activeProjects) >= allowedProjectMaxCount() {
		tilog.Info(ctx, "CreateProject fail: project count reached the upper limit", zap.Any("org_id", req.OrgId), zap.Any("err", err))
		return errs.NewBadRequest("The project count reached the upper limit.", nil)
	}
	var eTidbxVirCnt, eDedicatedCnt = 0, 0
	for _, ap := range activeProjects {
		if ap.Name == req.Name {
			tilog.Info(ctx, "CreateProject fail: project name already exists")
			return errs.NewBadRequest("The project name already exists.", nil)
		}
		if ap.Type == entity.ProjectTypeTiDBXVirtual {
			eTidbxVirCnt += 1
		}
		if ap.Type == entity.ProjectTypeDedicated {
			eDedicatedCnt += 1
		}
	}
	if req.Type == entity.ProjectTypeTiDBXVirtual && eTidbxVirCnt >= tidbxHiddenProjectCountUpperLimit {
		tilog.Info(ctx, "CreateProject fail: already exist tidbx hidden project")
		return errs.NewBadRequest("only allowed to create one tidbx_hidden project", nil)
	}
	if req.Type == entity.ProjectTypeDedicated && eDedicatedCnt > 0 {
		// check free trial limit
		planInfo, err := billing2.GetHandler().GetTenantPlanInfo(ctx, req.OrgId)
		if err != nil || planInfo == nil {
			tilog.Error(ctx, "CreateProject call billing get org plan info error", zap.Any("org_id", req.OrgId), zap.Any("plan_info", planInfo), zap.Any("err", err))
			return err
		}
		plan := planInfo.EffectivePlan
		tilog.Info(ctx, "get org plan from billing", zap.Any("EffectivePlan", plan))
		if plan == string(entity.TenantPlanNewUser) || plan == string(entity.TenantPlanFreeTrial) {
			tilog.Info(ctx, "the EffectivePlan [new_user,free_trial] only allowed to own one project", zap.Any("EffectivePlan", plan))
			return errs.NewBadRequest("free trial users are only allowed to own one project", nil)
		}
	}

	return nil
}

func (s *ProjectSvc) UpdateProjectName(ctx context.Context, projectId uint64, projectName string) error {
	tilog.Info(ctx, "UpdateProjectName param", zap.Any("projectId", projectId), zap.Any("projectName", projectName))
	if projectName == "" {
		return errs.NewBadRequest("project_name is blank", nil)
	}
	pEnt, err := dao.GetDaoHub().Project.GetProjectByID(ctx, database.DBConn.DB, projectId)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			pEnt = nil
		} else {
			tilog.Error(ctx, "UpdateProjectName GetProjectByID error", zap.Uint64("projectId", projectId), zap.Any("err", err))
			return err
		}
	}
	if pEnt == nil {
		tilog.Info(ctx, "UpdateProjectName get db project is nil", zap.Any("projectId", projectId))
		return errs.NewNotFound("not found project", nil)
	}
	if pEnt.Name == projectName {
		tilog.Info(ctx, "UpdateProjectName to changed name is same with the original name, do not need to update", zap.Any("projectId", projectId), zap.Any("projectName", projectName))
		return nil
	}
	activeProjects, err := dao.GetDaoHub().Project.ListActiveProjectsByOrgID(ctx, database.DBConn.DB, pEnt.TenantID.Uint64())
	if err != nil {
		tilog.Error(ctx, "UpdateProjectName ListActiveProjectsByOrgID error", zap.Any("orgId", pEnt.TenantID), zap.Any("err", err))
		return err
	}
	for _, ap := range activeProjects {
		if ap.Name == projectName {
			return errs.NewBadRequest("The project name already exists.", nil)
		}
	}
	err = dao.GetDaoHub().Project.UpdateProjectByID(ctx, database.DBConn.DB, &entity.Project{
		Updatable: pEnt.Updatable,
		Name:      projectName,
	})
	if err != nil {
		tilog.Info(ctx, "UpdateProjectName UpdateProjectByID error", zap.Any("projectId", projectId), zap.Any("projectName", projectName), zap.Any("err", err))
		return err
	}
	tilog.Info(ctx, "UpdateProjectName success", zap.Any("projectId", projectId), zap.Any("projectName", projectName))
	return nil
}

// GetUserOrgsProjects
//
//	@Description:	get user' org ,user's project  and org role ,project role
//	@Author wangletian 2024-03-14 15:06:39
//	@receiver s
//	@param ctx
//	@param userId
//	@return []*central.OrgInfo
//	@return []*central.ProjectInfo
//	@return error
func (s *ProjectSvc) GetUserOrgsProjects(ctx context.Context, userId uint64) ([]*central.OrgInfo, []*central.ProjectInfo, error) {

	//orgSvc, _ := deps.GetOrgSvc()
	//projectSvc, _ := deps.GetProjectSvc()

	var respOrgs []*central.OrgInfo
	var respProjects []*central.ProjectInfo
	var projects []entity.Project
	var orgIds []uint64
	var orgIDs []entity.ID

	//
	tenants, err := s.hub.Cross.ListOrgsByUserID(ctx, s.db.WithContext(ctx), userId, []string{string(entity.UserStatusActive)})
	if err != nil {
		return nil, nil, err
	}
	//  find all  !=enterprise  org  id
	for _, org := range tenants {
		if org.OrgType != entity.OrgTypeEnterprise {
			orgIds = append(orgIds, org.ID.Uint64())
			orgIDs = append(orgIDs, org.ID)
		}
	}
	if len(orgIds) > 0 {
		// quer projects int the  org
		projects, err = s.ListUserVisibleProjects(ctx, userId, orgIds)
		if err != nil {
			return nil, nil, err
		}
	}
	var pIDs []entity.ID
	for _, p := range projects {
		pIDs = append(pIDs, p.ID)
	}

	var roleBindings []entity.RoleBinding
	if len(orgIds) > 0 {
		// query  org role  and project role
		filter := role.RoleBindingFilter{
			TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
			TargetIDs:  []entity.ID{entity.ID(userId)},
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
			ScopeIDs:   orgIDs,
			Status:     []string{entity.RoleBindingStatusActive},
		}
		orgRoleBindings, err := s.hub.Role.ListRoleBindings(ctx, s.db.WithContext(ctx), filter)
		if err != nil {
			tilog.Error(ctx, "get user orgs projects, fail ListRoleBindings ", zap.Error(err))
			return nil, nil, err
		}
		roleBindings = append(roleBindings, orgRoleBindings...)
	}
	if len(pIDs) > 0 {
		filter := role.RoleBindingFilter{
			TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
			TargetIDs:  []entity.ID{entity.ID(userId)},
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeProject},
			ScopeIDs:   pIDs,
			Status:     []string{entity.RoleBindingStatusActive},
		}

		projectRoleBindings, err := s.hub.Role.ListRoleBindings(ctx, s.db.WithContext(ctx), filter)
		if err != nil {
			tilog.Error(ctx, "get user orgs projects, fail ListRoleBindings ", zap.Error(err))
			return nil, nil, err
		}
		roleBindings = append(roleBindings, projectRoleBindings...)

	}
	// find the user' role in the project
	orgRoleMap := make(map[uint64]string)
	projectRoleMap := make(map[uint64]string)
	for _, r := range roleBindings {
		if r.ScopeType == entity.RoleBindingScopeOrg {
			orgRoleMap[r.ScopeID.Uint64()] = r.Role
		} else if r.ScopeType == entity.RoleBindingScopeProject {
			projectRoleMap[r.ScopeID.Uint64()] = r.Role
		}
	}
	//
	for _, org := range tenants {
		if org.OrgType != entity.OrgTypeEnterprise {
			orgInfo := &central.OrgInfo{
				Id:      org.ID.Uint64(),
				Name:    org.Name,
				OrgRole: orgRoleMap[org.ID.Uint64()],
			}
			respOrgs = append(respOrgs, orgInfo)

		}
	}
	for _, p := range projects {
		projectInfo := &central.ProjectInfo{
			Id:          p.ID.Uint64(),
			Name:        p.Name,
			ProjectRole: projectRoleMap[p.ID.Uint64()],
			OrgId:       p.TenantID.Uint64(),
			Type:        p.Type,
		}
		respProjects = append(respProjects, projectInfo)

	}

	//resp := &central.GetUserOrgsProjectsResp{
	//	Username: user.FirstName,
	//	Orgs:     respOrgs,
	//	Projects: respProjects,
	//}

	return respOrgs, respProjects, nil

}

// Check resources before delete
func (s *ProjectSvc) DeleteProject(ctx context.Context, projectId uint64) error {
	tilog.Info(ctx, "DeleteProject param", zap.Any("projectId", projectId))

	projectEnt, err := dao.GetDaoHub().Project.GetProjectByID(ctx, database.DBConn.DB, projectId)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			projectEnt = nil
		} else {
			tilog.Error(ctx, "DeleteProject GetProjectByID error", zap.Uint64("projectId", projectId), zap.Any("err", err))
			return err
		}
	}
	tilog.Info(ctx, "DeleteProject GetProjectByID result", zap.Any("project_ent", projectEnt))
	if projectEnt == nil || projectEnt.Status != entity.ProjectStatusActive {
		tilog.Info(ctx, "DeleteProject fail: project not found or status is not active", zap.Uint64("projectId", projectId))
		return errs.NewBadRequest("project status is not active", nil)
	}
	if projectEnt.Type == entity.ProjectTypeTiDBXVirtual {
		tilog.Info(ctx, "DeleteProject fail: tidbx hidden project can not be delete", zap.Uint64("projectId", projectId))
		return errs.NewBadRequest("tidbx hidden project can not be deleted", nil)
	}
	if projectEnt.Type == entity.ProjectTypeTiDBX {
		var deleted = false
		clustersRsp, err := mgmt.GeClient().ListClusters(ctx, &mgmtv1.ListClustersRequest{
			Filter: &mgmtv1.ClusterFilter{
				ProjectIds: []uint64{projectEnt.ID.Uint64()},
				Deleted:    &deleted,
			},
			Page:     1,
			PageSize: 1,
		})
		if err != nil || clustersRsp == nil {
			tilog.Error(ctx, "DeleteProject call mgmt check clusters error", zap.Uint64("projectId", projectId), zap.Any("err", err))
			return err
		}
		if clustersRsp.Total > 0 {
			tilog.Info(ctx, "DeleteProject fail: exist non delete clusters in tidbx project", zap.Uint64("projectId", projectId))
			return errs.NewBadRequest("exist clusters", nil)
		}
	}
	if projectEnt.Type == entity.ProjectTypeDedicated {
		// check resources before delete (original logic)
		resourceResult, err := infra.CheckProjectResource(ctx, projectId)
		if err != nil {
			return err
		}
		if resourceResult.IsHaveResource {
			tilog.Info(ctx, "DeleteOrgByID return", zap.Any("msg", resourceResult.ResourcesMsg))
			return errors.New(resourceResult.ResourcesMsg)
		}
	}

	err = database.Transaction(func(tx *gorm.DB) error {
		err := dao.GetDaoHub().Project.MarkProjectAsDeletedByID(ctx, tx, projectEnt.ID.Uint64())
		if err != nil {
			tilog.Error(ctx, "DeleteProject DeleteProjectByID error", zap.Uint64("projectID", projectEnt.ID.Uint64()), zap.Error(err))
			return errcode.NewInternalErr(errors.New("failed to delete project"))
		}
		err = dao.GetDaoHub().ResourceRelation.DeleteResourceRelationsByParent(ctx, tx, entity.RoleBindingScopeProject.String(), projectEnt.ID.Uint64())
		if err != nil {
			tilog.Error(ctx, "DeleteProject DeleteResourceRelationsByParent error", zap.Uint64("projectID", projectEnt.ID.Uint64()), zap.Error(err))
			return errcode.NewInternalErr(errors.New("failed to delete project relate cluster relations"))
		}
		_, err = dao.GetDaoHub().Role.DeleteRoleBindingByScopeInfo(ctx, tx, entity.RoleBindingScopeProject, projectEnt.ID.Uint64(), "")
		if err != nil {
			tilog.Error(ctx, "DeleteProject DeleteRoleBindingByScopeInfo error", zap.Uint64("projectID", projectEnt.ID.Uint64()), zap.Error(err))
			return errcode.NewInternalErr(errors.New("failed to delete project relate role_bindings"))
		}
		tilog.Info(ctx, "DeleteProject success", zap.Any("projectId", projectId))
		return nil
	})

	return err
}
func (s *ProjectSvc) AdvancedListProjectsByPage(ctx context.Context, pageParams dto.PageParams) (*utils.Paginator, error) {
	//orgProjects, err := project.GetActiveProjects(ctx, s.db.WithContext(ctx), entity.ID(orgID))

	paginator, err := comdao.ListEntitiesByPage(ctx, database.DBConn.DB.WithContext(ctx), pageParams, &entity.Project{})
	//paginator, err := comdao.ListEntitiesByPage(ctx, params, &entity.User{})
	tilog.Info(ctx, "list project by page", zap.Any("paginator.Total", paginator.Total))
	if err != nil {
		tilog.Error(ctx, "failed to get active projects", zap.Error(err))
		return nil, errcode.NewInternalErr(errors.New("failed to get active projects"))
	}
	orgProjects := paginator.Data.([]entity.Project)

	var respProjects []Project
	for _, project := range orgProjects {
		p := Project{
			ID:             project.ID.Uint64(),
			OrgID:          project.TenantID.Uint64(),
			Name:           project.Name,
			CreatedAt:      project.CreatedAt,
			IsCrossAccount: project.IsCrossAccount,
			AwsCmekEnabled: project.AwsCmekEnabled,
			Type:           project.Type,
		}
		if !config.IsLocalEnv() {

			activeClustersCount, err := infra.GetHandler().CountClustersByProject(ctx, []uint64{project.ID.Uint64()})
			if err != nil {
				tilog.Error(ctx, "failed to count active clusters", zap.Uint64("projectID", project.ID.Uint64()), zap.Error(err))
				return nil, errcode.NewInternalErr(errors.New("failed to count active clusters"))
			}
			p.ActiveClustersCount = uint64(activeClustersCount)
		}
		filter := userDao.RoleBindingUserFilter{
			RoleBindingStatus: []string{string(entity.Active), entity.RoleBindingStatusInactive},
			UserStatus:        []string{string(entity.UserStatusActive), string(entity.UserStatusPending)},
			ScopeType:         entity.RoleBindingScopeProject,
			ScopeId:           project.ID.Uint64(),
		}
		users, err := dao.GetDaoHub().User.ListRoleBindingUsers(ctx, database.DBConn.DB.WithContext(ctx), filter)
		if err != nil {
			tilog.Error(ctx, "failed to count active clusters", zap.Uint64("projectID", project.ID.Uint64()), zap.Error(err))
			return nil, errcode.NewInternalErr(errors.New("failed to count active clusters"))
		}
		// ignore system user
		isEven := func(i int, user userDao.RoleBindingUser) bool {
			//if user.FirstName == systemFirstName && user.LastName == userLastName {
			return user.FirstName == entity.SystemUserFirstName && user.LastName == entity.SystemUserLastName
		}
		userCount := uint64(len(users))
		_, ok := slice.Find(users, isEven)
		if ok {
			userCount = userCount - 1
		}
		p.UserCount = userCount
		respProjects = append(respProjects, p)
	}
	paginator.Data = respProjects
	return paginator, nil
}

type OrgProjects struct {
	OrgID    uint64
	Projects []entity.Project
}
type Project struct {
	ID                  uint64
	OrgID               uint64
	Name                string
	Status              string
	UserCount           uint64
	ActiveClustersCount uint64
	IsCrossAccount      bool
	AwsCmekEnabled      bool
	CreatedAt           time.Time
	Type                string
}
