// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package api

import (
	"context"

	apiutil "github.com/absmach/supermq/api/http/util"
	"github.com/absmach/supermq/groups"
	"github.com/absmach/supermq/pkg/authn"
	"github.com/absmach/supermq/pkg/errors"
	svcerr "github.com/absmach/supermq/pkg/errors/service"
	"github.com/go-kit/kit/endpoint"
)

func CreateGroupEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(createGroupReq)
		if err := req.validate(); err != nil {
			return createGroupRes{created: false}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return createGroupRes{created: false}, svcerr.ErrAuthentication
		}

		group, _, err := svc.CreateGroup(ctx, session, req.Group)
		if err != nil {
			return createGroupRes{created: false}, err
		}

		return createGroupRes{created: true, Group: group}, nil
	}
}

func ViewGroupEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(groupReq)
		if err := req.validate(); err != nil {
			return viewGroupRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return viewGroupRes{}, svcerr.ErrAuthentication
		}

		group, err := svc.ViewGroup(ctx, session, req.id, req.roles)
		if err != nil {
			return viewGroupRes{}, err
		}

		return viewGroupRes{Group: group}, nil
	}
}

func UpdateGroupEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(updateGroupReq)
		if err := req.validate(); err != nil {
			return updateGroupRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return updateGroupRes{}, svcerr.ErrAuthentication
		}

		group := groups.Group{
			ID:          req.id,
			Name:        req.Name,
			Description: req.Description,
			Metadata:    req.Metadata,
		}

		group, err := svc.UpdateGroup(ctx, session, group)
		if err != nil {
			return updateGroupRes{}, err
		}

		return updateGroupRes{Group: group}, nil
	}
}

func updateGroupTagsEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(updateGroupTagsReq)
		if err := req.validate(); err != nil {
			return nil, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return nil, svcerr.ErrAuthentication
		}

		g := groups.Group{
			ID:   req.id,
			Tags: req.Tags,
		}
		g, err := svc.UpdateGroupTags(ctx, session, g)
		if err != nil {
			return nil, err
		}

		return updateGroupRes{Group: g}, nil
	}
}

func EnableGroupEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(changeGroupStatusReq)
		if err := req.validate(); err != nil {
			return changeStatusRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return changeStatusRes{}, svcerr.ErrAuthentication
		}

		group, err := svc.EnableGroup(ctx, session, req.id)
		if err != nil {
			return changeStatusRes{}, err
		}
		return changeStatusRes{Group: group}, nil
	}
}

func DisableGroupEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(changeGroupStatusReq)
		if err := req.validate(); err != nil {
			return changeStatusRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return changeStatusRes{}, svcerr.ErrAuthentication
		}

		group, err := svc.DisableGroup(ctx, session, req.id)
		if err != nil {
			return changeStatusRes{}, err
		}
		return changeStatusRes{Group: group}, nil
	}
}

func ListGroupsEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(listGroupsReq)

		if err := req.validate(); err != nil {
			return groupPageRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return groupPageRes{}, svcerr.ErrAuthentication
		}

		var page groups.Page
		var err error
		switch {
		case req.userID != "":
			page, err = svc.ListUserGroups(ctx, session, req.userID, req.PageMeta)
		default:
			page, err = svc.ListGroups(ctx, session, req.PageMeta)
		}
		if err != nil {
			return groupPageRes{}, err
		}

		groups := []viewGroupRes{}
		for _, g := range page.Groups {
			groups = append(groups, toViewGroupRes(g))
		}

		return groupPageRes{
			pageRes: pageRes{
				Limit:  page.Limit,
				Offset: page.Offset,
				Total:  page.Total,
			},
			Groups: groups,
		}, nil
	}
}

func DeleteGroupEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(groupReq)
		if err := req.validate(); err != nil {
			return deleteGroupRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return deleteGroupRes{}, svcerr.ErrAuthentication
		}
		if err := svc.DeleteGroup(ctx, session, req.id); err != nil {
			return deleteGroupRes{}, err
		}
		return deleteGroupRes{deleted: true}, nil
	}
}

func retrieveGroupHierarchyEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(retrieveGroupHierarchyReq)
		if err := req.validate(); err != nil {
			return retrieveGroupHierarchyRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return changeStatusRes{}, svcerr.ErrAuthentication
		}

		hp, err := svc.RetrieveGroupHierarchy(ctx, session, req.id, req.HierarchyPageMeta)
		if err != nil {
			return retrieveGroupHierarchyRes{}, err
		}
		if req.HierarchyPageMeta.Tree {
			return buildGroupsResponseTree(hp), nil
		}

		groups := []viewGroupRes{}
		for _, g := range hp.Groups {
			groups = append(groups, toViewGroupRes(g))
		}
		return retrieveGroupHierarchyRes{Level: hp.Level, Direction: hp.Direction, Groups: groups}, nil
	}
}

func addParentGroupEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(addParentGroupReq)
		if err := req.validate(); err != nil {
			return addParentGroupRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return changeStatusRes{}, svcerr.ErrAuthentication
		}

		if err := svc.AddParentGroup(ctx, session, req.id, req.ParentID); err != nil {
			return addParentGroupRes{}, err
		}
		return addParentGroupRes{}, nil
	}
}

func removeParentGroupEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(removeParentGroupReq)
		if err := req.validate(); err != nil {
			return removeParentGroupRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return changeStatusRes{}, svcerr.ErrAuthentication
		}

		if err := svc.RemoveParentGroup(ctx, session, req.id); err != nil {
			return removeParentGroupRes{}, err
		}
		return removeParentGroupRes{}, nil
	}
}

func addChildrenGroupsEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(addChildrenGroupsReq)
		if err := req.validate(); err != nil {
			return addChildrenGroupsRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return changeStatusRes{}, svcerr.ErrAuthentication
		}

		if err := svc.AddChildrenGroups(ctx, session, req.id, req.ChildrenIDs); err != nil {
			return addChildrenGroupsRes{}, err
		}
		return addChildrenGroupsRes{}, nil
	}
}

func removeChildrenGroupsEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(removeChildrenGroupsReq)
		if err := req.validate(); err != nil {
			return removeChildrenGroupsRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return changeStatusRes{}, svcerr.ErrAuthentication
		}

		if err := svc.RemoveChildrenGroups(ctx, session, req.id, req.ChildrenIDs); err != nil {
			return removeChildrenGroupsRes{}, err
		}
		return removeChildrenGroupsRes{}, nil
	}
}

func removeAllChildrenGroupsEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(removeAllChildrenGroupsReq)
		if err := req.validate(); err != nil {
			return removeAllChildrenGroupsRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return changeStatusRes{}, svcerr.ErrAuthentication
		}

		if err := svc.RemoveAllChildrenGroups(ctx, session, req.id); err != nil {
			return removeAllChildrenGroupsRes{}, err
		}
		return removeAllChildrenGroupsRes{}, nil
	}
}

func listChildrenGroupsEndpoint(svc groups.Service) endpoint.Endpoint {
	return func(ctx context.Context, request any) (any, error) {
		req := request.(listChildrenGroupsReq)
		if err := req.validate(); err != nil {
			return listChildrenGroupsRes{}, errors.Wrap(apiutil.ErrValidation, err)
		}

		session, ok := ctx.Value(authn.SessionKey).(authn.Session)
		if !ok {
			return changeStatusRes{}, svcerr.ErrAuthentication
		}

		gp, err := svc.ListChildrenGroups(ctx, session, req.id, req.startLevel, req.endLevel, req.PageMeta)
		if err != nil {
			return listChildrenGroupsRes{}, err
		}
		viewGroups := []viewGroupRes{}

		for _, group := range gp.Groups {
			viewGroups = append(viewGroups, toViewGroupRes(group))
		}
		return listChildrenGroupsRes{
			pageRes: pageRes{
				Limit:  gp.Limit,
				Offset: gp.Offset,
				Total:  gp.Total,
			},
			Groups: viewGroups,
		}, nil
	}
}

func toViewGroupRes(group groups.Group) viewGroupRes {
	view := viewGroupRes{
		Group: group,
	}
	return view
}

func buildGroupsResponseTree(page groups.HierarchyPage) retrieveGroupHierarchyRes {
	groupsMap := map[string]*groups.Group{}
	parentsMap := map[string][]*groups.Group{}
	for i := range page.Groups {
		if _, ok := groupsMap[page.Groups[i].ID]; !ok {
			groupsMap[page.Groups[i].ID] = &page.Groups[i]
			parentsMap[page.Groups[i].ID] = make([]*groups.Group, 0)
		}
	}

	for _, group := range groupsMap {
		if children, ok := parentsMap[group.Parent]; ok {
			children = append(children, group)
			parentsMap[group.Parent] = children
		}
	}

	res := retrieveGroupHierarchyRes{
		Level:     page.Level,
		Direction: page.Direction,
		Groups:    []viewGroupRes{},
	}

	for _, group := range groupsMap {
		if children, ok := parentsMap[group.ID]; ok {
			group.Children = children
		}
	}

	for _, group := range groupsMap {
		view := toViewGroupRes(*group)
		if children, ok := parentsMap[group.Parent]; len(children) == 0 || !ok {
			res.Groups = append(res.Groups, view)
		}
	}

	return res
}
