package user

import (
	"context"
	"fmt"
	"strconv"

	historysvc "github.com/tidbcloud/account/internel/svc/history"
	orgsvc "github.com/tidbcloud/account/internel/svc/org"
	projectsvc "github.com/tidbcloud/account/internel/svc/project"
	"github.com/tidbcloud/account/internel/svc/rbac"
	"github.com/tidbcloud/account/internel/svc/survey"
	usersvc "github.com/tidbcloud/account/internel/svc/user"
	"github.com/tidbcloud/account/internel/svc/user/session"

	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/tiinf/sdk/auditlog"

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

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

	org_dao "github.com/tidbcloud/account/internel/dao/org"

	"github.com/duke-git/lancet/v2/slice"
	"github.com/spf13/viper"
	"github.com/tidbcloud/account/idl/pbgen/proto/central"
	"github.com/tidbcloud/account/internel/dao/history"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/account/internel/dao/entity"
	"google.golang.org/protobuf/types/known/emptypb"
	"google.golang.org/protobuf/types/known/timestamppb"
)

var (
	clusterModeNum2Str = map[uint64]string{
		1: "classic",
		2: "new-offerings",
		3: "new-offerings-preview",
	}
	clusterModeStr2Num = map[string]uint64{
		"classic":               1,
		"new-offerings":         2,
		"new-offerings-preview": 3,
	}
)

type UserService struct {
	central.UnimplementedUserServiceServer
}

func NewUserService() central.UserServiceServer {
	return &UserService{}
}

func (s *UserService) GetUserUIPrivileges(ctx context.Context, req *emptypb.Empty) (*central.GetUserUIPrivilegesResp, error) {
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return nil, err
	}
	svc, _ := rbac.GetRbacUserSvc()
	systems := viper.GetStringSlice("rbac.systems")
	if config.IsLocalEnv() {
		systems = []string{"cp-dbaas-ui"}
	}
	tilog.Info(ctx, "get value from viper", zap.Any("rbac.systems", systems))
	roleBindings, roleRrivileges, err := svc.GetUserRolesAndPrivileges(ctx, user.ID.Uint64(), systems)
	var rbacInfos []*central.RbacInfo
	for _, b := range roleBindings {

		rbacInfo := &central.RbacInfo{
			ScopeType: string(b.ScopeType),
			ScopeId:   b.ScopeID.Uint64(),
			RoleId:    b.RoleID.Uint64(),
			RoleName:  b.Role,
		}
		DbPrivileges := roleRrivileges[b.RoleID]
		var accessArr []*central.Access
		for _, s := range systems {
			// find system=
			ps := slice.Filter(DbPrivileges, func(i int, e entity.RbacPrivilege) bool {
				return s == e.System
			})
			if len(ps) > 0 {
				var privileges []string
				for _, p := range ps {
					privileges = append(privileges, p.ResourceUri)
				}
				access := &central.Access{System: s, Privileges: privileges}
				accessArr = append(accessArr, access)
			}
		}

		rbacInfo.Access = accessArr
		rbacInfos = append(rbacInfos, rbacInfo)
	}
	resp := &central.GetUserUIPrivilegesResp{RbacInfo: rbacInfos}
	return resp, err
}

// CreateUserVisitHistory
//
//	@Description:  UserVisitHistory:visit_project,visit_org
//	@Author wangletian 2023-09-19 14:43:06
//	@receiver s
//	@param ctx
//	@param req
//	@return *emptypb.Empty
//	@return error
func (s *UserService) CreateUserVisitHistory(ctx context.Context, req *central.CreateUserVisitHistoryReq) (*emptypb.Empty, error) {
	tilog.Info(ctx, "CreateUserVisitHistory", zap.Any("req", req))
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return &emptypb.Empty{}, err
	}
	if user.IsImpersonated {
		return &emptypb.Empty{}, nil
	}
	if err := checkParam(ctx, user.ID.Uint64(), req.OrgId, req.ProjectId); err != nil {
		return &emptypb.Empty{}, err
	}
	details := auditlog.Details{
		auditlog.KeyUserEmail: user.Email,
		auditlog.KeyError:     nil,
	}

	visitHistory, err := s.GetUserLastVisitHistory(ctx, nil)
	if err == nil && visitHistory != nil && visitHistory.OrgId != 0 && req.OrgId != 0 && visitHistory.OrgId != req.OrgId {
		// it means user switch org
		// add logout audit log
		aerr := auditlog.NewEvent(ctx, auditlog.EventLogoutOrg).SetOperatorType(auditlog.OperatorTypeUser).
			SetOperatorEmail(user.Email).SetOperatorID(user.ID.Uint64()).SetDetails(details.Build()).
			SetOrgID(visitHistory.OrgId).SetResult(true).Submit()
		tilog.Info(ctx, "CreateUserVisitHistory create LogoutOrg auditlog result", zap.Any("email", user.Email), zap.Any("org_id", visitHistory.OrgId), zap.Any("err", aerr))
		// add login audit log
		aerr = auditlog.NewEvent(ctx, auditlog.EventLoginOrg).SetOperatorType(auditlog.OperatorTypeUser).
			SetOperatorEmail(user.Email).SetOperatorID(user.ID.Uint64()).SetDetails(details.Build()).
			SetOrgID(req.OrgId).SetResult(true).Submit()
		tilog.Info(ctx, "CreateUserVisitHistory create LoginOrg auditlog result", zap.Any("email", user.Email), zap.Any("org_id", req.OrgId), zap.Any("err", aerr))
	}

	loginHistorySvc, _ := historysvc.GetHistorySvc()

	if req.OrgId != 0 {
		history := entity.EventHistory{
			IdentityType:  entity.IdentityTypeUser,
			IdentityID:    user.ID,
			IdentityValue: user.Email,
			TargetType:    entity.HistoryTargetTypeOrg,
			TargetID:      entity.ID(req.OrgId),
			Event:         entity.HistoryEventVisitOrg,
		}
		err := loginHistorySvc.CreateEventHistory(ctx, &history)
		if err != nil {
			return &emptypb.Empty{}, err
		}
	}
	projectHistory := entity.EventHistory{
		IdentityType:  entity.IdentityTypeUser,
		IdentityID:    user.ID,
		IdentityValue: user.Email,
		TargetType:    entity.HistoryTargetTypeProject,
		Event:         entity.HistoryEventVisitProject,
	}

	if req.ProjectId != 0 {
		projectHistory.TargetID = entity.ID(req.ProjectId)
	} else {
		projectSvc, _ := projectsvc.GetProjectSvc()
		projects, err := projectSvc.ListUserVisibleProjects(ctx, user.ID.Uint64(), []uint64{req.OrgId})
		if err != nil {
			return nil, err
		}
		if len(projects) > 0 {
			projectHistory.TargetID = projects[0].ID
			tilog.Info(ctx, "param projectId is nil ,get the first project  ", zap.Any("project id", projectHistory.TargetID))
		}
	}
	tilog.Info(ctx, "CreateUserVisitHistory set projectId ", zap.Any("projectHistory.TargetID", projectHistory.TargetID))
	if projectHistory.TargetID != 0 {
		err = loginHistorySvc.CreateEventHistory(ctx, &projectHistory)
		if err != nil {
			return &emptypb.Empty{}, err
		}
	}

	if req.ClusterMode != "" {
		modeInt, ok := clusterModeStr2Num[req.ClusterMode]
		if !ok {
			tilog.Warn(ctx, fmt.Sprintf("unknown cluster mode: %s", req.ClusterMode))
		} else {
			err = loginHistorySvc.CreateEventHistory(ctx, &entity.EventHistory{
				IdentityType:  entity.IdentityTypeUser,
				IdentityID:    user.ID,
				IdentityValue: user.Email,
				TargetType:    entity.HistoryTargetTypeClusterMode,
				TargetID:      entity.ID(modeInt),
				Event:         entity.HistoryEventVisitClusterMode,
			})
			if err != nil {
				return &emptypb.Empty{}, err
			}
		}
	}

	return &emptypb.Empty{}, err
}

func checkParam(ctx context.Context, userId, orgId, projectId uint64) error {
	msg := "org_id or project_id is invalid"
	projectSvc, _ := projectsvc.GetProjectSvc()
	roleBindingSvc, _ := rbac.GetRbacRoleBindingSvc()
	if orgId != 0 && projectId != 0 {
		// check user in historyOrgId
		orgIds, err := roleBindingSvc.GetUserOrgIds(ctx, userId)
		if err != nil {
			return err
		}
		if slice.Contain(orgIds, orgId) {
			//   user not in historyOrgId,find the first org
			projectIds, err := projectSvc.ListUserVisibleProjectIds(ctx, userId, []uint64{orgId})
			if err != nil {
				return err
			}
			if slice.Contain(projectIds, projectId) {
				return nil
			}
		}
		tilog.Info(ctx, "CreateUserVisitHistory org_id  is invalid ", zap.Any("orgId", orgId))

	} else if orgId != 0 && projectId == 0 {
		// check orgId
		orgIds, err := roleBindingSvc.GetUserOrgIds(ctx, userId)
		if err != nil {
			return err
		}
		if slice.Contain(orgIds, orgId) {
			return nil
		}
		tilog.Info(ctx, "CreateUserVisitHistory project_id  is invalid ", zap.Any("project_id", projectId))
	}

	return errs.NewBadRequest(msg, nil)
}

// GetUserLastVisitHistory
// get user last visit org or project
//
//	1、is impersonatedUser:return impersonated org and the first project
//	2、if last visit record exist,query table ( event_history_last)
//	3、if last visit record not exist, return first(random) org and project
//
// @Description:
// @Author wangletian 2023-09-12 17:02:42
// @receiver s
// @param ctx
// @param req
// @return *central.GetUserLastVisitHistoryResp
// @return error
func (s *UserService) GetUserLastVisitHistory(ctx context.Context, req *emptypb.Empty) (*central.GetUserLastVisitHistoryResp, error) {
	orgSvc, _ := orgsvc.GetOrgSvc()
	projectSvc, _ := projectsvc.GetProjectSvc()
	roleBindingSvc, _ := rbac.GetRbacRoleBindingSvc()
	loginHistorySvc, _ := historysvc.GetHistorySvc()
	resp := &central.GetUserLastVisitHistoryResp{}

	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return nil, err
	}

	// impersonated user
	if user.IsImpersonated {
		return getImpersonatedHistory(ctx, user.ID.Uint64())
	}

	// not impersonated
	filter := history.EventHistoryFilter{
		IdentityIDs:   []entity.ID{user.ID},
		IdentityTypes: []entity.HistoryIdentityType{entity.IdentityTypeUser},
		Events:        []entity.HistoryEvent{entity.HistoryEventVisitOrg, entity.HistoryEventVisitProject, entity.HistoryEventVisitClusterMode},
	}

	histories, err := loginHistorySvc.ListEventHistoryLast(ctx, filter)
	if err != nil {
		return nil, err
	}

	var historyOrgId uint64
	var historyProjectId uint64
	var historyClusterModeInt uint64
	for _, h := range histories {
		if h.TargetType == entity.HistoryTargetTypeOrg {
			historyOrgId = h.TargetID.Uint64()
		}
		if h.TargetType == entity.HistoryTargetTypeProject {
			historyProjectId = h.TargetID.Uint64()
		}
		if h.TargetType == entity.HistoryTargetTypeClusterMode {
			historyClusterModeInt = h.TargetID.Uint64()
		}
	}

	clusterModeStr := clusterModeNum2Str[historyClusterModeInt]
	resp.ClusterMode = clusterModeStr

	// check user in historyOrgId
	orgIds, err := roleBindingSvc.GetUserOrgIds(ctx, user.ID.Uint64())
	if err != nil {
		return nil, err
	}
	orgFilter := org_dao.ListOrgsFilter{
		ID: orgIds,
	}
	orgs, err := orgSvc.ListOrgsByFilter(ctx, orgFilter)

	if err != nil {
		return nil, err
	}
	// query user's person orgIds  and enterpriseOrgIds
	var personOrgIds []uint64
	var enterpriseOrgIds []uint64
	for _, org := range orgs {
		if org.OrgType == entity.OrgTypeEnterprise {
			enterpriseOrgIds = append(enterpriseOrgIds, org.ID.Uint64())
		} else {
			personOrgIds = append(personOrgIds, org.ID.Uint64())
		}
	}
	loginOrgType := ctx.Value(entity.ClaimsOrgTypeKey)
	claimsOrgId := ctx.Value(entity.ClaimsOrgIDKey).(string)
	orgId, err := strconv.ParseUint(claimsOrgId, 10, 64)
	if err != nil {
		return nil, err
	}

	//loginOrgType := OrgType.(string)
	tilog.Info(ctx, "get user loginOrgType ", zap.Any("loginOrgType", loginOrgType), zap.Any("ClaimsOrgId", orgId))
	if loginOrgType == string(entity.OrgTypeEnterprise) {
		resp.OrgId = orgId
	} else if loginOrgType != string(entity.OrgTypeEnterprise) && slice.Contain(personOrgIds, historyOrgId) {
		resp.OrgId = historyOrgId

		//   user not in historyOrgId,find the first org
	} else if loginOrgType != string(entity.OrgTypeEnterprise) && len(personOrgIds) > 0 {
		resp.OrgId = personOrgIds[0]
	} else if loginOrgType == string(entity.OrgTypeEnterprise) && len(enterpriseOrgIds) > 0 {
		resp.OrgId = enterpriseOrgIds[0]
	}

	// check user in historyProjectId

	// query user's projectId in org
	projectIds, err := projectSvc.ListUserVisibleProjectIds(ctx, user.ID.Uint64(), []uint64{resp.OrgId})
	if err != nil {
		return nil, err
	}
	// user in the historyProjectId now
	if slice.Contain(projectIds, historyProjectId) {
		resp.ProjectId = historyProjectId
	} else if len(projectIds) > 0 {
		resp.ProjectId = projectIds[0]
	}

	tilog.Info(ctx, "GetUserLastVisitHistory ", zap.Any("user.id", user.ID), zap.Any("user.email", user.Email), zap.Any("impersonated", user.IsImpersonated), zap.Any("resp", resp))
	return resp, nil
}

func getImpersonatedHistory(ctx context.Context, userId uint64) (*central.GetUserLastVisitHistoryResp, error) {
	userSvc, _ := usersvc.GetUserSvc()
	projectSvc, _ := projectsvc.GetProjectSvc()
	resp := &central.GetUserLastVisitHistoryResp{}
	impersonatedOrg, _, err := userSvc.GetUserImpersonatedOrg(ctx, userId)
	if err != nil {
		tilog.Error(ctx, "AddImpersonateUser GetImpersonatedUser error", zap.Any("err", err))

		return nil, err
	}
	resp.OrgId = impersonatedOrg.ID.Uint64()
	tilog.Info(ctx, "user has impersonate org", zap.Any("impersonatedOrg", impersonatedOrg.ID))
	// query all projects ,find the first project
	projects, err := projectSvc.ListActiveProjectsByOrgID(ctx, impersonatedOrg.ID.Uint64())
	if err != nil {
		return nil, err
	}
	if len(projects) > 0 {
		resp.ProjectId = projects[0].ID.Uint64()

	}
	return resp, nil
}

// GetUserProfileV2
//
//	@Description:query user's  orgs and projects
//	@Author wangletian 2023-09-13 14:34:39
//	@receiver s
//	@param ctx
//	@param req
//	@return *central.GetUserProfileResp
//	@return error
func (s *UserService) GetUserProfileV2(ctx context.Context, req *emptypb.Empty) (*central.GetUserProfileResp, error) {
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return nil, err
	}
	userSvc, _ := usersvc.GetUserSvc()
	orgSvc, _ := orgsvc.GetOrgSvc()
	projectSvc, _ := projectsvc.GetProjectSvc()

	var respOrgs []*central.UserProfileOrg
	var respProjects []*central.UserProfileProject
	var projects []entity.Project
	var orgIds []uint64

	// impersonated
	if user.IsImpersonated {
		impersonatedOrg, _, err := userSvc.GetUserImpersonatedOrg(ctx, user.ID.Uint64())
		if err != nil {
			tilog.Error(ctx, "AddImpersonateUser GetImpersonatedUser error", zap.Any("err", err))

			return nil, err
		}
		tilog.Info(ctx, "user has impersonate org", zap.Any("impersonatedOrg", impersonatedOrg.ID))
		userProfileOrg := OrgToUserProfileOrg(impersonatedOrg)
		respOrgs = append(respOrgs, userProfileOrg)
		projects, err = projectSvc.ListActiveProjectsByOrgID(ctx, impersonatedOrg.ID.Uint64())
		if err != nil {
			return nil, err
		}
	} else {
		// not impersonated
		tenants, err := orgSvc.ListOrgsByUserID(ctx, user.ID.Uint64())
		if err != nil {
			return nil, err
		}
		for _, org := range tenants {
			// tidb cloud console filter parter org
			if slice.Contain(orgsvc.PartnerOrgTags, org.OrgTag) {
				continue
			}
			userProfileOrg := OrgToUserProfileOrg(&org)
			respOrgs = append(respOrgs, userProfileOrg)
			orgIds = append(orgIds, org.ID.Uint64())
		}
		if len(orgIds) > 0 {
			// projects
			projects, err = projectSvc.ListUserVisibleProjects(ctx, user.ID.Uint64(), orgIds)
			if err != nil {
				return nil, err
			}
		}
	}

	for _, p := range projects {
		userProfileProject := &central.UserProfileProject{
			Id:             p.ID.Uint64(),
			Name:           p.Name,
			OrgId:          p.TenantID.Uint64(),
			IsCrossAccount: p.IsCrossAccount,
			AwsCmekEnabled: p.AwsCmekEnabled,
			Type:           p.Type,
		}
		respProjects = append(respProjects, userProfileProject)

	}
	userId := user.ID.Uint64()
	isNewUser, err := userSvc.IsNewUser(ctx, &userId)

	if err != nil {
		return nil, err
	}

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

		}
		landingActions, err := userSvc.ListUserLandingActions(ctx, userDao.LandingActionFilter{
			UserIDs:       []entity.ID{user.ID},
			TargetActions: []entity.UserTargetAction{entity.DefaultClusterInOrgAction, entity.DefaultClusterInProjectAction},
		})
		if err != nil {
			tilog.Error(ctx, "GetNewUserDefaultClusterInProjectAndOrg error", zap.Any("userID", user.ID), zap.Any("err", err))
		}
		for _, landingA := range landingActions {
			if landingA.TargetAction == entity.DefaultClusterInOrgAction {
				defaultClusterInOrgId = landingA.TargetID.Uint64()
			}
			if landingA.TargetAction == entity.DefaultClusterInProjectAction {
				defaultClusterInProjectId = landingA.TargetID.Uint64()
			}
		}
	}
	resp := &central.GetUserProfileResp{
		Orgs:             respOrgs,
		Projects:         respProjects,
		UserCreatedAt:    utils.ToTimestamp(user.CreatedAt),
		UserId:           user.ID.Uint64(),
		Email:            user.Email,
		FirstName:        user.FirstName,
		LastName:         user.LastName,
		PhoneNumber:      user.PhoneNumber,
		IsNewUser:        isNewUser,
		DefaultClusterId: defaultClusterId,
		DefaultClusterInfo: &central.ClusterInfo{
			ClusterId: defaultClusterId,
			ProjectId: defaultClusterInProjectId,
			OrgId:     defaultClusterInOrgId,
		},
		IsImpersonatedUser: user.IsImpersonated}
	return resp, nil

}

func OrgToUserProfileOrg(org *entity.Tenant) *central.UserProfileOrg {
	userProfileOrg := &central.UserProfileOrg{
		Id:              org.ID.Uint64(),
		Name:            org.Name,
		OrgType:         string(org.OrgType),
		OrgTag:          org.OrgTag,
		CompanyName:     org.CompanyName,
		Country:         org.Country,
		UserSetTimezone: org.TimeZone,
		CreatedAt:       timestamppb.New(org.CreatedAt),
		Email:           org.Email,
	}
	timezone := 0
	userSetTimeZone := "+00:00"
	if org.TimeZone != "" {
		timezoneStr := utils.ConvTimezone2OldTimezone(org.TimeZone)
		tz, err := strconv.Atoi(timezoneStr)
		if err != nil {
			tilog.PlainError("invalid timezone value", zap.Error(err))
		} else {
			timezone = tz
		}
		userSetTimeZone = utils.ConvTimezone2NewTimezone(org.TimeZone)
	}
	userProfileOrg.Timezone = int32(timezone)
	userProfileOrg.UserSetTimezone = userSetTimeZone

	if org.ThirdPartyAccountProvider != nil {
		userProfileOrg.ThirdPartyAccountProvider = *org.ThirdPartyAccountProvider
	}
	if org.ThirdPartyAccountCode != nil {
		userProfileOrg.ThirdPartyAccountCode = *org.ThirdPartyAccountCode
	}
	return userProfileOrg
}

func (s *UserService) GetUserOrgsProjects(ctx context.Context, req *emptypb.Empty) (*central.GetUserOrgsProjectsResp, error) {

	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return nil, err
	}
	projectSvc, _ := projectsvc.GetProjectSvc()
	respOrgs, respProjects, err := projectSvc.GetUserOrgsProjects(ctx, user.ID.Uint64())
	if err != nil {
		return nil, err
	}
	resp := &central.GetUserOrgsProjectsResp{
		Username: user.FirstName,
		Orgs:     respOrgs,
		Projects: respProjects,
	}

	return resp, nil

}

/*
func (s *UserService) GetUserOrgsProjects(ctx context.Context, req *emptypb.Empty) (*central.GetUserOrgsProjectsResp, error) {

	user, err := authuser.GetUserFromContext(ctx)
	if err != nil {
		return nil, err
	}

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

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

	//
	tenants, err := orgSvc.ListOrgsByUserID(ctx, user.ID.Uint64())
	if err != nil {
		return nil, err
	}
	//  find all  !=enterprise  org  id
	for _, org := range tenants {
		if org.OrgType != entity.OrgTypeEnterprise {
			orgIds = append(orgIds, org.ID.Uint64())
			orgIDs = append(orgIDs, org.ID)
		}
	}
	// quer projects int the  org
	projects, err = projectSvc.ListUserVisibleProjects(ctx, user.ID.Uint64(), orgIds)
	if err != nil {
		return nil, err
	}
	var pIDs []entity.ID
	for _, p := range projects {
		pIDs = append(pIDs, p.ID)
	}

	// query  org role  and project role
	roleBindingSvc, _ := deps.GetRbacRoleBindingSvc()
	queryScopeIDs := append(orgIDs, pIDs...)
	filter := role.RoleBindingFilter{
		TargetType: []entity.TargetType{entity.RoleBindingTargetUser},
		TargetIDs:  []entity.ID{user.ID},
		ScopeTypes: []entity.ScopeType{entity.RoleBindingScopeOrg, entity.RoleBindingScopeProject},
		ScopeIDs:   queryScopeIDs,
		Status:     []string{entity.RoleBindingStatusActive},
	}
	roleBindings, err := roleBindingSvc.ListRoleBindings(ctx, filter)
	if err != nil {
		tilog.Error(ctx, "accept invitation ,fail ListRoleBindings ", zap.Error(err))
		return nil, err
	}
	// find the user' role in the project
	orgRoleMap := make(map[uint64]string)
	projectRoleMap := make(map[uint64]string)
	for _, r := range roleBindings {
		if r.ScopeType == entity.RoleBindingScopeOrg {
			orgRoleMap[r.ScopeID.Uint64()] = r.Role
		} else if r.ScopeType == entity.RoleBindingScopeProject {
			projectRoleMap[r.ScopeID.Uint64()] = r.Role
		}
	}
	//
	for _, org := range tenants {
		if org.OrgType != entity.OrgTypeEnterprise {
			orgInfo := &central.OrgInfo{
				Id:      org.ID.Uint64(),
				Name:    org.Name,
				OrgRole: orgRoleMap[org.ID.Uint64()],
			}
			respOrgs = append(respOrgs, orgInfo)

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

	}

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

	return resp, nil

}
*/
func (s *UserService) GetUserLoginExtInfo(ctx context.Context, req *central.GetUserLoginExtInfoReq) (*central.GetUserLoginExtInfoResp, error) {
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		tilog.Error(ctx, "GetUserLoginExtInfo GetUserFromContext error", zap.Any("err", err))
		return nil, err
	}

	userSvc, _ := usersvc.GetUserSvc()
	// get user signup_source from registration_logs table
	registrationLog, err := userSvc.GetUserRegistrationLog(ctx, userDao.RegistrationLogParam{UserId: user.ID.Uint64()})
	if err != nil {
		tilog.Error(ctx, "GetUserLoginExtInfo GetUserRegistrationLog error", zap.Any("userID", user.ID), zap.Any("err", err))
		return nil, err
	}
	landingActions, err := userSvc.ListUserLandingActions(ctx, userDao.LandingActionFilter{
		UserIDs:       []entity.ID{user.ID},
		TargetActions: []entity.UserTargetAction{entity.NewUserAction},
	})
	if err != nil {
		tilog.Error(ctx, "GetUserLoginExtInfo ListUserLandingActions error", zap.Any("userID", user.ID), zap.Any("err", err))
		return nil, err
	}
	var isSignupUser = false
	if len(landingActions) > 0 {
		isSignupUser = true
	}

	historySvc, _ := historysvc.GetHistorySvc()
	histories, err := historySvc.ListEventHistoryLast(ctx, history.EventHistoryFilter{
		IdentityTypes: []entity.HistoryIdentityType{entity.IdentityTypeUser},
		IdentityIDs:   []entity.ID{user.ID},
		Events:        []entity.HistoryEvent{entity.HistoryEventLogin}})
	if err != nil {
		tilog.Error(ctx, "GetUserLoginExtInfo ListEventHistoryLast error", zap.Any("userID", user.ID), zap.Any("err", err))
		return nil, err
	}
	var isFirstLogin = false
	if len(histories) == 0 {
		isFirstLogin = true
	} else {
		last := histories[0]
		if last.CreatedAt.Equal(*last.UpdatedAt) {
			isFirstLogin = true
		}
	}

	surveySvc, _ := survey.GetSurveySvc()
	showOnBoardingSurvey, err := surveySvc.IsShowPlgOnBoardingSurveyPage(ctx, user.Email)
	isShowOnBoardingSurveyPage := false
	if err == nil && showOnBoardingSurvey {
		isShowOnBoardingSurveyPage = true
	}

	var isOpsPromotion bool
	if req != nil && req.OrgId != 0 {
		promotionStatus, err := billing2.GetHandler().GetTenantPromotionStatus(ctx, req.OrgId)
		if err != nil {
			tilog.Warn(ctx, "GetUserLoginExtInfo GetTenantPromotionStatus error", zap.Any("userID", user.ID), zap.Any("err", err))
		} else if promotionStatus != nil {
			isOpsPromotion = promotionStatus.IsPromotion
		}
	}

	resp := central.GetUserLoginExtInfoResp{
		IsSignupUser:                  isSignupUser,
		IsFirstLogin:                  isFirstLogin,
		IsShowPlgOnBoardingSurveyPage: isShowOnBoardingSurveyPage,
		FirstName:                     user.FirstName,
		LastName:                      user.LastName,
		IsOpcPromotion:                isOpsPromotion,
	}
	if registrationLog != nil {
		resp.SignupSource = registrationLog.SignupSource
	}
	tilog.Info(ctx, "GetUserLoginExtInfo result", zap.Any("userID", user.ID), zap.Any("isSignupUser", isSignupUser), zap.Any("isFirstLogin", isFirstLogin),
		zap.Any("isShowOnBoardingSurveyPage", isShowOnBoardingSurveyPage))

	return &resp, nil
}

func (s *UserService) CreatePlgOnBoardingSurvey(ctx context.Context, req *central.CreatePlgOnBoardingSurveyReq) (*emptypb.Empty, error) {
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		tilog.Error(ctx, "CreatePlgOnBoardingSurvey GetUserFromContext error", zap.Any("err", err))
		return nil, err
	}
	svc, _ := survey.GetSurveySvc()
	err = svc.CreatePlgOnBoardingSurvey(ctx, user.Email, req)
	if err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, nil
}

func (s *UserService) createRoleBindings(ctx context.Context) error {
	svc, _ := rbac.GetRbacRoleBindingSvc()
	a := entity.RoleBinding{
		TargetType: entity.RoleBindingTargetUser,
		TargetID:   250008,
		ScopeType:  entity.RoleBindingScopeOrg,
		ScopeID:    2130251,
		Role:       entity.RoleOrgMember,
	}
	b := entity.RoleBinding{
		TargetType: entity.RoleBindingTargetUser,
		TargetID:   250008,
		ScopeType:  entity.RoleBindingScopeProject,
		ScopeID:    3199697,
		Role:       entity.RoleProjectOwner,
	}

	arr := []entity.RoleBinding{a, b}
	err := svc.CreateRoleBindings(ctx, arr)
	tilog.Info(ctx, "", zap.Error(err))

	return err

}

func (s *UserService) RevokeUserLoginState(ctx context.Context, req *emptypb.Empty) (*emptypb.Empty, error) {
	user, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		tilog.Error(ctx, "RevokeUserLoginState GetUserFromContext error", zap.Any("err", err))
		return nil, err
	}
	svc := session.GetUserSessionSvc()
	err = svc.RevokeLoginSessionByEmail(ctx, user.Email)
	return &emptypb.Empty{}, err
}
