package invite

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

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

	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/internel/dao/accesslog"
	"github.com/tidbcloud/account/internel/dao/role"
	"github.com/tidbcloud/account/internel/svc/dbuser"

	"github.com/google/uuid"
	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/account/commons/config"
	accountpb "github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/account/internel/dao"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/thirdparty/account"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type InviteUserSvcIface interface {
	InviteUser(ctx context.Context, inviteUserParam *InviteUserParam) (*InviteUserRet, error)
}

var (
	inviteUserSvcInitOnce sync.Once
	inviteUserSvcInstance InviteUserSvcIface
)

func GetInviteUserSvc() (InviteUserSvcIface, error) {
	inviteUserSvcInitOnce.Do(func() {
		inviteUserSvcInstance = NewInviteUserSvc()

	})
	return inviteUserSvcInstance, nil
}

type InviteUserSvc struct {
}

func NewInviteUserSvc() InviteUserSvcIface {
	return &InviteUserSvc{}
}

type InviteUserParam struct {
	EmailList       []string
	OrgID           uint64
	ProjectID       uint64
	Role            string
	OperatorID      uint64
	OperatorEmail   string
	OperatorType    entity.TargetType
	WithoutEmail    bool
	WithoutAuditLog bool
}

type InviteUserRet struct {
	FailEmailList []InviteOneUserRet
}

type InviteOneUserParam struct {
	Email           string
	Org             *entity.Tenant
	Project         *entity.Project
	Role            string
	OperatorID      uint64
	OperatorEmail   string
	OperatorType    entity.TargetType
	WithoutEmail    bool
	WithoutAuditLog bool
}

type InviteOneUserRet struct {
	Email string
	Code  int
	Msg   string
}

func (s *InviteUserSvc) InviteUser(ctx context.Context, inviteUserParam *InviteUserParam) (*InviteUserRet, error) {
	tilog.Info(ctx, "InviteUser start", zap.Any("param", inviteUserParam))

	// check org valid
	org, err := dao.GetDaoHub().Org.GetOrgByOrgID(ctx, database.DBConn.DB, inviteUserParam.OrgID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			tilog.Info(ctx, "InviteUser org not found")
			return nil, errs.NewNotFound("org not found", nil)
		}
		tilog.Error(ctx, "InviteUser GetOrgByOrgID error", zap.Any("err", err))
		return nil, err
	}
	if org.Status != entity.TenantStatusActive {
		tilog.Info(ctx, "InviteUser org status not active")
		return nil, errs.NewBadRequest("org status not active", nil)
	}

	// check project valid
	var project *entity.Project = nil
	if inviteUserParam.ProjectID != 0 {
		project, err = dao.GetDaoHub().Project.GetProjectByID(ctx, database.DBConn.DB, inviteUserParam.ProjectID)
		if err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				tilog.Info(ctx, "InviteUser project not found")
				return nil, errs.NewNotFound("project not found", nil)
			}
			tilog.Error(ctx, "InviteUser GetProjectByID error", zap.Any("err", err))
			return nil, err
		}
		if project.Status != entity.ProjectStatusActive {
			tilog.Info(ctx, "InviteUser project status not active")
			return nil, errs.NewBadRequest("project status not active", nil)
		}
	}

	eventType := auditlog.EventInviteUserToOrg
	var projectID uint64 = 0
	if project != nil {
		eventType = auditlog.EventInviteUserToProject
		projectID = project.ID.Uint64()
	}

	var failEmailList []InviteOneUserRet

	for _, email := range inviteUserParam.EmailList {
		inviteOneUserParam := InviteOneUserParam{
			Email:           email,
			Org:             org,
			Project:         project,
			Role:            inviteUserParam.Role,
			OperatorID:      inviteUserParam.OperatorID,
			OperatorEmail:   inviteUserParam.OperatorEmail,
			OperatorType:    inviteUserParam.OperatorType,
			WithoutEmail:    inviteUserParam.WithoutEmail,
			WithoutAuditLog: inviteUserParam.WithoutAuditLog,
		}
		inviteRet, err := s.InviteOneUser(ctx, &inviteOneUserParam)
		if err != nil {
			ret := InviteOneUserRet{
				Email: email,
				Code:  entity.InviteUserInternalErr,
				Msg:   err.Error(),
			}
			failEmailList = append(failEmailList, ret)
		} else if inviteRet.Code != entity.InviteUserSuccess {
			ret := InviteOneUserRet{
				Email: email,
				Code:  inviteRet.Code,
				Msg:   inviteRet.Msg,
			}
			failEmailList = append(failEmailList, ret)
		}

		if !inviteOneUserParam.WithoutAuditLog {
			details := auditlog.Details{
				auditlog.KeyUserEmail: inviteOneUserParam.Email,
				auditlog.KeyRole:      inviteOneUserParam.Role,
				auditlog.KeyError:     err,
			}
			err = auditlog.NewEvent(ctx, eventType).SetDetails(details.Build()).SetResult(err == nil).SetOrgID(org.ID.Uint64()).
				SetProjectID(projectID).Submit()
			if err != nil {
				tilog.Warn(ctx, "InviteUser add audit log error", zap.Error(err))
			}
		}
	}

	inviteUserRet := InviteUserRet{
		FailEmailList: failEmailList,
	}

	return &inviteUserRet, nil
}

func (s *InviteUserSvc) InviteOneUser(ctx context.Context, inviteUserParam *InviteOneUserParam) (*InviteOneUserRet, error) {
	tilog.Info(ctx, "InviteOneUser start", zap.Any("email", inviteUserParam.Email))
	project := inviteUserParam.Project
	org := inviteUserParam.Org

	// get db user
	dbUser, err := dao.GetDaoHub().User.GetUserByEmail(ctx, database.DBConn.DB, inviteUserParam.Email)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			dbUser = nil
		} else {
			tilog.Error(ctx, "InviteOneUser GetUserByEmail error", zap.Any("err", err))
			return nil, errors.New("failed to get user")
		}
	}

	// check user already in resource
	alreadyIn, err := s.checkUserInResource(ctx, dbUser, org, project)
	if err != nil {
		tilog.Error(ctx, "InviteOneUser checkUserAlreadyInResource error", zap.Any("err", err))
		return nil, err
	}
	if alreadyIn {
		tilog.Info(ctx, "InviteOneUser user already in resource, ignore", zap.Any("inviteUserParam", inviteUserParam))
		return &InviteOneUserRet{
			Email: inviteUserParam.Email,
			Code:  entity.InviteUserRepeated,
			Msg:   "repeated invite",
		}, nil
	}

	// save user to db
	retUser, err := s.saveInviteUser(ctx, dbUser, inviteUserParam.Email, org.ID)
	tilog.Info(ctx, "InviteOneUser saveInviteUser result", zap.Any("retUser", retUser), zap.Any("err", err))
	if err != nil {
		tilog.Error(ctx, "InviteOneUser saveInviteUser error", zap.Any("err", err))
		return nil, err
	}

	// select db org role_binding
	dbOrgRoleBinding, err := s.getUserOrgRoleBinding(ctx, retUser.ID.Uint64(), org.ID.Uint64())
	if err != nil {
		tilog.Error(ctx, "InviteOneUser getUserExistedOrgRoleBinding error", zap.Any("err", err))
		return nil, err
	}
	// add user to resource
	if project == nil {
		// just invite user to org
		err := s.inviteUserToOrg(ctx, retUser, org, inviteUserParam, dbOrgRoleBinding)
		if err != nil {
			tilog.Error(ctx, "InviteOneUser inviteUserToOrg error", zap.Any("err", err))
			return nil, err
		}
		tilog.Info(ctx, "InviteOneUser add user to org success")
	} else {
		// invite user to org and project
		err := s.inviteUserToProject(ctx, retUser, org, project, inviteUserParam, dbOrgRoleBinding)
		if err != nil {
			tilog.Error(ctx, "InviteOneUser inviteUserToProject error", zap.Any("err", err))
			return nil, err
		}
		tilog.Info(ctx, "InviteOneUser add user to org and project success")
	}

	var userAlreadyInOrg = false
	if dbOrgRoleBinding != nil && dbOrgRoleBinding.Status == entity.RoleBindingStatusActive {
		userAlreadyInOrg = true
	}
	// create token and send email
	if !userAlreadyInOrg && !inviteUserParam.WithoutEmail {
		sessionSvc, _ := token.GetSession()
		inviteToken, err := sessionSvc.InsertUserInviteToken(database.DBConn.DB, entity.ID(inviteUserParam.OperatorID), org.ID, token.InvitationVerificationMethod, inviteUserParam.Email, entity.InviteTokenValidityPeriod, true)
		tilog.Info(ctx, "InviteOneUser this user need to send invite email ,to create user invite token", zap.Any("token", inviteToken))
		if err != nil {
			tilog.Error(ctx, "InviteOneUser failed to create user invite token", zap.String("feature", "invitation"), zap.String("email", inviteUserParam.Email), zap.Error(err))
			return nil, errors.New("failed to create user invite token")
		}
		if org.OrgTag == string(entity.OrgTagMsp) || org.OrgTag == string(entity.OrgTagAwsCppo) || org.OrgTag == string(entity.OrgTagGcpMCPOReseller) || org.OrgTag == string(entity.OrgTagResalePartner) || org.OrgTag == string(entity.OrgTagAlicloud) {
			err = sendPartnerInviteEmail(ctx, inviteUserParam.OperatorEmail, inviteUserParam.Email, inviteToken, org.Name, org.ID.Uint64())
		} else {
			err = sendInviteEmail(ctx, inviteUserParam.OperatorEmail, inviteUserParam.Email, inviteToken, org.Name, org.ID.Uint64())
		}
		if err != nil {
			tilog.Error(ctx, "InviteOneUser sendInviteEmail error", zap.Error(err))
			return nil, err
		}
		err = accesslog.AddAccessLogNow(database.DBConn.DB, org.ID, inviteUserParam.OperatorEmail, fmt.Sprintf("invite user: %s", inviteUserParam.Email))
		if err != nil {
			tilog.Error(ctx, "InviteOneUser AddAccessLogNow error", zap.Error(err))
		}
	} else {
		tilog.Info(ctx, "InviteOneUser this user already in active org or not send email", zap.Any("email", inviteUserParam.Email))
	}

	return &InviteOneUserRet{
		Email: inviteUserParam.Email,
		Code:  entity.InviteUserSuccess,
		Msg:   "invite success",
	}, nil
}

func (s *InviteUserSvc) checkUserInResource(ctx context.Context, dbUser *entity.User, org *entity.Tenant, project *entity.Project) (bool, error) {
	if dbUser == nil {
		return false, nil
	}
	var filter role.RoleBindingFilter
	if project != nil {
		filter = role.RoleBindingFilter{
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeProject},
			ScopeIDs:   []entity.ID{project.ID},
			TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
			TargetIDs:  []entity.ID{dbUser.ID},
			Status:     []string{entity.RoleBindingStatusActive},
		}

	} else {
		filter = role.RoleBindingFilter{
			ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
			ScopeIDs:   []entity.ID{org.ID},
			TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
			TargetIDs:  []entity.ID{dbUser.ID},
			Status:     []string{entity.RoleBindingStatusActive},
		}

	}
	bindings, err := dao.GetDaoHub().Role.ListRoleBindings(ctx, database.DBConn.DB, filter)
	if err != nil {
		return false, err
	}
	if len(bindings) > 0 {
		return true, nil
	}
	return false, nil
}

func (s *InviteUserSvc) inviteUserToOrg(ctx context.Context, retUser *entity.User, org *entity.Tenant, inviteUserParam *InviteOneUserParam, dbOrgRoleBinding *entity.RoleBinding) error {
	assignOrgRole := inviteUserParam.Role
	if dbOrgRoleBinding == nil {
		_, err := dao.GetDaoHub().Role.CreateRole(ctx, database.DBConn.DB, entity.RoleBindingTargetUser, retUser.ID.Uint64(), entity.RoleBindingScopeOrg, org.ID.Uint64(), assignOrgRole, entity.RoleBindingStatusInactive)
		if err != nil {
			tilog.Error(ctx, "InviteOneUser create org role error", zap.Any("err", err))
			return errors.New("failed to insert user org relationship")
		}
	} else {
		// if exist, just update the existed role
		_, err := dao.GetDaoHub().Role.ChangeRoleTo(ctx, database.DBConn.DB, entity.RoleBindingTargetUser, retUser.ID.Uint64(), entity.RoleBindingScopeOrg, org.ID.Uint64(), dbOrgRoleBinding.Role, assignOrgRole)
		if err != nil {
			tilog.Error(ctx, "InviteOneUser ChangeRoleTo error", zap.Any("err", err))
			return errors.New("failed to change user org relationship")
		}

		// db account
		if dbOrgRoleBinding.Status == entity.RoleBindingStatusActive {
			var action = entity.CommandActionDelete
			if assignOrgRole == entity.RoleOrgOwner {
				action = entity.CommandActionCreate
			}
			operatorType := entity.RoleBindingTargetUser
			if inviteUserParam.OperatorType != "" {
				operatorType = inviteUserParam.OperatorType
			}
			err := dao.GetDaoHub().DBUser.CreateCommandUserAction(ctx, database.DBConn.DB, inviteUserParam.OperatorID, retUser.ID.Uint64(), inviteUserParam.Email, org.ID.Uint64(), 0, entity.CommandStatusWaiting, action, entity.RoleBindingTargetUser, operatorType)
			if err != nil {
				tilog.Error(ctx, "InviteOneUser invite user to org CreateCommandUserAction error", zap.Any("user", inviteUserParam.Email), zap.Any("orgID", org.ID.Uint64()), zap.Any("assignOrgRole", assignOrgRole), zap.Any("err", err))
			}
		}
	}
	return nil
}

func (s *InviteUserSvc) inviteUserToProject(ctx context.Context, retUser *entity.User, org *entity.Tenant, project *entity.Project, inviteUserParam *InviteOneUserParam, dbOrgRoleBinding *entity.RoleBinding) error {
	// just create org role when not exist org role
	if dbOrgRoleBinding == nil {
		_, err := dao.GetDaoHub().Role.CreateRole(ctx, database.DBConn.DB, entity.RoleBindingTargetUser, retUser.ID.Uint64(), entity.RoleBindingScopeOrg, org.ID.Uint64(), entity.RoleOrgMember, entity.RoleBindingStatusInactive)
		if err != nil {
			tilog.Error(ctx, "InviteOneUser crate org role_binding error", zap.Any("err", err))
			return errors.New("failed to insert user org relationship")
		}
	}

	var projectRoleBindingStatus = entity.RoleBindingStatusInactive
	if dbOrgRoleBinding != nil && dbOrgRoleBinding.Status == entity.RoleBindingStatusActive {
		projectRoleBindingStatus = entity.RoleBindingStatusActive
	}
	assignProjectRole := inviteUserParam.Role
	dbProjectRoleBindings, err := dao.GetDaoHub().Role.ListRolesByTargetAndScope(ctx, database.DBConn.DB, entity.RoleBindingTargetUser, retUser.ID.Uint64(), entity.RoleBindingScopeProject, project.ID.Uint64(), []string{})
	var dbProjectRoleBinding *entity.RoleBinding = nil
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			dbProjectRoleBinding = nil
		} else {
			tilog.Error(ctx, "InviteOneUser inviteUserToProject ListRolesByTargetAndScope error", zap.Any("user_id", retUser.ID.Uint64()), zap.Any("project_id", project.ID), zap.Any("err", err))
			return errors.New("failed to get user project relationship")
		}
	}
	if len(dbProjectRoleBindings) > 0 {
		// user just have one role binding in scope
		dbProjectRoleBinding = &dbProjectRoleBindings[0]
	}
	if dbProjectRoleBinding == nil {
		// frontend send multi request at same time, no distribute lock to limit, use upsert
		_, err = dao.GetDaoHub().Role.UpsertRole(ctx, database.DBConn.DB,
			entity.RoleBindingTargetUser, retUser.ID.Uint64(), entity.RoleBindingScopeProject, project.ID.Uint64(), assignProjectRole, projectRoleBindingStatus)
		if err != nil {
			tilog.Error(ctx, "InviteOneUser create project role error", zap.Any("err", err))
			return errors.New("failed to insert user project relationship")
		}
	} else {
		_, err = dao.GetDaoHub().Role.ChangeRoleTo(ctx, database.DBConn.DB, entity.RoleBindingTargetUser, retUser.ID.Uint64(), entity.RoleBindingScopeProject, project.ID.Uint64(), dbProjectRoleBinding.Role, assignProjectRole)
		if err != nil {
			tilog.Error(ctx, "InviteOneUser ChangeRoleTo error", zap.Any("err", err))
			return errors.New("failed to change user project relationship")
		}
		if dbProjectRoleBinding.Status != projectRoleBindingStatus {
			err = dao.GetDaoHub().Role.UpdateRoleStatusByID(ctx, database.DBConn.DB, dbProjectRoleBinding.ID.Uint64(), projectRoleBindingStatus)
			return err
		}
	}

	// db account
	if projectRoleBindingStatus == entity.RoleBindingStatusActive {
		dbUserSvc, _ := dbuser.GetDBUserSvc()
		isIgnore, err := dbUserSvc.IsIgnoreProjectDbUser(ctx, retUser.ID.Uint64(), project.ID.Uint64(), org.ID.Uint64())
		// if err, just to create
		if err != nil || !isIgnore {
			operatorType := entity.RoleBindingTargetUser
			if inviteUserParam.OperatorType != "" {
				operatorType = inviteUserParam.OperatorType
			}
			err = dao.GetDaoHub().DBUser.CreateCommandUserAction(ctx, database.DBConn.DB, inviteUserParam.OperatorID, retUser.ID.Uint64(), inviteUserParam.Email, org.ID.Uint64(), project.ID.Uint64(), entity.CommandStatusWaiting, entity.CommandActionDelete, entity.RoleBindingTargetUser, operatorType)
			if err != nil {
				tilog.Error(ctx, "InviteOneUser invite user to project CreateCommandUserAction error", zap.Any("user", inviteUserParam.Email), zap.Any("projectID", project.ID.Uint64()), zap.Any("err", err))
			}
			err = dao.GetDaoHub().DBUser.CreateCommandUserAction(ctx, database.DBConn.DB, inviteUserParam.OperatorID, retUser.ID.Uint64(), inviteUserParam.Email, org.ID.Uint64(), project.ID.Uint64(), entity.CommandStatusWaiting, entity.CommandActionCreate, entity.RoleBindingTargetUser, operatorType)
			if err != nil {
				tilog.Error(ctx, "InviteOneUser invite user to project CreateCommandUserAction error", zap.Any("user", inviteUserParam.Email), zap.Any("projectID", project.ID.Uint64()), zap.Any("err", err))
			}
		}
	}

	return nil
}

func (s *InviteUserSvc) getUserOrgRoleBinding(ctx context.Context, userID, orgID uint64) (*entity.RoleBinding, error) {
	dbOrgRoleBindings, err := dao.GetDaoHub().Role.ListUserRolesByUserIDAndOrg(ctx, database.DBConn.DB, userID, orgID, nil)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			dbOrgRoleBindings = []entity.RoleBinding{}
		} else {
			tilog.Error(ctx, "InviteOneUser ListUserRolesByUserIDAndOrg error", zap.Any("err", err))
			return nil, errors.New("failed to get user org relationship")
		}
	}
	var dbOrgRoleBinding *entity.RoleBinding
	if len(dbOrgRoleBindings) > 0 {
		// user just have one org_role in a org
		dbOrgRoleBinding = &dbOrgRoleBindings[0]
	}
	return dbOrgRoleBinding, nil
}

func sendPartnerInviteEmail(ctx context.Context, inviterEmail, email, token, orgName string, orgID uint64) error {
	logger := tilog.WithContextL(ctx)

	url := fmt.Sprintf("https://%s/invitation-verification?token=%s", utils.GetCentralBackendDns(), token)
	subject := fmt.Sprintf("Invitation to partner organization of TiDB Cloud: %s", orgName)
	tilog.Info(ctx, "sendPartnerInviteEmail in invite user, subject:"+subject)
	escapeOrgName := utils.EscapeStr(ctx, orgName)

	vars := map[string]string{
		"organizationName": escapeOrgName,
		"orgNameInSubject": orgName,
		"inviterName":      inviterEmail,
		"url":              url,
	}
	_, err := account.GetHandler().SendEmail(ctx, &accountpb.SendEmailReq{
		OrgId:      orgID,
		Scene:      string(entity.NotificationSceneInvite),
		Identifier: uuid.New().String(),
		Subject:    subject,
		Emails:     []string{email},
		CcEmails:   nil,
		TemplateId: config.SendgridTemplateName2id[config.TNPartnerInviteUser],

		TemplateVars: vars,
	})
	if err != nil {
		logger.Error("failed to send partner invitation email", zap.String("feature", "invitation"), zap.String("email", email), zap.Error(err))
		return errors.New("failed send email")
	}
	logger.Info("send partner invitation email", zap.String("feature", "invitation"), zap.String("email", email), zap.Time("time", time.Now().UTC()))

	return nil
}

func sendInviteEmail(ctx context.Context, inviterEmail, email, token, orgName string, orgID uint64) error {
	logger := tilog.WithContextL(ctx)

	url := fmt.Sprintf("https://%s/invitation-verification?token=%s", utils.GetCentralBackendDns(), token)
	subject := fmt.Sprintf("Invitation to TiDB Cloud: %s", orgName)
	tilog.Info(ctx, "sendInviteEmail in invite user, subject:"+subject)
	escapeOrgName := utils.EscapeStr(ctx, orgName)

	vars := map[string]string{
		"organizationName": escapeOrgName,
		"orgNameInSubject": orgName,
		"inviterName":      inviterEmail,
		"url":              url,
	}
	_, err := account.GetHandler().SendEmail(ctx, &accountpb.SendEmailReq{
		OrgId:      orgID,
		Scene:      string(entity.NotificationSceneInvite),
		Identifier: uuid.New().String(),
		Subject:    subject,
		Emails:     []string{email},
		CcEmails:   nil,
		TemplateId: config.SendgridTemplateName2id[config.TNInviteUser],

		TemplateVars: vars,
	})
	if err != nil {
		logger.Error("failed to send invitation email", zap.String("feature", "invitation"), zap.String("email", email), zap.Error(err))
		return errors.New("failed send email")
	}
	logger.Info("send invitation email", zap.String("feature", "invitation"), zap.String("email", email), zap.Time("time", time.Now().UTC()))

	return nil
}

func (s *InviteUserSvc) saveInviteUser(ctx context.Context, dbUser *entity.User, email string, orgID entity.ID) (*entity.User, error) {
	now := time.Now()
	var retUser *entity.User
	if dbUser != nil {
		status := entity.UserStatusPending
		if dbUser.Status == entity.UserStatusActive {
			status = dbUser.Status
		}
		toUpdateUser := entity.User{
			Status:    status,
			InvitedAt: &now,
			TenantID:  orgID,
			Role:      entity.UserRoleMember, // no use, just set
		}
		toUpdateUser.ID = dbUser.ID
		err := (&toUpdateUser).Update(database.DBConn.DB,
			entity.UserDBSchema.Status,
			entity.UserDBSchema.InvitedAt,
			entity.UserDBSchema.TenantID,
			entity.UserDBSchema.Role,
		)
		if err != nil {
			tilog.Error(ctx, "InviteOneUser update user error", zap.Any("err", err))
			return nil, errors.New("failed to update user")
		}
		retUser = &toUpdateUser
	} else {
		toInsertUser := entity.User{
			Email:     email,
			FirstName: "",
			LastName:  "",
			Status:    entity.UserStatusPending,
			InvitedAt: &now,
			TenantID:  orgID,
			Role:      entity.UserRoleMember, // no use, just set
		}
		err := (&toInsertUser).Create(database.DBConn.DB)
		if err != nil {
			tilog.Error(ctx, "InviteOneUser insert user error", zap.Any("err", err))
			return nil, errors.New("failed to insert user")
		}
		retUser = &toInsertUser
	}
	return retUser, nil
}
