package user

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

	"github.com/tidbcloud/account/central/utils"
	"github.com/tidbcloud/account/idl/pbgen/proto/central"
	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/account/internel/svc/comsvc"
	usersvc "github.com/tidbcloud/account/internel/svc/user"
	"github.com/tidbcloud/account/internel/svc/user/oneconsole"
	"github.com/tidbcloud/tiinf/errs"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func (s *UserService) ListConsoleUsers(ctx context.Context, req *central.ListConsoleUsersReq) (*central.ListConsoleUsersResp, error) {
	bm := utils.Begin(ctx, "ListConsoleUsers", zap.Any("req", req))
	defer bm.OnExit(ctx)

	if req.OrgId == 0 {
		return nil, errs.NewBadRequest("org_id invalid", nil)
	}
	if req.ScopeType != central.ListConsoleUsersSupportScopeType_ORG && req.ScopeType != central.ListConsoleUsersSupportScopeType_PROJECT && req.ScopeType != central.ListConsoleUsersSupportScopeType_TIDB {
		return nil, errs.NewBadRequest("scope_type invalid", nil)
	}
	if req.ScopeType == central.ListConsoleUsersSupportScopeType_PROJECT && req.ProjectId == 0 {
		return nil, errs.NewBadRequest("project_id invalid", nil)
	}
	if req.ScopeType == central.ListConsoleUsersSupportScopeType_TIDB && req.ClusterId == 0 {
		return nil, errs.NewBadRequest("cluster_id invalid", nil)
	}
	if req.Email != "" {
		email, _ := url.QueryUnescape(req.Email)
		req.Email = email
	}
	if req.Page < 1 {
		req.Page = 1
	}
	if req.PerPage <= 0 || req.PerPage > 100 {
		req.PerPage = 100
	}

	operator, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return nil, errs.NewInternalUnknown("get login user internal error", err)
	}

	var scopeType entity.ScopeType
	var scopeId uint64
	switch req.ScopeType {
	case central.ListConsoleUsersSupportScopeType_ORG:
		scopeType = entity.RoleBindingScopeOrg
		scopeId = req.OrgId
	case central.ListConsoleUsersSupportScopeType_PROJECT:
		scopeType = entity.RoleBindingScopeProject
		scopeId = req.ProjectId
	case central.ListConsoleUsersSupportScopeType_TIDB:
		scopeType = entity.RoleBindingScopeTiDB
		scopeId = req.ClusterId
	default:
		return nil, errs.NewBadRequest("scope_type invalid", nil)
	}

	ret, err := oneconsole.GetOneConsoleUserSvc().ListUsers(ctx, &oneconsole.ListUsersParam{
		OrgId:      req.OrgId,
		ScopeType:  scopeType,
		ScopeId:    scopeId,
		OperatorId: operator.ID.Uint64(),
		Email:      req.Email,
		Roles:      req.Roles,
		Statuses:   req.Statuses,
		SortBy:     req.SortBy,
		Order:      req.Order,
		PageSize:   int32(req.PerPage),
		PageNumber: req.Page,
		Source:     oneconsole.ListUsersSourceConsole,
	})
	if err != nil {
		return nil, err
	}

	return convertListUsersRetToConsoleResp(ret, scopeType, scopeId), nil
}

func (s *UserService) CheckEmailAlreadyHasOrgRole(ctx context.Context, req *central.CheckEmailAlreadyHasOrgRoleReq) (*central.CheckEmailAlreadyHasOrgRoleResp, error) {
	bm := utils.Begin(ctx, "CheckEmailAlreadyHasOrgRole", zap.Any("req", req))
	defer bm.OnExit(ctx)

	if len(req.Emails) == 0 || req.OrgId == 0 {
		return nil, errs.NewBadRequest("param invalid", nil)
	}
	hasRoleEmails, err := oneconsole.GetOneConsoleUserSvc().CheckEmailAlreadyHasOrgRole(ctx, req.Emails, req.OrgId)
	if err != nil {
		return nil, err
	}
	return &central.CheckEmailAlreadyHasOrgRoleResp{
		HasRoleEmails: hasRoleEmails,
	}, nil

}

func (s *UserService) AssignConsoleUserRole(ctx context.Context, req *central.AssignConsoleUserRoleReq) (*central.AssignConsoleUserRoleResp, error) {
	bm := utils.Begin(ctx, "AssignConsoleUserRole", zap.Any("req", req))
	defer bm.OnExit(ctx)

	if req.OrgId == 0 || len(req.Emails) == 0 {
		return nil, errs.NewBadRequest("param invalid", nil)
	}
	var trimEmails []string
	for _, e := range req.Emails {
		trimE := strings.TrimSpace(e)
		if !utils.IsValidEmail(trimE) {
			return nil, errs.NewBadRequest(fmt.Sprintf("email %s invlaid", e), nil)
		}
		trimEmails = append(trimEmails, strings.ToLower(trimE))
	}

	if req.OrgRole == nil {
		return nil, errs.NewBadRequest("role invalid", nil)
	}
	if _, ok := entity.ValidUserOrgRbacRoles[req.OrgRole.RbacRole]; !ok {
		return nil, errs.NewBadRequest(fmt.Sprintf("org role %s invlaid", req.OrgRole.RbacRole), nil)
	}
	pRoles := map[uint64]string{}
	if len(req.ProjectRoles) > 0 {
		var existPids = map[uint64]struct{}{}
		for _, pr := range req.ProjectRoles {
			if _, ok := existPids[pr.ScopeId]; ok {
				return nil, errs.NewBadRequest(fmt.Sprintf("project %d repeated", pr.ScopeId), nil)
			}
			existPids[pr.ScopeId] = struct{}{}
			if _, ok := entity.ValidUserProjectRbacRoles[pr.RbacRole]; !ok {
				return nil, errs.NewBadRequest(fmt.Sprintf("project role %s invlaid", pr.RbacRole), nil)
			}
			pRoles[pr.ScopeId] = pr.RbacRole
		}
	}
	cRoles := map[uint64]string{}
	if len(req.ClusterRoles) > 0 {
		var existCids = map[uint64]struct{}{}
		for _, cr := range req.ClusterRoles {
			if _, ok := existCids[cr.ScopeId]; ok {
				return nil, errs.NewBadRequest(fmt.Sprintf("cluster %d repeated", cr.ScopeId), nil)
			}
			existCids[cr.ScopeId] = struct{}{}
			if _, ok := entity.ValidClusterRbacRoles[cr.RbacRole]; !ok {
				return nil, errs.NewBadRequest(fmt.Sprintf("cluster role %s invlaid", cr.RbacRole), nil)
			}
			cRoles[cr.ScopeId] = cr.RbacRole
		}
	}

	operator, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return nil, errs.NewInternalUnknown("get login user internal error", err)
	}

	param := &oneconsole.InviteUsersParam{
		EmailList: trimEmails,
		OrgID:     req.OrgId,
		ORole:     req.OrgRole.RbacRole,
		PRoles:    pRoles,
		CRoles:    cRoles,
	}
	oper := &comsvc.Operator{
		OpTye: entity.RoleBindingTargetUser,
		OpID:  operator.ID.Uint64(),
		OpVal: operator.Email,
	}
	inviteRets, err := oneconsole.GetOneConsoleUserSvc().InviteUsersToOrg(ctx, param, oper)
	if err != nil {
		return nil, errs.NewInternalUnknown("assign console user role failed", err)
	}

	var finalRets []*central.AssignConsoleUserRoleRet
	var repeatedEmails, internalFailEmail []string
	for _, ret := range inviteRets {
		rRet := central.AssignConsoleUserRoleRet{
			Email:   ret.Email,
			Success: ret.Code == entity.InviteUserSuccess,
			Message: ret.Message,
		}
		finalRets = append(finalRets, &rRet)
		if ret.Code == entity.InviteUserRepeated {
			repeatedEmails = append(repeatedEmails, ret.Email)
		}
		if ret.Code == entity.InviteUserInternalErr {
			internalFailEmail = append(internalFailEmail, ret.Email)
		}
	}

	var ErrMessage string
	if len(repeatedEmails) > 0 {
		ErrMessage += fmt.Sprintf(oneconsole.ErrRepeatedInviteEmailTemplate, strings.Join(repeatedEmails, ", "))
	}
	if len(internalFailEmail) != 0 {
		ErrMessage = fmt.Sprintf(oneconsole.ErrFailedInviteEmailTemplate, strings.Join(internalFailEmail, ", "))
	}
	if ErrMessage != "" {
		if len(internalFailEmail) > 0 {
			err = errs.NewInternalUnknown(ErrMessage, nil)
		} else {
			err = errs.NewBadRequest(ErrMessage, nil)
		}
	}
	return &central.AssignConsoleUserRoleResp{
		Rets: finalRets,
	}, err
}

func (s *UserService) EditConsoleUserRole(ctx context.Context, req *central.EditConsoleUserRoleReq) (*emptypb.Empty, error) {
	bm := utils.Begin(ctx, "EditConsoleUserRole", zap.Any("req", req))
	defer bm.OnExit(ctx)

	if req.UserId == 0 || req.OrgId == 0 {
		return nil, errs.NewBadRequest("param invalid", nil)
	}
	if req.OrgRole == nil {
		return nil, errs.NewBadRequest("org role invalid", nil)
	}
	if _, ok := entity.ValidUserOrgRbacRoles[req.OrgRole.RbacRole]; !ok {
		return nil, errs.NewBadRequest(fmt.Sprintf("org role %s invlaid", req.OrgRole.RbacRole), nil)
	}
	var pRoles = map[uint64]string{}
	if len(req.ProjectRoles) > 0 {
		var existPids = map[uint64]struct{}{}
		for _, pr := range req.ProjectRoles {
			if _, ok := existPids[pr.ScopeId]; ok {
				return nil, errs.NewBadRequest(fmt.Sprintf("project %d repeated", pr.ScopeId), nil)
			}
			existPids[pr.ScopeId] = struct{}{}
			if _, ok := entity.ValidUserProjectRbacRoles[pr.RbacRole]; !ok {
				return nil, errs.NewBadRequest(fmt.Sprintf("project role %s invlaid", pr.RbacRole), nil)
			}
			pRoles[pr.ScopeId] = pr.RbacRole
		}
	}
	var cRoles = map[uint64]string{}
	if len(req.ClusterRoles) > 0 {
		var existCids = map[uint64]struct{}{}
		for _, cr := range req.ClusterRoles {
			if _, ok := existCids[cr.ScopeId]; ok {
				return nil, errs.NewBadRequest(fmt.Sprintf("cluster %d repeated", cr.ScopeId), nil)
			}
			existCids[cr.ScopeId] = struct{}{}
			if _, ok := entity.ValidClusterRbacRoles[cr.RbacRole]; !ok {
				return nil, errs.NewBadRequest(fmt.Sprintf("cluster role %s invlaid", cr.RbacRole), nil)
			}
			cRoles[cr.ScopeId] = cr.RbacRole
		}
	}

	operator, err := usersvc.GetUserFromContext(ctx)
	if err != nil {
		return nil, errs.NewInternalUnknown("get login user internal error", err)
	}

	param := &oneconsole.UpdateRolesParam{
		UserID: req.UserId,
		OrgID:  req.OrgId,
		ORole:  &req.OrgRole.RbacRole,
		PRoles: pRoles,
		CRoles: cRoles,
	}
	oper := &comsvc.Operator{
		OpTye: entity.RoleBindingTargetUser,
		OpID:  operator.ID.Uint64(),
		OpVal: operator.Email,
	}
	err = oneconsole.GetOneConsoleUserSvc().UpdateUserRoles(ctx, param, oper)
	if err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func convertListUsersRetToConsoleResp(ret *oneconsole.ListUsersRet, scopeType entity.ScopeType, scopeId uint64) *central.ListConsoleUsersResp {
	var items []*central.ConsoleUser
	for _, u := range ret.Users {
		cu := &central.ConsoleUser{
			Id:         u.UserId,
			Email:      u.Email,
			FirstName:  u.FirstName,
			LastName:   u.LastName,
			Status:     u.Status,
			InviteTime: timestamppb.New(u.InviteTime),
		}
		if u.LastLoginTime != nil {
			cu.LoginTime = timestamppb.New(*u.LastLoginTime)
		}
		if u.OrgRole != nil {
			cu.OrgRole = &central.ConsoleUserRole{
				ScopeType: u.OrgRole.ScopeType,
				ScopeId:   u.OrgRole.ScopeId,
				RbacRole:  u.OrgRole.RbacRole,
			}
		}
		for _, pr := range u.ProjectRoles {
			cu.ProjectRoles = append(cu.ProjectRoles, &central.ConsoleUserRole{
				ScopeType: pr.ScopeType,
				ScopeId:   pr.ScopeId,
				RbacRole:  pr.RbacRole,
			})
		}
		for _, cr := range u.ClusterRoles {
			cu.ClusterRoles = append(cu.ClusterRoles, &central.ConsoleUserRole{
				ScopeType: cr.ScopeType,
				ScopeId:   cr.ScopeId,
				RbacRole:  cr.RbacRole,
			})
		}
		items = append(items, cu)
	}
	if items == nil {
		items = []*central.ConsoleUser{}
	}
	return &central.ListConsoleUsersResp{
		Total: ret.TotalSize,
		Items: items,
	}
}
