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

package api

import (
	"context"
	"encoding/json"
	"net/http"
	"strings"
	"time"

	api "github.com/absmach/supermq/api/http"
	apiutil "github.com/absmach/supermq/api/http/util"
	groups "github.com/absmach/supermq/groups"
	"github.com/absmach/supermq/pkg/errors"
	"github.com/go-chi/chi/v5"
)

func DecodeGroupCreate(_ context.Context, r *http.Request) (any, error) {
	if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
		return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
	}
	var g groups.Group
	if err := json.NewDecoder(r.Body).Decode(&g); err != nil {
		return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
	}
	req := createGroupReq{
		Group: g,
	}

	return req, nil
}

func DecodeListGroupsRequest(_ context.Context, r *http.Request) (any, error) {
	pm, err := decodePageMeta(r)
	if err != nil {
		return nil, err
	}

	userID, err := apiutil.ReadStringQuery(r, api.UserKey, "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	groupID, err := apiutil.ReadStringQuery(r, api.GroupKey, "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	req := listGroupsReq{
		PageMeta: pm,
		userID:   userID,
		groupID:  groupID,
	}
	return req, nil
}

func DecodeGroupUpdate(_ context.Context, r *http.Request) (any, error) {
	if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
		return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
	}
	req := updateGroupReq{
		id: chi.URLParam(r, "groupID"),
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
	}
	return req, nil
}

func decodeUpdateGroupTags(_ context.Context, r *http.Request) (any, error) {
	if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
		return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
	}

	req := updateGroupTagsReq{
		id: chi.URLParam(r, "groupID"),
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
	}

	return req, nil
}

func DecodeGroupRequest(_ context.Context, r *http.Request) (any, error) {
	roles, err := apiutil.ReadBoolQuery(r, api.RolesKey, false)
	if err != nil {
		return nil, errors.Wrap(apiutil.ErrValidation, err)
	}

	req := groupReq{
		id:    chi.URLParam(r, "groupID"),
		roles: roles,
	}

	return req, nil
}

func DecodeChangeGroupStatusRequest(_ context.Context, r *http.Request) (any, error) {
	req := changeGroupStatusReq{
		id: chi.URLParam(r, "groupID"),
	}
	return req, nil
}

func decodeRetrieveGroupHierarchy(_ context.Context, r *http.Request) (any, error) {
	hm, err := decodeHierarchyPageMeta(r)
	if err != nil {
		return nil, err
	}

	req := retrieveGroupHierarchyReq{
		id:                chi.URLParam(r, "groupID"),
		HierarchyPageMeta: hm,
	}
	return req, nil
}

func decodeAddParentGroupRequest(_ context.Context, r *http.Request) (any, error) {
	if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
		return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
	}

	req := addParentGroupReq{
		id: chi.URLParam(r, "groupID"),
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
	}
	return req, nil
}

func decodeRemoveParentGroupRequest(_ context.Context, r *http.Request) (any, error) {
	req := removeParentGroupReq{
		id: chi.URLParam(r, "groupID"),
	}
	return req, nil
}

func decodeAddChildrenGroupsRequest(_ context.Context, r *http.Request) (any, error) {
	if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
		return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
	}
	req := addChildrenGroupsReq{
		id: chi.URLParam(r, "groupID"),
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
	}
	return req, nil
}

func decodeRemoveChildrenGroupsRequest(_ context.Context, r *http.Request) (any, error) {
	if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
		return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
	}
	req := removeChildrenGroupsReq{
		id: chi.URLParam(r, "groupID"),
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		return nil, errors.Wrap(apiutil.ErrMalformedRequestBody, err)
	}
	return req, nil
}

func decodeRemoveAllChildrenGroupsRequest(_ context.Context, r *http.Request) (any, error) {
	req := removeAllChildrenGroupsReq{
		id: chi.URLParam(r, "groupID"),
	}
	return req, nil
}

func decodeListChildrenGroupsRequest(_ context.Context, r *http.Request) (any, error) {
	pm, err := decodePageMeta(r)
	if err != nil {
		return nil, err
	}

	startLevel, err := apiutil.ReadNumQuery[int64](r, api.StartLevelKey, api.DefStartLevel)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	endLevel, err := apiutil.ReadNumQuery[int64](r, api.EndLevelKey, api.DefEndLevel)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	req := listChildrenGroupsReq{
		id:         chi.URLParam(r, "groupID"),
		PageMeta:   pm,
		startLevel: startLevel,
		endLevel:   endLevel,
	}
	return req, nil
}

func decodeHierarchyPageMeta(r *http.Request) (groups.HierarchyPageMeta, error) {
	level, err := apiutil.ReadNumQuery[uint64](r, api.LevelKey, api.DefLevel)
	if err != nil {
		return groups.HierarchyPageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	tree, err := apiutil.ReadBoolQuery(r, api.TreeKey, false)
	if err != nil {
		return groups.HierarchyPageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	hierarchyDir, err := apiutil.ReadNumQuery[int64](r, api.DirKey, -1)
	if err != nil {
		return groups.HierarchyPageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	return groups.HierarchyPageMeta{
		Level:     level,
		Direction: hierarchyDir,
		Tree:      tree,
	}, nil
}

func decodePageMeta(r *http.Request) (groups.PageMeta, error) {
	s, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefGroupStatus)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	st, err := groups.ToStatus(s)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	name, err := apiutil.ReadStringQuery(r, api.NameKey, "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	id, err := apiutil.ReadStringQuery(r, api.IDOrder, "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	meta, err := apiutil.ReadMetadataQuery(r, api.MetadataKey, nil)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	allActions, err := apiutil.ReadStringQuery(r, api.ActionsKey, "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	actions := []string{}

	allActions = strings.TrimSpace(allActions)
	if allActions != "" {
		actions = strings.Split(allActions, ",")
	}
	roleID, err := apiutil.ReadStringQuery(r, api.RoleIDKey, "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	roleName, err := apiutil.ReadStringQuery(r, api.RoleNameKey, "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	accessType, err := apiutil.ReadStringQuery(r, api.AccessTypeKey, "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	rootGroup, err := apiutil.ReadBoolQuery(r, api.RootGroupKey, false)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	ot, err := apiutil.ReadBoolQuery(r, api.OnlyTotal, false)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	order, err := apiutil.ReadStringQuery(r, api.OrderKey, api.DefOrder)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	dir, err := apiutil.ReadStringQuery(r, api.DirKey, api.DefDir)
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	tags, err := apiutil.ReadStringQuery(r, api.TagsKey, "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	var tq groups.TagsQuery
	if tags != "" {
		tq = groups.ToTagsQuery(tags)
	}

	cfrom, err := apiutil.ReadStringQuery(r, "created_from", "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}
	cto, err := apiutil.ReadStringQuery(r, "created_to", "")
	if err != nil {
		return groups.PageMeta{}, errors.Wrap(apiutil.ErrValidation, err)
	}

	var createdFrom, createdTo time.Time
	if cfrom != "" {
		if createdFrom, err = time.Parse(time.RFC3339, cfrom); err != nil {
			return groups.PageMeta{}, errors.Wrap(apiutil.ErrInvalidQueryParams, err)
		}
	}
	if cto != "" {
		if createdTo, err = time.Parse(time.RFC3339, cto); err != nil {
			return groups.PageMeta{}, errors.Wrap(apiutil.ErrInvalidQueryParams, err)
		}
	}

	ret := groups.PageMeta{
		Offset:      offset,
		Limit:       limit,
		Name:        name,
		ID:          id,
		Metadata:    meta,
		Status:      st,
		RoleName:    roleName,
		RoleID:      roleID,
		Actions:     actions,
		AccessType:  accessType,
		RootGroup:   rootGroup,
		OnlyTotal:   ot,
		Order:       order,
		Dir:         dir,
		Tags:        tq,
		CreatedFrom: createdFrom,
		CreatedTo:   createdTo,
	}
	return ret, nil
}
