package identity

import (
	"context"
	"net/url"

	cs3group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
	cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
	cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
	libregraph "github.com/owncloud/libre-graph-api-go"

	"github.com/owncloud/ocis/graph/pkg/config"
	"github.com/owncloud/ocis/graph/pkg/service/v0/errorcode"
	"github.com/owncloud/ocis/ocis-pkg/log"
)

var (
	errNotImplemented = errorcode.New(errorcode.NotSupported, "not implemented")
)

type CS3 struct {
	Config *config.Reva
	Logger *log.Logger
}

// CreateUser implements the Backend Interface. It's currently not supported for the CS3 backend
func (i *CS3) CreateUser(ctx context.Context, user libregraph.User) (*libregraph.User, error) {
	return nil, errNotImplemented
}

// DeleteUser implements the Backend Interface. It's currently not supported for the CS3 backend
func (i *CS3) DeleteUser(ctx context.Context, nameOrID string) error {
	return errNotImplemented
}

// UpdateUser implements the Backend Interface. It's currently not suported for the CS3 backend
func (i *CS3) UpdateUser(ctx context.Context, nameOrID string, user libregraph.User) (*libregraph.User, error) {
	return nil, errNotImplemented
}

func (i *CS3) GetUser(ctx context.Context, userID string) (*libregraph.User, error) {
	client, err := pool.GetGatewayServiceClient(i.Config.Address)
	if err != nil {
		i.Logger.Error().Err(err).Msg("could not get client")
		return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
	}

	res, err := client.GetUserByClaim(ctx, &cs3user.GetUserByClaimRequest{
		Claim: "userid", // FIXME add consts to reva
		Value: userID,
	})

	switch {
	case err != nil:
		i.Logger.Error().Err(err).Str("userid", userID).Msg("error sending get user by claim id grpc request")
		return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
	case res.Status.Code != cs3rpc.Code_CODE_OK:
		if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
			return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message)
		}
		i.Logger.Error().Err(err).Str("userid", userID).Msg("error sending get user by claim id grpc request")
		return nil, errorcode.New(errorcode.GeneralException, res.Status.Message)
	}
	return CreateUserModelFromCS3(res.User), nil
}

func (i *CS3) GetUsers(ctx context.Context, queryParam url.Values) ([]*libregraph.User, error) {
	client, err := pool.GetGatewayServiceClient(i.Config.Address)
	if err != nil {
		i.Logger.Error().Err(err).Msg("could not get client")
		return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
	}

	search := queryParam.Get("search")
	if search == "" {
		search = queryParam.Get("$search")
	}

	res, err := client.FindUsers(ctx, &cs3user.FindUsersRequest{
		// FIXME presence match is currently not implemented, an empty search currently leads to
		// Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented
		Filter: search,
	})
	switch {
	case err != nil:
		i.Logger.Error().Err(err).Str("search", search).Msg("error sending find users grpc request")
		return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
	case res.Status.Code != cs3rpc.Code_CODE_OK:
		if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
			return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message)
		}
		i.Logger.Error().Err(err).Str("search", search).Msg("error sending find users grpc request")
		return nil, errorcode.New(errorcode.GeneralException, res.Status.Message)
	}

	users := make([]*libregraph.User, 0, len(res.Users))

	for _, user := range res.Users {
		users = append(users, CreateUserModelFromCS3(user))
	}

	return users, nil
}

func (i *CS3) GetGroups(ctx context.Context, queryParam url.Values) ([]*libregraph.Group, error) {
	client, err := pool.GetGatewayServiceClient(i.Config.Address)
	if err != nil {
		i.Logger.Error().Err(err).Msg("could not get client")
		return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
	}

	search := queryParam.Get("search")
	if search == "" {
		search = queryParam.Get("$search")
	}

	res, err := client.FindGroups(ctx, &cs3group.FindGroupsRequest{
		// FIXME presence match is currently not implemented, an empty search currently leads to
		// Unwilling To Perform": Search Error: error parsing filter: (&(objectclass=posixAccount)(|(cn=*)(displayname=*)(mail=*))), error: Present filter match for cn not implemented
		Filter: search,
	})

	switch {
	case err != nil:
		i.Logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request")
		return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
	case res.Status.Code != cs3rpc.Code_CODE_OK:
		if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
			return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message)
		}
		i.Logger.Error().Err(err).Str("search", search).Msg("error sending find groups grpc request")
		return nil, errorcode.New(errorcode.GeneralException, res.Status.Message)
	}

	groups := make([]*libregraph.Group, 0, len(res.Groups))

	for _, group := range res.Groups {
		groups = append(groups, createGroupModelFromCS3(group))
	}

	return groups, nil
}

// CreateGroup implements the Backend Interface. It's currently not supported for the CS3 backend
func (i *CS3) CreateGroup(ctx context.Context, group libregraph.Group) (*libregraph.Group, error) {
	return nil, errorcode.New(errorcode.NotSupported, "not implemented")
}

func (i *CS3) GetGroup(ctx context.Context, groupID string) (*libregraph.Group, error) {
	client, err := pool.GetGatewayServiceClient(i.Config.Address)
	if err != nil {
		i.Logger.Error().Err(err).Msg("could not get client")
		return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
	}

	res, err := client.GetGroupByClaim(ctx, &cs3group.GetGroupByClaimRequest{
		Claim: "groupid", // FIXME add consts to reva
		Value: groupID,
	})

	switch {
	case err != nil:
		i.Logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request")
		return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
	case res.Status.Code != cs3rpc.Code_CODE_OK:
		if res.Status.Code == cs3rpc.Code_CODE_NOT_FOUND {
			return nil, errorcode.New(errorcode.ItemNotFound, res.Status.Message)
		}
		i.Logger.Error().Err(err).Str("groupid", groupID).Msg("error sending get group by claim id grpc request")
		return nil, errorcode.New(errorcode.GeneralException, res.Status.Message)
	}

	return createGroupModelFromCS3(res.Group), nil
}

// DeleteGroup implements the Backend Interface. It's currently not supported for the CS3 backend
func (i *CS3) DeleteGroup(ctx context.Context, id string) error {
	return errorcode.New(errorcode.NotSupported, "not implemented")
}

// GetGroupMembers implements the Backend Interface. It's currently not supported for the CS3 backend
func (i *CS3) GetGroupMembers(ctx context.Context, groupID string) ([]*libregraph.User, error) {
	return nil, errorcode.New(errorcode.NotSupported, "not implemented")
}

// AddMembersToGroup implements the Backend Interface. It's currently not supported for the CS3 backend
func (i *CS3) AddMembersToGroup(ctx context.Context, groupID string, memberID []string) error {
	return errorcode.New(errorcode.NotSupported, "not implemented")
}

// RemoveMemberFromGroup implements the Backend Interface. It's currently not supported for the CS3 backend
func (i *CS3) RemoveMemberFromGroup(ctx context.Context, groupID string, memberID string) error {
	return errorcode.New(errorcode.NotSupported, "not implemented")
}

func createGroupModelFromCS3(g *cs3group.Group) *libregraph.Group {
	if g.Id == nil {
		g.Id = &cs3group.GroupId{}
	}
	return &libregraph.Group{
		Id:                       &g.Id.OpaqueId,
		OnPremisesDomainName:     &g.Id.Idp,
		OnPremisesSamAccountName: &g.GroupName,
		DisplayName:              &g.DisplayName,
		Mail:                     &g.Mail,
		// TODO when to fetch and expand memberof, usernames or ids?
	}
}
