package tenant

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/gogo/protobuf/types"
	"github.com/pingcap/dbaas/central/config"
	"github.com/pingcap/dbaas/central/db/entity"
	"github.com/pingcap/dbaas/central/model/tenant"
	"github.com/pingcap/dbaas/central/service"
	"github.com/pingcap/dbaas/central/singleton"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/idl/pbgen/proto/central"
)

func (s *tenantService) ListProjects(ctx context.Context, req *central.ListProjectsReq) (*central.Projects, error) {
	projects, err := s.userContext(ctx).ListProjects(ctx, entity.ID(req.OrgId))
	if err != nil {
		return nil, err
	}

	return &central.Projects{
		Total: int32(len(projects)),
		Items: service.Pagination(req.Page, req.PerPage, projects).([]*central.Project),
	}, nil
}

func (s *tenantService) CreateProject(ctx context.Context, req *central.CreateProjectReq) (*central.ProjectId, error) {
	orgID := entity.ID(req.OrgId)
	userContext := s.userContext(ctx)
	err := userContext.CheckOrgID(orgID)
	if err != nil {
		return nil, err
	}
	err = userContext.CheckIfOwner()
	if err != nil {
		return nil, err
	}

	projectName := s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.Name))
	err = service.IsValidProjectName(projectName)
	if err != nil {
		return nil, errcode.NewInvalidInputErr(err)
	}

	createProjectReq := tenant.CreateProjectReq{
		OrgID:                orgID,
		ProjectName:          projectName,
		EnableXAccountDeploy: req.EnableXaccountDeploy,
	}
	return userContext.CreateProject(createProjectReq)
}

func (s *tenantService) UpdateProject(ctx context.Context, req *central.UpdateProjectReq) (*types.Empty, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userContext(ctx)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}
	err = userContext.CheckIfOwner()
	if err != nil {
		return nil, err
	}

	projectName := s.SanitizerPolicy().Sanitize(strings.TrimSpace(req.Name))
	err = service.IsValidProjectName(projectName)
	if err != nil {
		return nil, errcode.NewInvalidInputErr(err)
	}

	updateProjectReq := tenant.UpdateProjectReq{
		OrgID:       orgID,
		ProjectID:   projectID,
		ProjectName: projectName,
	}
	err = userContext.UpdateProject(updateProjectReq)
	if err != nil {
		return nil, err
	}
	return &types.Empty{}, nil
}

func (s *tenantService) DeleteProject(ctx context.Context, req *central.DeleteProjectReq) (*types.Empty, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userContext(ctx)

	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}
	err = userContext.CheckIfOwner()
	if err != nil {
		return nil, err
	}

	return &types.Empty{}, tenant.DeleteProject(ctx, orgID, projectID)
}

func (s *tenantService) ListProjectUsers(ctx context.Context, req *central.ListProjectUsersReq) (*central.Users, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userContext(ctx)
	logger := s.Log(ctx)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	ListProjectUsersReq := tenant.ListProjectUsersReq{
		ProjectID: projectID,
	}
	if req.Email != "" {
		ListProjectUsersReq.Email = &req.Email
	}
	userList, err := userContext.ListProjectUsers(ListProjectUsersReq)
	if err != nil {
		return nil, err
	}
	users := usersOutput(logger, userList)

	return &central.Users{
		Total: int32(len(users)),
		Items: service.Pagination(req.Page, req.PerPage, users).([]*central.User),
	}, nil
}

func (s *tenantService) InviteUser(ctx context.Context, req *central.InviteUserReq) (*types.Empty, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userContext(ctx)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}
	err = userContext.CheckIfOwner()
	if err != nil {
		return nil, err
	}

	// 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, ";")
	emailList := []string{}
	for _, email := range emailSlice {
		email = strings.ToLower(strings.TrimSpace(email))
		if email != "" {
			emailList = append(emailList, email)
		}
	}

	inviteUserReq := tenant.InviteUserReq{
		OrgID:     orgID,
		ProjectID: projectID,
		EmailList: emailList,
		Role:      entity.UserRole(req.Role),
	}
	err = validateInviteUserReq(inviteUserReq)
	if err != nil {
		return nil, errcode.NewInvalidInputErr(err)
	}
	err = userContext.InviteUser(ctx, inviteUserReq)
	if err != nil {
		return nil, err
	}
	return &types.Empty{}, nil
}

func (s *tenantService) DeleteProjectUser(ctx context.Context, req *central.DeleteProjectUserReq) (*types.Empty, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userID := entity.ID(req.UserId)
	userContext := s.userContext(ctx)

	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}
	err = userContext.CheckIfOwner()
	if err != nil {
		return nil, err
	}

	return &types.Empty{}, userContext.DeleteProjectUser(projectID, userID)
}

func (s *tenantService) GetProjectTrafficFilter(ctx context.Context, req *central.GetProjectTrafficFilterReq) (*central.TrafficFilterResp, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userContext(ctx)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	trafficFilters, err := userContext.GetProjectTrafficFilter(projectID)
	if err != nil {
		return nil, errcode.NewInternalErr(errors.New("failed to get project traffic filter"))
	}
	return &central.TrafficFilterResp{
		Total:          int32(len(trafficFilters.GetCidrs())),
		TrafficFilters: trafficFilters,
	}, nil
}

func (s *tenantService) UpdateProjectTrafficFilter(ctx context.Context, req *central.UpdateProjectTrafficFilterReq) (*types.Empty, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userContext(ctx)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	return &types.Empty{}, userContext.UpdateProjectTrafficFilter(projectID, req.GetTrafficFilters())
}
