// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package handler

import (
	"context"
	"fmt"
	"strings"

	"github.com/go-openapi/runtime/middleware"

	"github.com/goharbor/harbor/src/common"
	"github.com/goharbor/harbor/src/common/rbac"
	ugCtl "github.com/goharbor/harbor/src/controller/usergroup"
	"github.com/goharbor/harbor/src/lib/config"
	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/q"
	"github.com/goharbor/harbor/src/pkg/usergroup/model"
	"github.com/goharbor/harbor/src/server/v2.0/models"
	operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/usergroup"
)

type userGroupAPI struct {
	BaseAPI
	ctl ugCtl.Controller
}

func newUserGroupAPI() *userGroupAPI {
	return &userGroupAPI{ctl: ugCtl.Ctl}
}

func (u *userGroupAPI) CreateUserGroup(ctx context.Context, params operation.CreateUserGroupParams) middleware.Responder {
	if err := u.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourceUserGroup); err != nil {
		return u.SendError(ctx, err)
	}
	if params.Usergroup == nil {
		return operation.NewCreateUserGroupBadRequest()
	}
	if len(params.Usergroup.GroupName) == 0 {
		return operation.NewCreateUserGroupBadRequest()
	}
	ug := model.UserGroup{
		GroupName:   params.Usergroup.GroupName,
		GroupType:   int(params.Usergroup.GroupType),
		LdapGroupDN: params.Usergroup.LdapGroupDn,
	}
	id, err := u.ctl.Create(ctx, ug)
	if err != nil {
		return u.SendError(ctx, err)
	}
	location := fmt.Sprintf("%s/%d", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), id)
	return operation.NewCreateUserGroupCreated().WithLocation(location)
}

func (u *userGroupAPI) DeleteUserGroup(ctx context.Context, params operation.DeleteUserGroupParams) middleware.Responder {
	if err := u.RequireSystemAccess(ctx, rbac.ActionDelete, rbac.ResourceUserGroup); err != nil {
		return u.SendError(ctx, err)
	}
	if params.GroupID <= 0 {
		return u.SendError(ctx, errors.BadRequestError(nil).WithMessage("the group id should be provided"))
	}
	err := u.ctl.Delete(ctx, int(params.GroupID))
	if err != nil {
		return u.SendError(ctx, err)
	}
	return operation.NewDeleteUserGroupOK()
}

func (u *userGroupAPI) GetUserGroup(ctx context.Context, params operation.GetUserGroupParams) middleware.Responder {
	if err := u.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceUserGroup); err != nil {
		return u.SendError(ctx, err)
	}
	if params.GroupID <= 0 {
		return u.SendError(ctx, errors.BadRequestError(nil).WithMessage("the group id should be provided"))
	}
	ug, err := u.ctl.Get(ctx, int(params.GroupID))
	if err != nil {
		return u.SendError(ctx, err)
	}
	if ug == nil {
		return u.SendError(ctx, errors.NotFoundError(nil).WithMessagef("the user group with id %v is not found", params.GroupID))
	}
	userGroup := &models.UserGroup{
		GroupName:   ug.GroupName,
		GroupType:   int64(ug.GroupType),
		LdapGroupDn: ug.LdapGroupDN,
	}
	return operation.NewGetUserGroupOK().WithPayload(userGroup)
}

func (u *userGroupAPI) ListUserGroups(ctx context.Context, params operation.ListUserGroupsParams) middleware.Responder {
	if err := u.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceUserGroup); err != nil {
		return u.SendError(ctx, err)
	}
	authMode, err := config.AuthMode(ctx)
	if err != nil {
		return u.SendError(ctx, err)
	}
	query, err := u.BuildQuery(ctx, nil, nil, params.Page, params.PageSize)
	if err != nil {
		return u.SendError(ctx, err)
	}
	if params.GroupName != nil && len(*params.GroupName) > 0 {
		query.Keywords["GroupName"] = &q.FuzzyMatchValue{Value: *params.GroupName}
	}
	switch authMode {
	case common.LDAPAuth:
		query.Keywords["GroupType"] = common.LDAPGroupType
		if params.LdapGroupDn != nil && len(*params.LdapGroupDn) > 0 {
			query.Keywords["LdapGroupDN"] = *params.LdapGroupDn
		}
	case common.HTTPAuth:
		query.Keywords["GroupType"] = common.HTTPGroupType
	}

	total, err := u.ctl.Count(ctx, query)
	if err != nil {
		return u.SendError(ctx, err)
	}
	if total == 0 {
		return operation.NewListUserGroupsOK().WithXTotalCount(0).WithPayload([]*models.UserGroup{})
	}
	ug, err := u.ctl.List(ctx, query)
	if err != nil {
		return u.SendError(ctx, err)
	}
	return operation.NewListUserGroupsOK().
		WithXTotalCount(total).
		WithPayload(getUserGroupResp(ug)).
		WithLink(u.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String())
}
func getUserGroupResp(ug []*model.UserGroup) []*models.UserGroup {
	result := make([]*models.UserGroup, 0)
	for _, u := range ug {
		ug := &models.UserGroup{
			GroupName:   u.GroupName,
			GroupType:   int64(u.GroupType),
			LdapGroupDn: u.LdapGroupDN,
			ID:          int64(u.ID),
		}
		result = append(result, ug)
	}
	return result
}
func getUserGroupSearchItem(ug []*model.UserGroup) []*models.UserGroupSearchItem {
	result := make([]*models.UserGroupSearchItem, 0)
	for _, u := range ug {
		ug := &models.UserGroupSearchItem{
			GroupName: u.GroupName,
			GroupType: int64(u.GroupType),
			ID:        int64(u.ID),
		}
		result = append(result, ug)
	}
	return result
}
func (u *userGroupAPI) UpdateUserGroup(ctx context.Context, params operation.UpdateUserGroupParams) middleware.Responder {
	if err := u.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourceUserGroup); err != nil {
		return u.SendError(ctx, err)
	}
	if params.GroupID <= 0 {
		return operation.NewUpdateUserGroupBadRequest()
	}
	if params.Usergroup == nil || len(params.Usergroup.GroupName) == 0 {
		return operation.NewUpdateUserGroupBadRequest()
	}
	err := u.ctl.Update(ctx, int(params.GroupID), params.Usergroup.GroupName)
	if err != nil {
		return u.SendError(ctx, err)
	}
	return operation.NewUpdateUserGroupOK()
}

func (u *userGroupAPI) SearchUserGroups(ctx context.Context, params operation.SearchUserGroupsParams) middleware.Responder {
	if err := u.RequireAuthenticated(ctx); err != nil {
		return u.SendError(ctx, err)
	}
	query, err := u.BuildQuery(ctx, nil, nil, params.Page, params.PageSize)
	if err != nil {
		return u.SendError(ctx, err)
	}
	if len(params.Groupname) == 0 {
		return u.SendError(ctx, errors.BadRequestError(nil).WithMessage("need to provide groupname to search user group"))
	}
	query.Keywords["GroupName"] = &q.FuzzyMatchValue{Value: params.Groupname}
	total, err := u.ctl.Count(ctx, query)
	if err != nil {
		return u.SendError(ctx, err)
	}
	if total == 0 {
		return operation.NewSearchUserGroupsOK().WithXTotalCount(0).WithPayload([]*models.UserGroupSearchItem{})
	}
	ug, err := u.ctl.SearchByName(ctx, params.Groupname, int(*params.PageSize))
	if err != nil {
		return u.SendError(ctx, err)
	}
	result := getUserGroupSearchItem(ug)
	return operation.NewSearchUserGroupsOK().WithXTotalCount(total).
		WithPayload(result).
		WithLink(u.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String())
}
