package tenant

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

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

	accountErr "github.com/tidbcloud/account/commons/errors"
	"github.com/tidbcloud/account/internel/dao"

	"github.com/tidbcloud/account/central/events/publish"

	pkg_http "github.com/tidbcloud/account/pkg/grpc/http"

	"github.com/tidbcloud/account/internel/controllers"
	"github.com/tidbcloud/account/internel/controllers/central/location"
	controllerorg "github.com/tidbcloud/account/internel/controllers/central/org"
	"github.com/tidbcloud/account/internel/svc/admin"
	auditlog3 "github.com/tidbcloud/account/internel/svc/auditlog"
	"github.com/tidbcloud/account/internel/svc/authn"
	"github.com/tidbcloud/account/internel/svc/dbuser"
	historysvc "github.com/tidbcloud/account/internel/svc/history"
	orgsvc "github.com/tidbcloud/account/internel/svc/org"
	tenant2 "github.com/tidbcloud/account/internel/svc/org/tenantctx"
	orgsso2 "github.com/tidbcloud/account/internel/svc/orgsso"
	rbac2 "github.com/tidbcloud/account/internel/svc/rbac"
	usersvc "github.com/tidbcloud/account/internel/svc/user"

	"github.com/duke-git/lancet/v2/slice"
	"github.com/tidbcloud/account/commons/database"
	"github.com/tidbcloud/account/internel/dao/project"
	userDao "github.com/tidbcloud/account/internel/dao/user"

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

	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/account/central/events"
	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/account/idl/pbgen/proto/central"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/internel/dao/role"
	"github.com/tidbcloud/pkgv2/auth/auth0"
	"github.com/tidbcloud/pkgv2/http/recaptcha"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"
	"google.golang.org/protobuf/types/known/timestamppb"
	"gorm.io/gorm"
)

type tenantService struct {
	central.UnimplementedTenantServiceServer
	authn.OperationsService
	tenantModel tenant2.Model
	adminModel  admin.Model
}

var _ central.TenantServiceServer = (*tenantService)(nil)

const (
	defaultOrder    = "asc"
	OrderDesc       = "desc"
	sortByLoginTime = "login_time"
)

// NewService constructs a tenantService
func NewService(s authn.OperationsService, model tenant2.Model, adminModel admin.Model) central.TenantServiceServer {

	return &tenantService{OperationsService: s, tenantModel: model, adminModel: adminModel}
}

func (s *tenantService) userContext(ctx context.Context) tenant2.Context {
	return s.tenantModel.User(ctx, s.CurrentUser(ctx))
}

func (s *tenantService) Log(ctx context.Context) *zap.Logger {
	return s.userContext(ctx).Log()
}

func (s *tenantService) getAuth0ConnectionType(ctx context.Context, auth0Token string) (config.Auth0ConnectionType, error) {
	if auth0Token == "" {
		return config.Auth0DatabaseConnection, nil
	}

	sub, err := s.tenantModel.GetSubFromAuth0Token(auth0Token)
	if err != nil {
		return "", err
	}
	tilog.Info(ctx, "GetSubFromAuth0Token ", zap.String("sub", sub))
	if strings.HasPrefix(sub, config.Auth0SocialConnectionGoogle.String()) {
		return config.Auth0SocialConnectionGoogle, nil
	} else if strings.HasPrefix(sub, config.Auth0SocialConnectionGithub.String()) {
		return config.Auth0SocialConnectionGithub, nil
	} else if strings.HasPrefix(sub, config.Auth0SocialConnectionMicrosoft.String()) {
		return config.Auth0SocialConnectionMicrosoft, nil
	} else {
		split := strings.Split(sub, "|")
		if len(split) == 3 && (split[0] == "oidc" || split[0] == "samlp") {
			return config.Auth0ConnectionType(split[1]), nil
		}
	}
	tilog.Info(ctx, "unknown connection type", zap.String("sub", "sub"))
	return "", errors.Errorf("unknown connection type %v", sub) // TODO
}

func buildCreateOrgParams(req *central.RegistrationReq, clientIP string, connectionType config.Auth0ConnectionType) *tenant2.CreateOrgParams {
	params := &tenant2.CreateOrgParams{
		Name:                      req.Company,
		Email:                     req.Email,
		Country:                   req.Country,
		Password:                  req.Password,
		FirstName:                 req.FirstName,
		LastName:                  req.LastName,
		Job:                       req.Job,
		PhoneNumber:               req.PhoneNumber,
		ConnectionType:            connectionType,
		ClientIP:                  clientIP,
		Auth0Token:                req.Auth0Token,
		ThirdPartyAccountProvider: req.ThirdPartyAccountProvider,
		ThirdPartyAccountToken:    req.ThirdPartyAccountToken,
		ThirdPartyFreeTrial:       req.ThirdPartyFreeTrial,
	}

	if req.Source != nil {
		source := req.Source
		params.Source = &tenant2.RegistrationSource{
			UtmCampaign:           source.UtmCampaign,
			UtmContent:            source.UtmContent,
			UtmMedium:             source.UtmMedium,
			UtmSource:             source.UtmSource,
			UtmTerm:               source.UtmTerm,
			MostRecentReferralUrl: source.MostRecentReferralUrl,
			WebsiteReferrerURL:    source.WebsiteReferrerUrl,
			SignupSource:          source.SignupSource,
			ProviderSource:        source.ProviderSource,
		}

		// truncate the URL if it is too long
		if len(source.WebsiteReferrerUrl) > 255 {
			params.Source.WebsiteReferrerURL = source.WebsiteReferrerUrl[:255]
		}
		if len(source.MostRecentReferralUrl) > 255 {
			params.Source.MostRecentReferralUrl = source.MostRecentReferralUrl[:255]
		}
	}

	return params
}

func (s *tenantService) registerFromOKTA(ctx context.Context, req *central.RegistrationReq, connectionType config.Auth0ConnectionType) error {
	userContext := s.tenantModel.User(ctx, &usersvc.User{})
	logger := tilog.WithContextL(ctx)

	return database.Transaction(func(tx *gorm.DB) error {
		tenantID := entity.ID(config.Global().OktaMapperConfig[connectionType.String()])

		defaultProject, err := project.GetActiveProjectByOrgID(tx, tenantID)
		if err != nil {
			logger.Error("failed to find default project for tenant", zap.Uint64("tenantID", tenantID.Uint64()), zap.Error(err))
			return err
		}

		auth0User, err := userContext.GetUserFromAuth0(ctx, connectionType, req.Auth0Token)
		if err != nil {
			logger.Error("failed to get auth0 user by token", zap.String("token", req.Auth0Token), zap.Error(err))
			return err
		}

		_, err = usersvc.UpsertUser(ctx, tx, usersvc.CreateUserRequired{
			Email:     auth0User.Email,
			FirstName: auth0User.FirstName,
			LastName:  auth0User.LastName,
			InviteAt:  nil,
			Status:    entity.UserStatusActive,
			TenantID:  tenantID,
			Role:      entity.UserRoleMember,
			Projects:  []entity.Project{defaultProject},
		})
		if err != nil {
			return err
		}

		//newUser, err := dbutil.GetUserByEmail(tx, auth0User.Email)
		//if err != nil {
		//	return err
		//}
		//
		//_, err = dbutil.AddProjectRoleForUser(ctx, tx, newUser.ID, defaultProject.ID, entity.RoleProjectOwner, entity.RoleBindingStatusActive)
		//if err != nil {
		//	return err
		//}

		return nil
	})
}

// Registration
//
//	@Description: register
//	@path:/api/v1/orgs [POST]
//
// @Author wangletian 2024-03-14 17:02:02
// @receiver s
// @param ctx
// @param req
// @return *central.DefaultResp
// @return error
func (s *tenantService) Registration(ctx context.Context, req *central.RegistrationReq) (*central.DefaultResp, error) {
	logger := tilog.WithContextL(ctx)
	logger.Info("user registration request", zap.Any("req", req))

	logger.Info("Registration ", zap.Any("email", req.Email), zap.Any("ThirdPartyAccountProvider", req.ThirdPartyAccountProvider), zap.Any("ThirdPartyAccountToken", req.ThirdPartyAccountToken))

	// since FE store remove token from local storage, fe can not get token again, so here get token from cookie to ensure origin logic
	if req.Auth0Token == "" {
		// only for social account (non email/password) signup
		if req.Email == "" && req.Password == "" {
			sub, token, err := parseAuth0TokenFromCookie(ctx)
			if err != nil {
				logger.Error("Registration parseAuth0TokenFromCookie error", zap.Any("err", err))
				return nil, err
			}
			if !strings.HasPrefix(sub, "auth0") {
				req.Auth0Token = token
			}
		}
	}

	// sanitize payload
	sanitizedRequest := &central.RegistrationReq{
		// country is not a must-have field now, and it will be guessed from IP if it's empty string

		Country:   s.SanitizerPolicy().Sanitize(strings.ToUpper(strings.TrimSpace(req.Country))),
		FirstName: s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.FirstName)),
		LastName:  s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.LastName)),
		// auth0 use lowercase email
		Email:                     s.SanitizerPolicy().Sanitize(strings.ToLower(strings.TrimSpace(req.Email))),
		Job:                       s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.Job)),
		Company:                   s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.Company)),
		PhoneNumber:               s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.PhoneNumber)),
		Password:                  req.Password,
		AcceptService:             req.AcceptService,
		RecaptchaResponse:         s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.RecaptchaResponse)),
		ThirdPartyAccountProvider: s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.ThirdPartyAccountProvider)),
		ThirdPartyAccountToken:    s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.ThirdPartyAccountToken)),
		ThirdPartyFreeTrial:       req.ThirdPartyFreeTrial,
		ConnectionType:            req.ConnectionType,
		Auth0Token:                req.Auth0Token,
		Channel:                   req.Channel,
		ReturnUrl:                 req.ReturnUrl,
	}
	if req.Source != nil {
		source := req.Source
		sanitizedRequest.Source = &central.RegistrationSource{
			WebsiteReferrerUrl:    s.SanitizerPolicy().Sanitize(source.WebsiteReferrerUrl),
			UtmCampaign:           s.SanitizerPolicy().Sanitize(source.UtmCampaign),
			UtmContent:            s.SanitizerPolicy().Sanitize(source.UtmContent),
			UtmMedium:             s.SanitizerPolicy().Sanitize(source.UtmMedium),
			UtmSource:             s.SanitizerPolicy().Sanitize(source.UtmSource),
			UtmTerm:               s.SanitizerPolicy().Sanitize(source.UtmTerm),
			MostRecentReferralUrl: s.SanitizerPolicy().Sanitize(source.MostRecentReferralUrl),
			SignupSource:          s.SanitizerPolicy().Sanitize(source.SignupSource),
			ProviderSource:        s.SanitizerPolicy().Sanitize(source.ProviderSource),
		}
	}

	connectionType, err := s.getAuth0ConnectionType(ctx, req.Auth0Token)
	logger.Info("register ", zap.Any("email", req.Email), zap.Any("connectionType", connectionType))
	if err != nil {
		tilog.Warn(ctx, "failed to get auth0 connection type", zap.String("feature", "signup"), zap.Error(err))
		return nil, errs.NewBadRequest("failed to get auth0 connection type", err)
	}

	if connectionType.IsOKTAConnection() {
		if err := s.registerFromOKTA(ctx, req, connectionType); err != nil {
			logger.Error("failed to register user for okta", zap.Any("req", req), zap.Error(err))
			return nil, err
		}

		resp := &central.DefaultResp{
			BaseResp: &base.BaseResp{},
		}
		return resp, nil
	}

	// database connection no auth0 token
	if connectionType != config.Auth0DatabaseConnection {
		claims, _ := orgsso2.GetClaimsFromToken(req.Auth0Token)
		orgID, _ := orgsso2.GetOrgIDClaim(claims)
		orgType, _ := orgsso2.GetOrgTypeClaim(claims)
		if orgID != 0 && orgType == string(entity.OrgTypeEnterprise) {
			email, _ := auth0.GetEmailClaim(claims)
			svc, _ := orgsso2.GetOrgUserRegisterSvc()
			autoProvision, err := svc.WhetherHitAutoProvision(ctx, email, orgID, connectionType.String())
			if err != nil {
				logger.Error("WhetherAutoRegisterSsoUser check error", zap.Any("req", req), zap.Error(err))
				return nil, err
			}
			if !autoProvision {
				logger.Info("sso org not open auto provision, register fail", zap.Any("orgID", orgID), zap.Any("email", email), zap.Error(err))
				return nil, errs.NewBadRequest("sso org just support auto provision register user, please contact owner to open auto provision", nil)
			} else {
				param := orgsso2.RegisterSsoUserParam{
					Auth0Token: req.Auth0Token,
					MethodName: connectionType.String(),
					OrgID:      orgID,
				}
				err = svc.RegisterSsoUser(ctx, &param)
				if err != nil {
					logger.Error("RegisterSsoUser error", zap.Any("orgID", orgID), zap.Any("token", req.Auth0Token), zap.Error(err))
					return nil, err
				}
				resp := &central.DefaultResp{
					BaseResp: &base.BaseResp{},
				}
				return resp, nil
			}
		}
	}

	err = validateRegistrationReq(ctx, sanitizedRequest, connectionType)
	if err != nil {
		return nil, errs.NewBadRequest(err.Error(), err)
	}

	if config.Global().ServerConfig.EnableRecaptcha && connectionType == config.Auth0DatabaseConnection {
		// send google response to https://www.google.com/recaptcha/api/siteverify
		// to check out weather user has passed the recaptcha verification.
		recaptchaHandle := recaptcha.NewRecaptchaHandle(&http.Client{Timeout: 5 * time.Second}, config.Global().ServerConfig.RecaptchaSecret)
		if _, err := recaptchaHandle.ValidateReCAPTCHA(req.RecaptchaResponse); err != nil {
			tilog.Error(ctx, "failed to validate recaptcha", zap.String("feature", "signup"), zap.Error(err))
			return nil, errs.NewInternalUnknown(err.Error(), err)
		}
	}

	clientIP, err := auditlog3.GetClientIP(ctx)
	if err != nil {
		tilog.Warn(ctx, "failed to get client ip", zap.Error(err))
	} else {
		// NOTE here we got a verbose logic, as we're trying our best to get the country code by ip
		// country code in request will be empty if:
		// * it's a fast registration
		// * it's a signup from Google/Github SSO
		if sanitizedRequest.Country == "" {
			var ipToInspect = clientIP
			var countryCode = ""

			if ipToInspect != "" {
				if countryCode, err = location.QueryCountryCodeByIP(ctx, ipToInspect); err != nil {

					tilog.Warn(ctx, "failed to get country code by ip", zap.Error(err), zap.String("ip", ipToInspect))
				} else {
					sanitizedRequest.Country = s.SanitizerPolicy().Sanitize(countryCode)
				}

			}
			tilog.Info(ctx, "query country_code by ip", zap.String("ip", ipToInspect), zap.String("QueryCountryCodeByIP", countryCode), zap.String("country_code", sanitizedRequest.Country), zap.Error(err))
		}
	}

	if clientIP != "" {
		// access limit by client ip
		rateConf := config.Global().RateControlConfig.Registration
		now := time.Now()
		rateController := singleton.RateControllerManager.WithScene(rateConf.Scene)
		if !rateController.AllowAll(ctx, clientIP, "", now, rateConf.Rates...) {
			tilog.Info(ctx, fmt.Sprintf("client ip: %s, access limit check blocked", clientIP))
			return nil, errcode.NewInvalidInputErr(errors.New("registration request too often"))
		}
		rateController.Access(ctx, clientIP, req.Email, now)
	}

	createOrgParams := buildCreateOrgParams(sanitizedRequest, clientIP, connectionType)
	userContext := s.tenantModel.User(ctx, &usersvc.User{})
	// Registration  ,to create org
	tilog.Info(ctx, "will CreateOrg ", zap.Any("email", req.Email))
	if err := userContext.CreateOrg(ctx, createOrgParams, req.ReturnUrl); err != nil {
		return nil, err
	}

	resp := &central.DefaultResp{
		BaseResp: &base.BaseResp{},
	}

	//
	if connectionType != config.Auth0DatabaseConnection {
		tilog.Info(ctx, "Registration  will publishRegisterEvent ", zap.Any("email", req.Email), zap.Any("connectionType", connectionType))
		q := events.UserQuery{Token: req.Auth0Token, Email: req.Email, ClientIP: clientIP}
		if err := events.PublishRegisterEvent(ctx, q, &req.Channel); err != nil {
			tilog.Info(ctx, "PublishRegisterEvent fail", zap.Any("email", req.Email), zap.Any("connectionType", connectionType))
		}
	} else {
		//email  register ， if from channel  save user_load_action
		if req.Channel {
			email := strings.ToLower(strings.TrimSpace(req.Email))
			tilog.Info(ctx, "Registration  from  channel ", zap.Any("email", email))
			userSvc, _ := usersvc.GetUserSvc()
			user, err := userSvc.GetUserByEmail(ctx, email)
			if err != nil {
				tilog.Error(ctx, "fail to  GetUserByEmail", zap.Any("email", strings.ToLower(email)), zap.Error(err))
				return resp, nil
			}

			userLandingAction := &entity.UserLandingAction{
				UserID:       user.ID,
				TargetAction: entity.RegisterChannelAction,
			}
			tilog.Info(ctx, "CreateUserLandingAction", zap.Any("userLandingAction", userLandingAction))
			if err := userSvc.CreateUserLandingAction(ctx, userLandingAction); err != nil {
				tilog.Error(ctx, "CreateUserLandingAction fail ", zap.Any("userLandingAction", userLandingAction), zap.Error(err))
			}
			tilog.Info(ctx, "Registration  from  channel  CreateUserLandingAction success", zap.Any("email", email), zap.Any("userLandingAction", userLandingAction))
		}

	}
	tilog.Info(ctx, "Registration success", zap.Any("email", req.Email), zap.Any("connectionType", connectionType))

	return resp, nil
}

func parseAuth0TokenFromCookie(ctx context.Context) (string, string, error) {
	cookieName := utils.GetCookieName()
	tilog.Info(ctx, "get cookie name", zap.Any("cookie_name", cookieName))
	sid := pkg_http.GetCookie(ctx, cookieName)
	tilog.Info(ctx, "get sid from cookie", zap.Any("sid", sid))
	if sid == "" {
		return "", "", errs.NewBadRequest("not found cookie", nil)
	}
	sEnt, err := dao.GetDaoHub().UserSession.GetUserSessionBySessionID(ctx, database.DBConn.DB, sid)
	if err != nil {
		return "", "", errs.NewInternalUnknown("get session by sid error", err)
	}
	if sEnt == nil {
		return "", "", errs.NewBadRequest("not found session by sid", err)
	}
	token := sEnt.IDToken
	tilog.Info(ctx, "parse token from cookie", zap.Any("cookie_name", cookieName), zap.Any("sid", sid), zap.Any("token", token))
	tokensvc, _ := token2.GetTokenSvc()
	claims, err := tokensvc.GetClaimsFromToken(ctx, token)
	if err != nil {
		return "", "", errs.NewUnauthorized("failed to parse token", err)
	}
	isub, ok := claims["sub"]
	if !ok {
		return "", "", errs.NewInternalUnknown("sub not found in claims", nil)
	}
	sub, ok := isub.(string)
	if !ok {
		return "", "", errs.NewInternalUnknown("sub not string in claims", nil)
	}
	return sub, token, nil

}

func (s *tenantService) SendVerificationEmail(ctx context.Context, req *central.SendVerificationEmailReq) (*emptypb.Empty, error) {

	tilog.Info(ctx, "SendVerificationEmail req", zap.Any("req", req))
	if req.Email == "" {
		return nil, errcode.NewInvalidInputErr(errors.New("email is blank"))
	}
	email := s.SanitizerPolicy().Sanitize(strings.ToLower(strings.TrimSpace(req.Email)))

	// limitation of send verification-email
	accessor := email
	target := email
	rateConf := config.Global().RateControlConfig.RegistrationVerification
	now := time.Now()
	rateController := singleton.RateControllerManager.WithScene(rateConf.Scene)
	if !rateController.AllowAll(ctx, accessor, target, now, rateConf.Rates...) {
		tilog.Info(ctx, "request send verification email too often", zap.String("email", email))
		return nil, errcode.NewInvalidInputErr(errors.New("request send verification email too often"))
	}
	rateController.Access(ctx, accessor, target, now)

	userContext := s.tenantModel.User(ctx, &usersvc.User{})
	if err := userContext.SendVerificationEmail(ctx, email, req.ReturnUrl); err != nil {
		return &emptypb.Empty{}, err
	}
	return &emptypb.Empty{}, nil
}

func (s *tenantService) GetOrganization(ctx context.Context, _ *emptypb.Empty) (*central.Organization, error) {
	organization, err := s.userContext(ctx).GetOrganization(ctx)
	if err != nil {
		logger := s.Log(ctx)
		logger.Error("failed to get organization", zap.Error(err))
		if errors.Find(err, func(e error) bool { return e == gorm.ErrRecordNotFound }) != nil {
			return nil, errcode.NewNotFoundErr(errors.New("failed to get organization"))
		}
		return nil, errcode.NewInternalErr(errors.New("failed to get organization"))
	}

	// is new
	// TODO: remove this flag when gray scale test is finished
	if strings.HasSuffix(strings.ToLower(organization.Email), "@pingcap.com") {
		organization.WhitelistFlag |= tenant2.EnableNewClusterListUIFlag
	}

	return s.organizationOutput(organization)
}

func (s *tenantService) organizationOutput(org *tenant2.Organization) (*central.Organization, error) {

	return &central.Organization{
		Id:              org.ID.Uint64(),
		Name:            org.Name,
		CreatedAt:       timestamppb.New(org.CreateAt),
		Email:           org.Email,
		Country:         org.Country,
		Status:          strings.ToUpper(org.Status),
		Plan:            strings.ToUpper(org.Plan),
		Timezone:        org.Timezone,
		UserSetTimezone: org.UserSetTimezone,
		TotalCredit:     org.TotalCredit,
		RemainingCredit: org.RemainingCredit,
		// ActiveClustersCount:       org.ActiveClustersCount,
		// FreeClustersCount:         org.FreeClustersCount,
		HasDiscount:         org.HasDiscount,
		DiscountDescription: org.DiscountDescription,
		Discount:            org.Discount,
		// AcknowledgeCnPolicy:       org.AcknowledgeCnPolicy,
		// HasTriedDevTier:           org.HasTriedDevTier,
		ThirdPartyAccountProvider: org.ThirdPartyAccountProvider,
		ThirdPartyAccountCode:     org.ThirdPartyAccountCode,
		ThirdPartyFreeTrial:       org.ThirdPartyFreeTrial,
		WhitelistFlag:             org.WhitelistFlag,
		OrgType:                   org.OrgType,
		CompanyName:               org.CompanyName,
	}, nil
}

func (s *tenantService) ListOrganizations(ctx context.Context, _ *emptypb.Empty) (*central.OrganizationList, error) {
	orgList, err := s.userContext(ctx).ListOrganizations(ctx)
	if err != nil {
		return nil, errs.NewBadRequest(err.Error(), err)
	}
	user := s.CurrentUser(ctx)
	var defaultClusterId uint64
	var isNewUser bool
	if user != nil {

		//userSvc, _ := deps.LoadUserSvc()
		userSvc, _ := usersvc.GetUserSvc()
		userId := user.ID.Uint64()

		isNewUser, err = userSvc.IsNewUser(ctx, &userId)
		tilog.Info(ctx, "isNewUser result", zap.Any("isNewUser", isNewUser), zap.Any("email", user.Email), zap.Any("userId", userId))
		if err != nil {
			return nil, err
		}
		if isNewUser {

			obj, err := userSvc.GetNewUserDefaultCluster(ctx, &userId, nil)
			if err != nil {
				tilog.Error(ctx, "GetNewUserDefaultCluster fail", zap.Error(err))
			}
			tilog.Info(ctx, "GetNewUserDefaultCluster  result ", zap.Any("userId", userId), zap.Any("DefaultCluster", obj))
			if obj != nil {
				defaultClusterId = obj.TargetID.Uint64()

			}
		}
	}
	response := &central.OrganizationList{
		Orgs:           make([]*central.Organization, len(orgList)),
		IsNewUser:      isNewUser,
		DefaultCluster: defaultClusterId,
	}

	for i, org := range orgList {
		out, _ := s.organizationOutput(org)
		response.Orgs[i] = out
	}

	return response, nil
}

func (s *tenantService) UpdateOrganization(ctx context.Context, req *central.UpdateOrgReq) (*emptypb.Empty, error) {
	userContext := s.userContext(ctx)

	orgSvc, _ := orgsvc.GetOrgSvc()
	org, err := orgSvc.GetOrgByID(ctx, req.OrgId)

	if err != nil {
		return nil, errs.WrapGORMError(err, "org not found")
	}

	// frontend will set null when user not set
	if req.UserSetTimezone == "null" {
		req.UserSetTimezone = ""
	}

	request := &central.UpdateOrgReq{
		Name:            strings.TrimSpace(req.Name),
		Timezone:        req.Timezone,
		UserSetTimezone: req.UserSetTimezone,
	}
	err = validateUpdateOrgReq(request)
	if err != nil {
		return &emptypb.Empty{}, errcode.NewInvalidInputErr(err)
	}
	err = userContext.UpdateOrganization(ctx, request)

	// timezone changed
	orgTimezone := org.TimeZone
	orgTimezone = utils.ConvTimezone2NewTimezone(orgTimezone)
	if orgTimezone != req.UserSetTimezone {
		details := auditlog.Details{
			auditlog.KeyOldTimezone: org.TimeZone,
			auditlog.KeyNewTimezone: req.UserSetTimezone,
			auditlog.KeyError:       err,
		}
		auditlog.NewEvent(ctx, auditlog.EventUpdateTimezone).SetResult(err == nil).SetDetails(details.Build()).SetOrgID(req.OrgId).Submit() //nolint:errcheck
	}
	// name changed
	if org.Name != req.Name {
		details := auditlog.Details{
			auditlog.KeyOldOrgName: org.Name,
			auditlog.KeyNewOrgName: req.Name,
			auditlog.KeyError:      err,
		}
		auditlog.NewEvent(ctx, auditlog.EventUpdateOrgName).SetResult(err == nil).SetDetails(details.Build()).SetOrgID(req.OrgId).Submit() //nolint:errcheck
	}

	if err != nil {
		return &emptypb.Empty{}, err
	}
	return &emptypb.Empty{}, nil
}

func (s *tenantService) AcknowledgeCNPolicy(ctx context.Context, req *central.AcknowledgeCNPolicyReq) (*emptypb.Empty, error) {
	userContext := s.userContext(ctx)

	ipAddress, err := auditlog3.GetClientIP(ctx)
	if err != nil {
		return nil, errcode.NewInternalErr(errors.New("failed to get client IP address"))
	}
	err = userContext.AcknowledgeCNPolicy(ipAddress)
	if err != nil {
		return nil, errcode.NewInternalErr(errors.New("failed to acknowledge CN policy"))
	}

	return &emptypb.Empty{}, nil
}

func (s *tenantService) InviteUserToOrg(ctx context.Context, req *central.InviteUserToOrgReq) (*emptypb.Empty, error) {
	orgID := entity.ID(req.OrgId)
	userContext := s.userContext(ctx)

	// limitation of send invite-email
	accessor := fmt.Sprint(s.CurrentUser(ctx).ID)
	rateConf := config.Global().RateControlConfig.InviteUser
	now := time.Now()
	rateController := singleton.RateControllerManager.WithScene(rateConf.Scene)
	// target(invitee) is unconcerned. limitation is for inviter.
	if !rateController.AllowAll(ctx, accessor, "", now, rateConf.Rates...) {
		return nil, errcode.NewInvalidInputErr(errors.New("request send invite email too often"))
	}
	rateController.Access(ctx, accessor, req.Email, now)

	emailAddresses := s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.Email))
	emailSlice := strings.Split(emailAddresses, ";")
	var emailList []string
	for _, email := range emailSlice {
		email = strings.ToLower(strings.TrimSpace(email))
		if email != "" {
			emailList = append(emailList, email)
		}
	}

	user := s.CurrentUser(ctx)
	tilog.Info(ctx, "get CurrentUser:", zap.Any("user", user))
	inviteUserReq := tenant2.InviteUserReq{
		Operator:  user.ID,
		OrgID:     orgID,
		ProjectID: 0,
		EmailList: emailList,
		RbacRole:  req.RbacRole,
	}
	err := validateInviteUserReq(inviteUserReq)
	if err != nil {
		return nil, errcode.NewInvalidInputErr(err)
	}
	err = userContext.InviteUser(ctx, inviteUserReq)
	if err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func (s *tenantService) ListUsers(ctx context.Context, req *central.ListUsersReq) (*central.Users, error) {

	f := userDao.RoleBindingUserFilter{
		RoleBindingStatus: []string{string(entity.Active), entity.RoleBindingStatusInactive},
		UserStatus:        []string{string(entity.UserStatusActive), string(entity.UserStatusPending)},
		ScopeType:         entity.RoleBindingScopeOrg,
		ScopeId:           req.OrgId,
		Order:             req.Order,
		SortBy:            req.SortBy,
		Roles:             req.Roles,
	}
	if req.Email != "" {

		email, _ := url.QueryUnescape(req.Email)
		f.EmailLike = email
		req.Email = email
	}
	userSvc, _ := usersvc.GetUserSvc()
	userList, err := userSvc.ListRoleBindingUsers(ctx, f)

	if err != nil {
		return nil, err
	}
	//  roleBindingUser convert to central.user and  ignore system user
	users := usersOutput(userList, req.Statuses)
	// add login history time
	loginHistorySvc, _ := historysvc.GetHistorySvc()
	loginHistorySvc.SetUserLoginTime(ctx, &users)
	//getLoginTime(ctx, &users)
	//  If it is sorted by login time, it needs to be processed separately
	userSortByLoginTime(ctx, req.SortBy, req.Order, users)
	return &central.Users{
		Total: int32(len(users)),
		Items: utils.Pagination(req.Page, req.PerPage, users).([]*central.User),
	}, nil
}

// If it is sorted by login time, it needs to be processed separately
func userSortByLoginTime(ctx context.Context, sortBy, order string, users []*central.User) {
	if sortBy == sortByLoginTime {
		orderStr := defaultOrder
		if order != "" {
			orderStr = order
		}
		if orderStr == defaultOrder {
			slice.SortBy(users, func(a, b *central.User) bool {
				if a.LoginTime == nil {
					return true
				}
				if b.LoginTime == nil {
					return false
				}
				return a.LoginTime.Seconds < b.LoginTime.Seconds
			})
		} else if orderStr == OrderDesc {
			slice.SortBy(users, func(a, b *central.User) bool {
				if b.LoginTime == nil {
					return true
				}
				if a.LoginTime == nil {
					return false
				}
				return b.LoginTime.Seconds < a.LoginTime.Seconds
			})
		}

		tilog.Info(ctx, "Sort by login_time in memory "+fmt.Sprintf("order=%s", orderStr))

	}
}

func (s *tenantService) UpdateUser(ctx context.Context, req *central.UpdateUserReq) (*emptypb.Empty, error) {
	// todo mock offline unused api
	return nil, errs.NewBadRequest("The API has been taken offline", nil)

	//orgID := entity.ID(req.OrgId)
	//userID := entity.ID(req.UserId)
	//newRole := entity.UserRole(req.Role)
	//
	//userContext := s.userContext(ctx)
	//
	//updateUserReq := tenant2.UpdateUserReq{
	//	OrgID:    orgID,
	//	UserID:   userID,
	//	Role:     newRole,
	//	RbacRole: req.RbacRole,
	//}
	//err := validateUpdateUserReq(updateUserReq)
	//if err != nil {
	//	return nil, errcode.NewInvalidInputErr(err)
	//}
	//
	//userEmail, oldRole, orgBinding, err := tenant2.GetUserEmailAndRole(ctx, orgID, userID)
	//if err != nil {
	//	return nil, err
	//}
	//err = userContext.UpdateUser(ctx, updateUserReq)
	//details := auditlog.Details{
	//	auditlog.KeyUserEmail: userEmail,
	//	auditlog.KeyOldRole:   oldRole,
	//	auditlog.KeyNewRole:   entity.RoleBindingDecode(req.RbacRole),
	//	auditlog.KeyError:     err,
	//}
	//auditlog.NewEvent(ctx, auditlog.EventUpdateUserRole).SetOrgID(orgID.Uint64()).SetDetails(details.Build()).SetResult(err == nil).Submit() //nolint:errcheck
	//
	//if orgBinding.Role != req.RbacRole {
	//	_ = userevent.PublishChangeUserOrgRoleEvent(ctx, req.OrgId, req.UserId, orgBinding.Role, req.RbacRole)
	//}
	//
	//tilog.Info(ctx, "UpdateUser", zap.String("oldRole", string(oldRole)), zap.String("newRole", req.RbacRole))
	//
	//// wangletian update，create or delete dbUser where  update user role
	//if err == nil && isActiveInOrg(ctx, userID, orgID) {
	//	{
	//		userAction := entity.CommandUserAction{
	//			UserID: userID,
	//			OrgID:  orgID,
	//			Email:  userEmail,
	//			Status: entity.CommandStatusWaiting,
	//		}
	//		userAction.Action = entity.CommandActionDelete
	//		if req.RbacRole == entity.RoleOrgOwner {
	//			userAction.Action = entity.CommandActionCreate
	//		}
	//		dbUserSvc, _ := dbuser.GetDBUserSvc()
	//		insertUserAction(ctx, dbUserSvc, userAction)
	//	}
	//}
	//
	//return &emptypb.Empty{}, err
}

func usersOutput(users []userDao.RoleBindingUser, filterStatus []string) []*central.User {
	var lowerFilterStatus []string
	if len(filterStatus) > 0 {
		for _, s := range filterStatus {
			lowerFilterStatus = append(lowerFilterStatus, strings.ToLower(s))
		}
	}
	var opUsers []*central.User
	for _, user := range users {
		// Ignore system user
		if user.FirstName == "system" && user.LastName == "user" {
			continue
		}
		// inactive in the rolBinding is pending
		if user.Status == entity.RoleBindingStatusInactive {
			user.Status = entity.RoleBindingStatusPending
		}
		if len(lowerFilterStatus) > 0 && !slice.Contain(lowerFilterStatus, strings.ToLower(user.Status)) {
			continue
		}
		opUser := &central.User{
			Id:         user.UserId,
			Email:      user.Email,
			Status:     strings.ToUpper(user.Status),
			InviteTime: timestamppb.New(user.InviteTime),
			RbacRole:   user.Role,
			//Role:       user.Role,
		}

		opUsers = append(opUsers, opUser)
	}
	return opUsers
}

/*// getLoginTime

// @Description:get user's  last login time
// @Author wangletian 2023-09-14 15:34:20
// @param ctx
// @param opUsers
func getLoginTime(ctx context.Context, opUsers *[]*central.User) {
	users := *opUsers
	var userIDs []entity.ID
	for _, u := range users {
		userIDs = append(userIDs, entity.ID(u.Id))
	}
	loginHistorySvc, _ := historysvc.GetHistorySvc()
	historyFilter := history.EventHistoryFilter{
		IdentityIDs:   userIDs,
		IdentityTypes: []entity.HistoryIdentityType{entity.IdentityTypeUser},
		Events:        []entity.HistoryEvent{entity.HistoryEventLogin}}

	histories, err := loginHistorySvc.ListEventHistoryLast(ctx, historyFilter)
	if err != nil {
		return
	}
	historyMap := make(map[uint64]*entity.EventHistoryLast)
	for i, h := range histories {
		historyMap[h.IdentityID.Uint64()] = &histories[i]

	}
	for _, u := range users {
		h := historyMap[u.Id]
		if h != nil && u.Status == strings.ToUpper(string(entity.Active)) {
			u.LoginTime = timestamppb.New(*(h.UpdatedAt))
		}

	}

}
*/
// BUG(iven): User account should not be deleted by org owner, but only by system admin or itself
func (s *tenantService) DeleteUser(ctx context.Context, req *central.DeleteUserReq) (*emptypb.Empty, error) {
	// delete user from org
	bm := utils.Begin(ctx, "DeleteUser", zap.Any("DeleteUserReq", req))
	defer bm.OnExit(ctx)

	userSvc, _ := usersvc.GetUserSvc()

	operator, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		tilog.Info(ctx, "failed to get user from context", zap.Error(err))
		return nil, err
	}

	if operator.ID.Uint64() == req.UserId {
		return nil, errcode.NewInvalidInputErr(errors.New("user cannot delete himself"))
	}
	// quer user status
	roleBindingUser, err := userSvc.GetRoleBindingUser(ctx, entity.RoleBindingScopeOrg, req.OrgId, req.UserId)
	if err != nil {
		return nil, err
	}

	err = userSvc.RemoveUserFromOrg(ctx, req.UserId, operator.ID.Uint64(), req.OrgId)
	tilog.Info(ctx, "RemoveUserFromOrg", zap.Any("success", err == nil))

	eventType := auditlog.EventDeleteInvitationToOrg
	if roleBindingUser.Status == entity.RoleBindingStatusActive {
		eventType = auditlog.EventDeleteUserFromOrg
	}

	details := auditlog.Details{
		auditlog.KeyUserEmail: roleBindingUser.Email,
		auditlog.KeyRole:      roleBindingUser.Role,
		auditlog.KeyError:     err,
	}
	if !config.IsLocalEnv() {
		auditlog.NewEvent(ctx, eventType).SetDetails(details.Build()).SetResult(err == nil).SetOrgID(req.OrgId).Submit() //nolint:errcheck
	}

	return &emptypb.Empty{}, err
}

func isActiveInOrg(ctx context.Context, userID entity.ID, orgID entity.ID) bool {
	svc, _ := rbac2.GetRbacRoleBindingSvc()
	filter := role.RoleBindingFilter{
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		TargetIDs:  []entity.ID{userID},
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg},
		ScopeIDs:   []entity.ID{orgID},
		Status:     []string{entity.RoleBindingStatusActive},
	}

	roleBindings, _ := svc.ListRoleBindings(ctx, filter)
	tilog.Info(ctx, "ListRoleBindings", zap.Any("roleBindings", roleBindings))
	if len(roleBindings) > 0 {
		tilog.Info(ctx, "user status in org", zap.Any("orgId", orgID.Uint64()), zap.Any("status", roleBindings[0].Status))
		return roleBindings[0].Status == entity.RoleBindingStatusActive

	}
	tilog.Info(ctx, "user status in org", zap.Any("orgId", orgID.Uint64()), zap.Any("status flag", false))
	return false
}
func insertUserAction(ctx context.Context, dbUserSvc dbuser.DBUserIface, userAction entity.CommandUserAction) {
	user, _ := usersvc.GetUserFromContext(ctx)
	if err := dbUserSvc.CreateCommandUserAction(ctx, user.ID.Uint64(), userAction.UserID.Uint64(), userAction.Email, userAction.OrgID.Uint64(), 0, entity.CommandStatusWaiting, userAction.Action, entity.RoleBindingTargetUser, entity.RoleBindingTargetUser); err != nil {
		tilog.Error(ctx, "insertUserAction", zap.Any("userAction", userAction), zap.Error(err))
		return
	}

	tilog.Info(ctx, "insertUserAction", zap.Any("userAction", userAction))
}

func (s *tenantService) CreateAuth0User(ctx context.Context, req *central.CreateAuth0UserReq) (*emptypb.Empty, error) {
	userContext := s.tenantModel.User(ctx, &usersvc.User{})
	orgID := entity.ID(req.OrgId)
	password := req.Password
	err := controllers.IsValidUserPassword(password)
	if err != nil {
		return nil, err
	}
	if err := userContext.CreateAuth0User(ctx, orgID, req.GetToken(), password); err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

// AcceptInvitation
//
//	@Description:  user accept Invitation
//	@Author wangletian 2023-11-29 10:36:27
//	@receiver s
//	@param ctx
//	@param req
//	@return *emptypb.Empty
//	@return error
func (s *tenantService) AcceptInvitation(ctx context.Context, req *central.AcceptInvitationReq) (*emptypb.Empty, error) {
	tilog.Info(ctx, "AcceptInvitation request", zap.Any("req", req))
	tenantID := entity.ID(req.OrgId)
	user := s.CurrentUser(ctx)
	email := strings.ToLower(req.GetEmail())
	auth0email := user.Email
	if auth0email != email {
		return nil, errcode.NewInvalidInputErr(errors.Errorf("the email address in the req %s and bearer token %s is inconsistent", email, auth0email))
	}

	userContext := s.tenantModel.User(
		ctx,
		&usersvc.User{
			TenantID: tenantID,
			User: entity.User{
				Email: email,
			},
		})

	firstName := req.FirstName
	lastName := req.LastName
	if firstName == "" && lastName == "" {
		tilog.Info(ctx, "AcceptInvitation firstname and lastname is blank, need to get name from auth0", zap.Any("email", email))
		tokenRaw, err := pkg_http.GetBearerToken(ctx)
		if err != nil {
			tilog.Warn(ctx, "AcceptInvitation firstname and lastname is blank, but get bearer token from header error, ignore to get name from auth0", zap.Any("email", email), zap.Any("err", err))
		} else {
			auth0User, err := userContext.GetUserBasicInfoFromAuth0(ctx, tokenRaw)
			if err != nil {
				tilog.Warn(ctx, "AcceptInvitation firstname and lastname is blank, GetUserBasicInfoFromAuth0 error", zap.Any("email", email), zap.Any("err", err))
			} else if auth0User != nil {
				firstName = auth0User.FirstName
				lastName = auth0User.LastName
				tilog.Info(ctx, "AcceptInvitation firstname and lastname is blank, get name from auth0 success", zap.Any("email", email), zap.Any("firstName", firstName), zap.Any("lastName", lastName))
			}
		}
	}

	userInviteToken, entityUser, err := userContext.AcceptInvitation(ctx, tenantID, req.GetEmail(), firstName, lastName)
	if err != nil {
		tilog.Warn(ctx, "failed to accept invitation", zap.String("feature", "invitation"), zap.String("email", email), zap.Error(err))
		return nil, err
	}
	// insert new login history
	insertHistory(ctx, entityUser, req.OrgId)
	tilog.Info(ctx, "AcceptInvitation ", zap.Any("user", user), zap.Any("user", user.ID.Uint64()))

	dbUserSvc, _ := dbuser.GetDBUserSvc()
	insertUserActionInInvite(ctx, dbUserSvc, email, tenantID, userInviteToken)

	tilog.Info(ctx, "accept invitation success", zap.String("feature", "invitation"), zap.String("email", email))
	return &emptypb.Empty{}, nil
}

func insertHistory(ctx context.Context, entityUser entity.User, orgId uint64) {
	tilog.Info(ctx, "after user accept invitation ,will insert user login history and visit history", zap.String("user", "entityUser"), zap.Any("orgId", orgId))
	loginHistorySvc, _ := historysvc.GetHistorySvc()

	visitHistory := entity.EventHistory{
		IdentityType:  entity.IdentityTypeUser,
		IdentityID:    entityUser.ID,
		IdentityValue: entityUser.Email,
		TargetType:    entity.HistoryTargetTypeOrg,
		TargetID:      entity.ID(orgId),
		Event:         entity.HistoryEventVisitOrg,
	}
	newCtx := context.Background()
	tilog.Info(ctx, "new goroutine  to insert user visit history", zap.Any("visitHistory", visitHistory))
	tilog.Info(newCtx, "new goroutine  to insert user visit history", zap.Any("visitHistory", visitHistory))
	go loginHistorySvc.CreateEventHistory(newCtx, &visitHistory) //nolint:errcheck

	loginHistory := entity.EventHistory{
		IdentityType:  entity.IdentityTypeUser,
		IdentityID:    entityUser.ID,
		IdentityValue: entityUser.Email,
		Event:         entity.HistoryEventLogin,
	}
	newCtx = context.Background()
	tilog.Info(ctx, "new goroutine  to insert user login history", zap.Any("visitHistory", visitHistory))
	tilog.Info(newCtx, "new goroutine  to insert user login history", zap.Any("visitHistory", visitHistory))
	go loginHistorySvc.CreateEventHistory(newCtx, &loginHistory) //nolint:errcheck
}

// CreateCommandUserAction
//
//	@Description:
//	@Author wangletian 2022-12-05 20:35:45
//	@param ctx
//	@param user
//	@param tenantID
//	@param userInviteToken
func insertUserActionInInvite(ctx context.Context, dbuserSvc dbuser.DBUserIface, email string, orgId entity.ID, userInviteToken entity.UserInviteToken) {
	userSvc, _ := usersvc.GetUserSvc()
	user, _ := userSvc.GetUserByEmail(ctx, email)
	if err := dbuserSvc.CreateCommandUserAction(ctx, userInviteToken.Operator.Uint64(), user.ID.Uint64(), email, orgId.Uint64(), 0, entity.CommandStatusWaiting, entity.CommandActionCreate, entity.RoleBindingTargetUser, entity.RoleBindingTargetUser); err != nil {
		tilog.Error(ctx, "CreateCommandUserAction fail", zap.Error(err))
	}

	tilog.Info(ctx, "this inInvite user not need  to create dbUser ", zap.Any("email", email))

}

func InsertUserActionFlag(ctx context.Context, orgId entity.ID, email *string, userId *entity.ID) (*entity.User, bool) {
	rbacSvc, _ := rbac2.GetRbacUserSvc()
	userSvc, _ := usersvc.GetUserSvc()
	var user *entity.User
	if email != nil {
		user, _ = userSvc.GetUserByEmail(ctx, *email)
	} else if userId != nil {
		user, _ = userSvc.GetUserByID(ctx, userId.Uint64())
	}

	userRoles, _ := rbacSvc.ListUserRoles(ctx,
		user.ID.Uint64(), []entity.ScopeType{entity.RoleBindingScopeOrg}, []entity.ID{orgId})
	flag := false

	for _, role := range userRoles {
		if role.RoleName == entity.RoleOrgOwner {
			tilog.Info(ctx, "this  user should be to create(delete) dbUser ", zap.Any("email", email), zap.Any("user.role", role.RoleName))
			flag = true
		}
	}
	if !flag {
		tilog.Info(ctx, "this  user ignore  to create(delete) dbUser ", zap.Any("email", email))

	}
	return user, flag
}

func (s *tenantService) GetUserProfile(ctx context.Context, req *central.OrgIdReq) (*central.UserProfile, error) {

	return controllerorg.GlobalOrgController.GetUserProfile(ctx, req)
}

func (s *tenantService) OpenOrgSso(ctx context.Context, req *central.OpenOrgSsoReq) (*emptypb.Empty, error) {

	orgController := controllerorg.GlobalOrgController

	return orgController.OpenOrgSso(ctx, req)
}

func (s *tenantService) GetExpWhiteList(ctx context.Context, req *central.GetExpWhiteListReq) (*central.GetExpWhiteListResp, error) {
	orgController := controllerorg.GlobalOrgController

	return orgController.GetExpWhiteList(ctx, req)
}

func (s *tenantService) UpdateUserProfile(ctx context.Context, req *central.UpdateUserProfileReq) (*emptypb.Empty, error) {
	firstName := s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.FirstName))
	lastName := s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.LastName))
	company := s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.Company))
	country := s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.Country))
	phoneNumber := s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.PhoneNumber))

	err := controllers.IsValidUsername(firstName, lastName)
	if err != nil {
		return &emptypb.Empty{}, errcode.NewInvalidInputErr(err)
	}
	user := s.CurrentUser(ctx)
	updateUserProfileReq := tenant2.UpdateUserProfileReq{
		Email:       user.Email,
		FirstName:   firstName,
		LastName:    lastName,
		Company:     company,
		Country:     country,
		PhoneNumber: phoneNumber,
	}
	err = s.userContext(ctx).UpdateUserProfile(entity.ID(req.OrgId), updateUserProfileReq)
	details := auditlog.Details{
		auditlog.KeyOldFirstName:   user.FirstName,
		auditlog.KeyNewFirstName:   firstName,
		auditlog.KeyOldLastName:    user.LastName,
		auditlog.KeyNewLastName:    lastName,
		auditlog.KeyOldPhoneNumber: user.PhoneNumber,
		auditlog.KeyNewPhoneNumber: phoneNumber,
		auditlog.KeyError:          err,
	}
	auditlog.NewEvent(ctx, auditlog.EventUpdateProfile).SetResult(err == nil).SetDetails(details.Build()).Submit() //nolint:errcheck
	if err != nil {
		return &emptypb.Empty{}, err
	}
	return &emptypb.Empty{}, nil
}

func (s *tenantService) GetClientIP(ctx context.Context, _ *emptypb.Empty) (*central.GetClientIPResp, error) {
	ip, err := auditlog3.GetClientIP(ctx)
	if err != nil {
		return nil, errcode.NewInternalErr(errors.New("failed to get client IP address"))
	}

	return &central.GetClientIPResp{
		Ip: ip,
	}, nil
}

func (s *tenantService) ListTenantAccessLogs(c context.Context, req *central.ListAccessLogReq) (*central.ListAccessLogResp, error) {
	ret, err := authn.ListAccessLogs(s.userContext(c).ListAccessLog(&auditlog3.ListAccessLogQuery{
		StartTimeInSec: req.StartTime,
		EndTimeInSec:   req.EndTime,
		User:           req.User,
		Content:        req.Content,
		From:           utils.GetStartIndex(int(req.Page), int(req.PerPage)),
		Size:           utils.GetSize(int(req.PerPage)),
	}))
	if err != nil {
		return ret, errcode.NewInternalErr(errors.New("failed to list tenant access logs"))
	}
	return ret, nil
}

func (s *tenantService) ListRolesByUserID(ctx context.Context, req *central.ListRolesByUserIDReq) (*central.ListRolesByUserIDRsp, error) {
	if req == nil || req.UserId == 0 {
		return nil, errcode.NewInvalidInputErr(errors.New("user_id invalid"))
	}
	var filterScopeTypes []entity.ScopeType
	if len(req.ScopeTypes) > 0 {
		for _, st := range req.ScopeTypes {
			scopeType := entity.ParseScopeType(st)
			if scopeType == entity.RoleBindingScopeInvalid {
				return nil, errcode.NewInvalidInputErr(errors.New("scope_types invalid"))
			}
			filterScopeTypes = append(filterScopeTypes, scopeType)
		}
	}
	srv, err := rbac2.GetRbacUserSvc()
	if err != nil {
		tilog.Error(ctx, "failed to GetRbacUserSvc",
			zap.Error(err))
		return nil, errcode.NewInternalErr(errors.New("fail to ListRolesByUserID"))
	}
	userRoles, err := srv.ListUserRoles(ctx, req.UserId, filterScopeTypes, []entity.ID{})
	if err != nil {
		tilog.Error(ctx, "failed to list roles by user_id", zap.String("scope_id", fmt.Sprintf("%d", req.UserId)), zap.Error(err))
		return nil, errcode.NewInternalErr(errors.New("fail to list roles by user_id"))
	}
	result := &central.ListRolesByUserIDRsp{
		Total: int32(len(userRoles)),
		Items: userRoles,
	}
	return result, nil
}

func (s *tenantService) ListLoginUserRoles(ctx context.Context, req *central.ListLoginUserRolesReq) (*central.ListLoginUserRolesRsp, error) {
	user := s.CurrentUser(ctx)
	if user == nil {
		return nil, errcode.NewForbiddenErr(errors.New("not found login user"))
	}
	lreq := &central.ListRolesByUserIDReq{
		UserId:     user.ID.Uint64(),
		ScopeTypes: req.ScopeTypes,
	}
	lrsp, err := s.ListRolesByUserID(ctx, lreq)
	if err != nil {
		return nil, err
	}
	rsp := &central.ListLoginUserRolesRsp{
		Total: lrsp.Total,
		Items: lrsp.Items,
	}
	return rsp, nil
}

func (s *tenantService) ListOrgActiveAuthMethods(ctx context.Context, req *central.ListOrgActiveAuthMethodsReq) (*central.ListOrgActiveAuthMethodsRsp, error) {
	if req.CompanyName == "" && req.OrgId == "" {
		return nil, utils.ConvertErr2GrpcErr(errs.NewBadRequest("param invalid", nil))
	}
	svc, _ := orgsso2.GetOrgSsoSvc()
	orgMethodsInfo, err := svc.GetOrgActiveAuthMethods(ctx, req)
	if err != nil {
		return nil, err
	}
	if orgMethodsInfo == nil {
		return &central.ListOrgActiveAuthMethodsRsp{}, nil
	}
	var authMethods []*central.OrgAuthMethod
	var emailAuthMethod *central.OrgAuthMethod = nil
	for _, method := range orgMethodsInfo.AuthMethods {
		authMethod := central.OrgAuthMethod{
			Type:        method.Type,
			Name:        method.Name,
			DisplayName: method.DisplayName,
		}
		if method.Type == string(entity.AuthMethodTypePassword) && method.Name == string(entity.AuthMethodNameEmail) {
			emailAuthMethod = &authMethod
			continue
		}
		authMethods = append(authMethods, &authMethod)
	}
	// put email auth method to the last
	if emailAuthMethod != nil {
		authMethods = append(authMethods, emailAuthMethod)
	}
	rsp := central.ListOrgActiveAuthMethodsRsp{
		AuthMethods: authMethods,
		OrgId:       orgMethodsInfo.OrgID,
		OrgName:     orgMethodsInfo.OrgName,
	}
	return &rsp, nil
}

func (s *tenantService) GetOrgByCompanyName(ctx context.Context, req *central.GetOrgByCompanyNameReq) (*central.GetOrgByCompanyNameRsp, error) {
	//if req.CompanyName == "" {
	//	return nil, utils.ConvertErr2GrpcErr(errs.NewBadRequest("param invalid", nil))
	//}
	//organization, err := s.userContext(ctx).GetOrganizationByCustomName(ctx, req.CompanyName)
	//if err != nil {
	//	return nil, err
	//}
	//
	//output, _ := s.organizationOutput(organization)
	//return &central.GetOrgByCompanyNameRsp{
	//	Org: output,
	//}, nil

	// 20260429 Security feedback: This interface lacks authentication. The frontend has confirmed that this API is no longer being called and has been temporarily removed.
	return nil, errs.NewBadRequest("The interface has been taken offline.", nil)

}

func (s *tenantService) OrgBindThirdParty(ctx context.Context, req *central.OrgBindThirdPartyReq) (*emptypb.Empty, error) {

	err := s.userContext(ctx).OrgBindThirdParty(ctx, &tenant2.OrgBindThirdPartyReq{
		OrgID:                     entity.ID(req.OrgId),
		ThirdPartyAccountProvider: req.ThirdPartyAccountProvider,
		ThirdPartyAccountToken:    req.ThirdPartyAccountToken,
		ThirdPartyFreeTrial:       req.ThirdPartyFreeTrial,
	})
	return &emptypb.Empty{}, err
}
func (s *tenantService) ValidateThirdPartyToken(ctx context.Context, req *central.ValidateThirdPartyTokenReq) (*central.ValidateThirdPartyTokenResponse, error) {
	res, err := tenant2.ValidateThirdPartyToken(ctx, &tenant2.ValidateThirdPartyTokenReq{
		ThirdPartyAccountProvider: req.ThirdPartyAccountProvider,
		ThirdPartyAccountToken:    req.ThirdPartyAccountToken,
	})
	if err != nil {
		return nil, err
	}
	return &central.ValidateThirdPartyTokenResponse{
		ThirdPartyAccountProvider:          req.ThirdPartyAccountProvider,
		ThirdPartyAccountCode:              res.ThirdPartyAccountCode,
		ThirdPartyAccountCodeAlreadyBinded: res.ThirdPartyAccountCodeAlreadyBinded,
	}, nil
}

func convertEntityUser2ProtoUser(users []rbac2.RbacUser) []*central.User {
	retUsers := make([]*central.User, 0)
	for _, rbacUser := range users {
		localUser := rbacUser.User
		var name = ""
		if localUser.FirstName != "" && localUser.LastName != "" {
			name = entity.FormatUserName(localUser.FirstName, localUser.LastName)
		}
		retUser := &central.User{
			Id:       localUser.ID.Uint64(),
			Email:    localUser.Email,
			Name:     name,
			Status:   string(localUser.Status),
			RbacRole: rbacUser.RbacRole,
		}
		retUsers = append(retUsers, retUser)
	}
	return retUsers
}

func (s *tenantService) ListUsersByScope(ctx context.Context, req *central.ListUsersByScopeReq) (*central.Users, error) {
	if req == nil || req.ScopeId == 0 || req.ScopeType == "" {
		return nil, errcode.NewInvalidInputErr(errors.New("param invalid"))
	}
	srv, err := rbac2.GetRbacUserSvc()
	if err != nil {
		tilog.Error(ctx, "failed to GetRbacUserSvc",
			zap.Error(err))
		return nil, errcode.NewInternalErr(errors.New("fail to list users by scope"))
	}
	scopeType := entity.ParseScopeType(strings.ToUpper(req.ScopeType))
	if scopeType == entity.RoleBindingScopeInvalid {
		return nil, errcode.NewInvalidInputErr(errors.Errorf("scope_type '%s' invalid", req.ScopeType))
	}
	users, err := srv.ListUsersByScope(ctx, scopeType, req.ScopeId)
	if err != nil {
		tilog.Error(ctx, "failed to list users by scope",
			zap.String("scope_id", fmt.Sprintf("%d", req.ScopeId)), zap.String("scope_type", req.ScopeType), zap.Error(err))
		return nil, errcode.NewInternalErr(errors.New("fail to list users by scope"))
	}
	retUsers := convertEntityUser2ProtoUser(users)
	result := &central.Users{
		Total: int32(len(retUsers)),
		Items: retUsers,
	}
	return result, nil
}

func (s *tenantService) GetMspConfig(ctx context.Context, req *central.GetMspConfigReq) (*central.GetMspConfigResp, error) {

	orgController := controllerorg.GlobalOrgController

	return orgController.GetMspConfig(ctx, req)
}

func (s *tenantService) GetMspCustomer(ctx context.Context, req *central.GetMspCustomerReq) (*central.GetMspCustomerResp, error) {
	orgController := controllerorg.GlobalOrgController

	return orgController.GetMspCustomer(ctx, req)
}
func (s *tenantService) GetPartnerByCustomerId(ctx context.Context, req *central.GetPartnerByCustomerIdReq) (*central.GetPartnerByCustomerIdResp, error) {
	orgController := controllerorg.GlobalOrgController

	return orgController.GetPartnerByCustomerId(ctx, req)
}

// ConfigFeatureFlagForProviderSource Temporary interface for alicoud provider source to config feature flag
func (s *tenantService) ConfigFeatureFlagForProviderSource(ctx context.Context, req *central.ConfigFeatureFlagForProviderSourceReq) (*emptypb.Empty, error) {
	tilog.Info(ctx, "ConfigFeatureFlagForProviderSource request", zap.Any("req", req))
	if req == nil || req.OrgId == 0 || req.ProviderSource == "" {
		return nil, errs.NewBadRequest("param invalid", nil)
	}
	svc, _ := orgsvc.GetOrgSvc()
	dbOrg, err := svc.GetOrgByID(ctx, req.OrgId)
	if err != nil {
		tilog.Error(ctx, "ConfigFeatureFlagForProviderSource, failed to get org", zap.String("org_id", "params.OrgID"), zap.Error(err))
		return nil, err
	}
	if dbOrg.ThirdPartyAccountCode != nil && *dbOrg.ThirdPartyAccountCode != "" {
		tilog.Info(ctx, "ConfigFeatureFlagForProviderSource, org already exists third party account", zap.Uint("orgID", uint(req.OrgId)), zap.Any("third_party_account_provider", dbOrg.ThirdPartyAccountProvider), zap.Any("third_party_account_code", dbOrg.ThirdPartyAccountCode))
		return nil, accountErr.NewErrDuplicatedThirdPartyAccountError("this org has already bind marketplace account", nil)
	}

	err = publish.PublishOrgRegistrationProviderSourceEvent(ctx, req.OrgId, req.ProviderSource)

	return &emptypb.Empty{}, err
}
