package user

import (
	"context"
	goerr "errors"
	"fmt"
	"strconv"
	"strings"
	"time"

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

	"github.com/duke-git/lancet/v2/slice"

	"github.com/tidbcloud/tiinf/errs"

	"github.com/tidbcloud/account/internel/svc/rbac"

	"github.com/pingcap/errcode"
	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/internel/dao/accesslog"
	daorole "github.com/tidbcloud/account/internel/dao/role"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

const singleOperateClusterCount = 50000

func UpsertUserInviteToken(tx *gorm.DB, userInviteToken entity.UserInviteToken) (entity.UserInviteToken, error) {
	var existingUserInviteToken entity.UserInviteToken
	err := entity.NewUserInviteTokenQuerySet(tx).MethodEq(
		userInviteToken.Method).EmailEq(userInviteToken.Email).One(&existingUserInviteToken)
	if err == nil {
		userInviteToken.ID = existingUserInviteToken.ID
		err = userInviteToken.Update(
			tx,
			entity.UserInviteTokenDBSchema.TenantID,
			entity.UserInviteTokenDBSchema.BeginAt,
			entity.UserInviteTokenDBSchema.ExpireAt,
			entity.UserInviteTokenDBSchema.IsOneTime,
		)
	} else if nil != errors.Find(err, func(e error) bool { return e == gorm.ErrRecordNotFound }) {
		err = userInviteToken.Create(tx)
	}
	return userInviteToken, errors.Annotate(err, "Upsert User Invite Token")
}

type InviteTokenFilter struct {
	TenantIDs []entity.ID
	Email     *string
}

func FindInviteTokens(tx *gorm.DB, filter *InviteTokenFilter) ([]entity.UserInviteToken, error) {
	var tokens []entity.UserInviteToken
	qs := entity.NewUserInviteTokenQuerySet(tx)

	if filter.Email != nil {
		qs = qs.EmailEq(*filter.Email)
	}
	if len(filter.TenantIDs) > 0 {
		qs = qs.TenantIDIn(filter.TenantIDs...)
	}

	err := qs.OrderDescByCreatedAt().All(&tokens)
	if err != nil {
		return nil, errors.AddStack(err)
	}

	return tokens, nil
}

// update org and user status after validate email
func UpdateRegistration(ctx context.Context, tx *gorm.DB, email string, orgID entity.ID) error {
	tenantEntity := entity.Tenant{}
	err := entity.NewTenantQuerySet(tx).IDEq(orgID).One(&tenantEntity)
	tilog.Info(ctx, "query new register user org ", zap.Any("org", tenantEntity))
	if err != nil {
		return errors.Annotate(dao.TagNotFound(err), "Update Registration")
	}

	userEntity := entity.User{}
	err = entity.NewUserQuerySet(tx).EmailEq(email).DeletedAtIsNull().One(&userEntity)
	if err != nil {
		return errors.Annotate(dao.TagNotFound(err), "Update Registration")
	}

	tenantEntity.Status = entity.TenantStatusActive
	userEntity.Status = entity.UserStatusActive
	err = tenantEntity.Update(tx, entity.TenantDBSchema.Status)

	if err != nil {
		return errors.Annotate(err, "Update Registration")
	}
	tilog.Info(ctx, "update  new register user's  org status=active  success ", zap.Any("orgId", tenantEntity.ID), zap.Any("orgName", tenantEntity.Name))
	err = userEntity.Update(tx, entity.UserDBSchema.Status)
	if err != nil {
		return errors.Annotate(err, "Update Registration")
	}
	tilog.Info(ctx, "update  new register user's   status=active  success ", zap.Any("user", userEntity.Email), zap.Any("userId", userEntity.ID))
	// update role bindings status
	//ctx := context.Background()
	var userOrgRoleBinding []entity.RoleBinding
	err = entity.NewRoleBindingQuerySet(tx).
		TargetTypeEq(entity.RoleBindingTargetUser).TargetIDEq(userEntity.ID).
		ScopeTypeEq(entity.RoleBindingScopeOrg).ScopeIDEq(tenantEntity.ID).All(&userOrgRoleBinding)
	if err != nil {
		tilog.Error(ctx, "UpdateRegistration ,ListOrgRoleBindings err", zap.Error(err))
		return errors.Annotate(err, "Update Registration")
	}
	if len(userOrgRoleBinding) > 0 && userOrgRoleBinding[0].Status != entity.RoleBindingStatusActive {
		tilog.Info(ctx, "UpdateRegistration, find userOrgRoleBinding", zap.Any("org_binding", userOrgRoleBinding))
		binding := userOrgRoleBinding[0]
		binding.Status = entity.RoleBindingStatusActive
		err = binding.Update(tx, entity.RoleBindingDBSchema.Status)
		if err != nil {
			tilog.Error(ctx, "UpdateRegistration ,UpdateRoleOrStatus err", zap.Error(err))
			return errors.Annotate(err, "Update Registration")
		}
	}
	var projects []entity.Project
	err = entity.NewProjectQuerySet(tx).TenantIDEq(tenantEntity.ID).StatusEq(entity.ProjectStatusActive).All(&projects)
	if err != nil {
		tilog.Error(ctx, "UpdateRegistration ,ListActiveProjectsByOrgID err", zap.Error(err))
		return errors.Annotate(err, "Update Registration")
	}
	for _, p := range projects {
		var roleBinding []entity.RoleBinding
		err = entity.NewRoleBindingQuerySet(tx).
			TargetTypeEq(entity.RoleBindingTargetUser).TargetIDEq(userEntity.ID).
			ScopeTypeEq(entity.RoleBindingScopeProject).ScopeIDEq(p.ID).All(&roleBinding)
		if err != nil {
			if goerr.Is(err, gorm.ErrRecordNotFound) {
				continue
			}
			tilog.Error(ctx, "UpdateRegistration ,ListProjectRoleBindings err", zap.Error(err))
			return errors.Annotate(err, "Update Registration")
		}
		if len(roleBinding) > 0 && roleBinding[0].Status != entity.RoleBindingStatusActive {
			tilog.Info(ctx, "UpdateRegistration, find userProjectRoleBinding", zap.Any("project_binding", roleBinding))
			binding := roleBinding[0]
			binding.Status = entity.RoleBindingStatusActive
			err = binding.Update(tx, entity.RoleBindingDBSchema.Status)
			if err != nil {
				tilog.Error(ctx, "UpdateRegistration ,UpdateRoleOrStatus err", zap.Error(err))
				return errors.Annotate(err, "Update Registration")
			}
		}
	}

	// send account data change event
	go func() {
		_ = comsvc.SendUserRoleBindingChangeEvent(context.Background(), userEntity.ID.Uint64(), tenantEntity.ID.Uint64(), "Registration Activate")
	}()

	tilog.Info(ctx, "update  new register user's   status=active  success ", zap.Any("user", userEntity.Email), zap.Any("userId", userEntity.ID))
	return nil
}

type UserFilter struct {
	Email     *string
	IDs       []entity.ID
	Statuses  []entity.UserStatus
	Roles     []entity.UserRole // Only available when ListUsersByOrg
	CreatedAt *time.Time
}

// ListUsers returns a list of organization users, default OrderDescByCreatedAt.
func ListUsers(tx *gorm.DB, filter UserFilter, limit int, offset int) ([]entity.User, error) {
	var users []entity.User
	qs := entity.NewUserQuerySet(tx.Preload("Roles"))

	if filter.Email != nil {
		qs = qs.EmailLike(fmt.Sprintf("%%%s%%", *filter.Email))
	}
	if len(filter.IDs) > 0 {
		qs = qs.IDIn(filter.IDs...)
	}
	if len(filter.Statuses) > 0 {
		qs = qs.StatusIn(filter.Statuses...)
	}
	if filter.CreatedAt != nil {
		qs = qs.CreatedAtGte(*filter.CreatedAt)
	}
	if limit != 0 {
		qs = qs.Limit(limit)
	}
	if offset != 0 {
		qs = qs.Offset(offset)
	}

	err := qs.OrderDescByCreatedAt().All(&users)
	if err != nil {
		return nil, errors.AddStack(err)
	}

	return users, nil
}

// Note: User role from this function is read from role_bindings table
func ListUsersByOrg(tx *gorm.DB, tenantID entity.ID, roleBindingStatus []string, filter UserFilter, limit int, offset int) ([]entity.User, error) {
	var (
		roles   []entity.RoleBinding
		userIDs []entity.ID
	)

	qs := tx.Where(
		"scope_type = ? AND scope_id = ? AND target_type = ?",
		entity.RoleBindingScopeOrg,
		tenantID,
		entity.RoleBindingTargetUser,
	)
	if len(roleBindingStatus) > 0 {
		qs = qs.Where("status IN ?", roleBindingStatus)
	}

	err := qs.Find(&roles).Error
	if err != nil {
		return nil, errors.AddStack(err)
	}

	userIDMap := make(map[entity.ID]bool)
	roleMap := make(map[entity.ID]entity.UserRole)
	for _, role := range roles {
		userID := role.TargetID
		if found, ok := userIDMap[userID]; !ok || !found {
			userIDMap[role.TargetID] = true
			userIDs = append(userIDs, userID)
			roleMap[role.TargetID] = entity.RoleBindingDecode(role.Role)
		}
	}
	if (userIDs) == nil {
		return []entity.User{}, nil
	}

	filter.IDs = userIDs
	users, err := ListUsers(tx, filter, limit, offset)
	if err != nil {
		return nil, errors.AddStack(err)
	}

	result := []entity.User{}
	for _, user := range users {
		// Use first role we found
		user.Role = roleMap[user.ID]

		// Filter by roles
		if len(filter.Roles) > 0 {
			found := false
			for _, role := range filter.Roles {
				if user.Role == role {
					found = true
					break
				}
			}
			if !found {
				continue
			}
		}

		result = append(result, user)
	}

	return result, nil
}

func GetUserByID(tx *gorm.DB, userID entity.ID) (*entity.User, error) {
	user := entity.User{}
	err := entity.NewUserQuerySet(tx.Preload("Roles")).IDEq(userID).One(&user)
	if err != nil {
		return nil, dao.TagNotFound(err)
	}

	return &user, err
}

func GetUserByEmail(tx *gorm.DB, email string) (entity.User, error) {
	user := entity.User{}
	err := entity.NewUserQuerySet(tx).EmailEq(email).One(&user)
	return user, dao.TagNotFound(err)
}

type CreateUserRequired struct {
	Email     string
	FirstName string
	LastName  string
	InviteAt  *time.Time
	Status    entity.UserStatus
	Role      entity.UserRole
	TenantID  entity.ID
	Projects  []entity.Project
}

func addUserToOrg(ctx context.Context, tx *gorm.DB, userID entity.ID, orgID entity.ID, role string) error {
	logger := tilog.WithContextL(ctx)

	// check if there is an existing relationship with org
	roleDao := daorole.NewDao()
	roleBindings, err := roleDao.ListRolesByTargetAndScope(
		ctx, tx, entity.RoleBindingTargetUser, userID.Uint64(), entity.RoleBindingScopeOrg, orgID.Uint64(), nil,
	)
	if err != nil && err != gorm.ErrRecordNotFound {
		return errors.Annotate(err, "failed to get roles of user")
	}
	logger.Info("current user-org relationship", zap.Any("roleBindings", roleBindings))

	// if we don't have an existing relationship, create one
	if len(roleBindings) == 0 {
		logger.Info("add org role for user", zap.Uint64("userID", userID.Uint64()), zap.Uint64("orgID", orgID.Uint64()), zap.String("role", role))
		_, err = rbac.AddOrgRoleForUser(ctx, tx, userID, orgID, role, entity.RoleBindingStatusInactive)
		if err != nil {
			return errors.Annotate(err, "add org role for user")
		}
	}

	return nil
}

// UpsertUser creates or update a new user.
// NOTES: Project roles of user will not be updated in this function.
func UpsertUser(ctx context.Context, tx *gorm.DB, input CreateUserRequired) (*entity.User, error) {
	logger := tilog.With(zap.Uint64("orgID", input.TenantID.Uint64()))
	daoHub := dao.NewDaoHub()

	user := entity.User{
		Email:     input.Email,
		FirstName: input.FirstName,
		LastName:  input.LastName,
		Status:    input.Status,
		InvitedAt: input.InviteAt,
		TenantID:  input.TenantID,
		Role:      input.Role,
	}

	var existingUser entity.User
	err := entity.NewUserQuerySet(tx).EmailEq(user.Email).DeletedAtIsNull().One(&existingUser)
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, err
	}
	logger.Info("existingUser", zap.Any("existingUser", existingUser))

	if err == gorm.ErrRecordNotFound {
		// user does not exists
		err = (&user).Create(tx)
		if err != nil {
			return nil, errors.Annotate(err, "create user record")
		}
	} else {
		user.ID = existingUser.ID
	}

	if err = addUserToOrg(ctx, tx, user.ID, input.TenantID, user.Role.Encode()); err != nil {
		return nil, err
	}

	err = tx.Model(&user).Association("Projects").Replace(input.Projects)
	if err != nil {
		return nil, errors.Annotate(err, "update user projects")
	}

	err = (&user).Update(tx,
		entity.UserDBSchema.FirstName,
		entity.UserDBSchema.LastName,
		entity.UserDBSchema.Status,
		entity.UserDBSchema.InvitedAt,
		entity.UserDBSchema.TenantID,
		entity.UserDBSchema.Role,
	)
	if err != nil {
		return nil, errors.Annotate(err, "update user record")
	}

	binding, exists, err := daoHub.Role.LegacyGetUserRoleByOrg(ctx, tx, user.ID.Uint64(), input.TenantID.Uint64(), nil)
	if err != nil {
		return nil, errors.Annotate(err, "update user, failed to get current user role")
	}
	if !exists {
		return nil, errors.New("user should have relationship with org but it does not")
	}

	_, err = rbac.ChangeRoleTo(ctx, tx,
		entity.RoleBindingTargetUser, user.ID,
		entity.RoleBindingScopeOrg, input.TenantID,
		binding.Role, user.Role.Encode(),
	)
	logger.Info("update user role", zap.Uint64("userID", user.ID.Uint64()), zap.String("fromRole", binding.Role), zap.String("toRole", user.Role.Encode()), zap.Error(err))
	if err != nil {
		return nil, errors.Annotate(err, "change role to")
	}

	return &user, nil
}

type ProjectUserFilter struct {
	ProjectID entity.ID
	Email     *string
	Statuses  []entity.UserStatus
}

func UserStatusContains(statuses []entity.UserStatus, status entity.UserStatus) bool {
	for _, s := range statuses {
		if s == status {
			return true
		}
	}
	return false
}

func ListOrgOwnerUsers(ctx context.Context, tx *gorm.DB, orgID entity.ID, email string, userStatus []entity.UserStatus) ([]entity.User, error) {
	daoHub := dao.NewDaoHub()
	bindings, err := daoHub.Role.ListRoleBindings(ctx, tx, daorole.RoleBindingFilter{
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
		ScopeIDs:   []entity.ID{orgID},
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		Roles:      []string{entity.RoleOrgOwner},
		Status:     []string{entity.RoleBindingStatusActive, entity.RoleBindingStatusInactive},
	})
	if err != nil {
		return nil, err
	}
	if len(bindings) == 0 {
		return []entity.User{}, nil
	}
	var userIDs []uint64
	for _, binding := range bindings {
		userIDs = append(userIDs, binding.TargetID.Uint64())
	}
	users, err := daoHub.User.ListUsers(ctx, tx, userIDs, userStatus)
	if err != nil {
		return nil, err
	}
	if email == "" {
		return users, nil
	}
	var retUsers []entity.User
	for _, user := range users {
		if user.Email == email {
			retUsers = append(retUsers, user)
		}
	}
	return retUsers, nil
}

// ListProjectUsers returns a list of project users
func ListProjectUsers(ctx context.Context, tx *gorm.DB, filter ProjectUserFilter, roleStatus []string) ([]entity.User, error) {
	daoHub := dao.NewDaoHub()
	logger := tilog.WithContextL(ctx)

	//var project entity.Project
	// TODO(iven): Remove `Preload("Users")` after project.Users is completely unused
	//err := entity.NewProjectQuerySet(tx.Preload("Users")).IDEq(filter.ProjectID).StatusEq(entity.ProjectStatusActive).One(&project)
	//if err != nil {
	//	return nil, errors.Annotatef(err, "failed to get project %d", filter.ProjectID.Uint64())
	//}

	projectUsers, err := daoHub.Cross.ListUsersByProjectID(ctx, tx, filter.ProjectID.Uint64(), roleStatus)
	userIDList := make([]uint64, len(projectUsers))
	for i, user := range projectUsers {
		userIDList[i] = user.ID.Uint64()
	}
	logger.Info("ListUsersByProjectID", zap.Uint64("projectID", filter.ProjectID.Uint64()), zap.Uint64s("users", userIDList), zap.Error(err))
	if err != nil {
		return nil, errors.Annotatef(err, "failed to get users by project id %d", filter.ProjectID.Uint64())
	}

	// TODO(Louis): filter by db query
	var users []entity.User
	for _, user := range projectUsers {
		if filter.Email != nil && !strings.Contains(user.Email, *filter.Email) {
			continue
		}
		if len(filter.Statuses) > 0 && !UserStatusContains(filter.Statuses, user.Status) {
			continue
		}
		users = append(users, user)
	}

	return users, nil
}

func IsImpersonatedUserInOrg(ctx context.Context, tx *gorm.DB, userID entity.ID, orgID entity.ID) (*entity.ImpersonatedUser, bool, error) {
	var user entity.ImpersonatedUser

	err := tx.Where(
		`user_id = ? AND tenant_id = ?`,
		userID, orgID,
	).First(&user).Error

	if err != nil {
		e := errors.Annotate(err, "failed to get impersonated user")
		return nil, false, e
	}

	result := user.UserID == userID && user.TenantID == orgID
	return &user, result, nil
}

// RemoveUserFromOrg removes a user from org, without deleting his/her account
// HACK(iven): To be compatible with the old behavior, user account will be removed when user belongs to no org
func DeleteUserFromOrg(ctx context.Context, OrgId, userId, operatorId uint64) (string, bool, error) {
	var (
		user             entity.User
		userEmail        string
		shouldDeleteUser bool
		userOrgRole      entity.RoleBinding
	)

	bm := utils.Begin(ctx, " RemoveUserFromOrg in cloud", zap.Any("userId", userId))
	defer bm.OnExit(ctx)
	daoHub := dao.NewDaoHub()
	//operator, err := GetUserFromContext(ctx)
	//if err != nil {
	//	return userEmail, false, err
	//}
	err := database.Transaction(func(tx *gorm.DB) error {
		_, userInOrg, err := daoHub.Role.LegacyGetUserRoleByOrg(ctx, tx, userId, OrgId, nil)
		if err != nil {
			return errcode.NewInternalErr(errors.New("failed to check if user is in org"))
		}
		if !userInOrg {
			//return errcode.NewForbiddenErr(errors.New("user not in org"))
			return errs.NewBadRequest("user not in org", nil)
		}
		tilog.Info(ctx, "check if user is in org", zap.Any("userInOrg", userInOrg))
		err = entity.NewUserQuerySet(tx).IDEq(entity.ID(userId)).StatusNotIn(entity.UserStatusInactive).One(&user)
		if err != nil {
			if err == gorm.ErrRecordNotFound {
				//return errcode.NewNotFoundErr(errors.New("user not found"))
				return errs.NewBadRequest("user not found", nil)
			}
			return errcode.NewInternalErr(errors.New("failed to get user from db"))
		}
		if user.Email == "" {
			tilog.Info(ctx, "the user is inactive ", zap.Any("userId", userId))
			return nil
		}
		//if operator.Email == user.Email {
		//	return errcode.NewInvalidInputErr(errors.New("user cannot delete himself"))
		//}

		userEmail = user.Email

		// Remove invite token if exists
		tokens, err := FindInviteTokens(tx, &InviteTokenFilter{Email: &userEmail, TenantIDs: []entity.ID{entity.ID(OrgId)}})
		if err != nil {
			return err
		}
		if len(tokens) != 0 {
			for _, token := range tokens {
				err := token.Delete(tx)
				tilog.Info(ctx, "delete invite token", zap.Any("token", token.ID))
				if err != nil {
					return err
				}
			}
		}

		// Remove org roles
		orgRoles, err := daoHub.Role.ListUserRolesByUserIDAndOrgs(ctx, tx, user.ID.Uint64(), []uint64{OrgId}, nil)
		if err != nil {
			return err
		}

		for _, roleBinding := range orgRoles {
			if roleBinding.ScopeType == entity.RoleBindingScopeOrg {
				userOrgRole = roleBinding // we can do this as we will not set multiple roleBinding binding in a given tenant?
			}

			_, err = rbac.RemoveOrgRoleForUser(ctx, tx, user.ID, roleBinding.ScopeID, roleBinding.Role)
			if err != nil && err != gorm.ErrRecordNotFound {
				return err
			}
			tilog.Info(ctx, "remove user from org", zap.Any("org_id", roleBinding.ScopeID), zap.Any("Role", roleBinding.Role))
		}

		// get all project ids in current org
		projects, err := daoHub.Project.ListProjects(ctx, tx, []uint64{OrgId}, nil, nil)
		if err != nil {
			return err
		}
		if len(projects) > 0 {
			// We have 2 case in here:
			// 1. If the user is owner, or was org owner before, he/she has all the relationship with projects in the org
			// 2. If the user is a poor member in org, he/she will only have a few relationship with projects in the org
			// So, we can just simply retrieve all the projects in the org, and delete it.

			// Remove project roles
			// include inactive roleBinding （add by wangletian)
			projectRoles, err := rbac.ListUserRolesByProjects(ctx, tx, user.ID, entity.GetEntityIDsFromProjects(projects))
			tilog.Info(ctx, "ListUserRolesByProjects Projects "+fmt.Sprintf("Size=%d", len(projectRoles)))
			if err != nil {
				return err
			}

			for _, role := range projectRoles {
				_, err = rbac.RemoveProjectRoleForUser(ctx, tx, user.ID, role.ScopeID, role.Role) //nolint
				if err != nil && err != gorm.ErrRecordNotFound {
					return err
				}
				tilog.Info(ctx, "remove user from project", zap.Any("project_id", role.ScopeID), zap.Any("Role", role.Role))
			}
			// get user-projects relationship after delete, and sync it to user_projects
			// TODO: the code in below should be removed after we stop using user_projects table
			err = tx.Model(&user).Association("Projects").Delete(projects)
			if err != nil {
				tilog.Error(ctx, "failed to delete user_projects relationship", zap.Error(err))
				return err
			}
			// end
		}
		// delete cluster role binding
		allClusterRoleBindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, daorole.RoleBindingFilter{
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeTiDB},
			TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
			TargetIDs:  []entity.ID{user.ID},
		})
		if err != nil {
			tilog.Error(ctx, "failed to list user cluster role bindings", zap.Error(err))
			return err
		}
		hasRoleAllClusterIDs := make([]uint64, 0, len(allClusterRoleBindings))
		for _, rb := range allClusterRoleBindings {
			scopeID := rb.ScopeID.Uint64()
			hasRoleAllClusterIDs = append(hasRoleAllClusterIDs, scopeID)
		}
		cidsInO, err := daoHub.ResourceRelation.GetResourceIDsByParentAndResourceIDs(
			ctx, tx, entity.RoleBindingScopeOrg.String(), OrgId, entity.RoleBindingScopeTiDB.String(), hasRoleAllClusterIDs)
		if err != nil {
			tilog.Error(ctx, "get org cluster relations error", zap.Any("org_id", OrgId))
			return errcode.NewInternalErr(errors.New("failed to get cluster relations"))
		}
		tilog.Info(ctx, fmt.Sprintf("find org cluster count: %d", len(cidsInO)))
		chunkCids := slice.Chunk(cidsInO, singleOperateClusterCount)
		for _, cids := range chunkCids {
			_, err = daoHub.Role.DeleteRoleByTargetAndScope(ctx, tx,
				entity.RoleBindingTargetUser, userId, entity.RoleBindingScopeTiDB, cids)
			if err != nil {
				tilog.Error(ctx, "failed to delete user cluster role binding", zap.Any("user_id", userId), zap.Any("cluster_ids", cids), zap.Error(err))
				return err
			}
		}

		allOrgRoles, err := daoHub.Role.ListUserRolesByUserIDAndOrgs(ctx, tx, user.ID.Uint64(), nil, nil)
		if err != nil {
			return err
		}
		if len(allOrgRoles) == 0 {

			shouldDeleteUser = true
		}

		if shouldDeleteUser {
			tilog.Info(ctx, "user is not in any org, delete user")
			now := time.Now()
			user.Email = fmt.Sprintf("deleted-%d", user.ID) //nolint
			user.DeletedAt = &now                           //nolint
			user.Status = entity.UserStatusInactive
			err = user.Update(tx, entity.UserDBSchema.Email, entity.UserDBSchema.DeletedAt, entity.UserDBSchema.Status)
			if err != nil {

				tilog.Error(ctx, "failed to delete user in db", zap.Error(err))
				return errcode.NewInternalErr(errors.New("failed to delete user from db"))
			}
		}
		tilog.Info(ctx, "DeleteUsersFromOrg success "+fmt.Sprintf("delete user:  %s", userEmail), zap.Any("OrgId", OrgId))

		return accesslog.AddAccessLogNow(tx, entity.ID(OrgId), strconv.FormatUint(operatorId, 10), fmt.Sprintf("delete user:  %s", userEmail))
	})

	if userOrgRole.Status == "" {
		tilog.Info(ctx, "failed to capture user role status in org", zap.Uint64("userID", userId), zap.Uint64("orgID", OrgId))

	}

	if err != nil {
		return userEmail, shouldDeleteUser, err
	}
	tilog.Info(ctx, "DeleteUserFromOrg success", zap.Uint64("userID", userId), zap.Uint64("orgID", OrgId))

	return userEmail, shouldDeleteUser, nil
}

func CheckTargetHasClusterRoles(ctx context.Context, targetType entity.TargetType, targetId uint64, clusterIds []uint64, roles []string) (map[uint64]bool, error) {
	bindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, daorole.RoleBindingFilter{
		TargetType: []entity.TargetType{targetType},
		TargetIDs:  []entity.ID{entity.ID(targetId)},
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeTiDB},
		ScopeIDs:   utils.UintToIDList(clusterIds),
		Roles:      roles,
		Status:     []string{entity.RoleBindingStatusActive},
	})
	if err != nil {
		tilog.Error(ctx, "CheckTargetHasClusterRoles list role binding error", zap.Any("err", err))
		return nil, err
	}
	var hasRoleClusterMap = map[uint64]struct{}{}
	for _, b := range bindings {
		hasRoleClusterMap[b.ScopeID.Uint64()] = struct{}{}
	}
	var ret = map[uint64]bool{}
	for _, cid := range clusterIds {
		if _, ok := hasRoleClusterMap[cid]; ok {
			ret[cid] = true
		} else {
			ret[cid] = false
		}
	}
	return ret, nil
}
