package deletesvc

import (
	"context"
	"errors"

	getsvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/get"
	putsvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/put"
	searchsvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/search"
	objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
)

type headSvcWrapper struct {
	s *getsvc.Service
}

type searchSvcWrapper struct {
	s *searchsvc.Service
}

type putSvcWrapper struct {
	s *putsvc.Service
}

type simpleIDWriter struct {
	ids []oid.ID
}

func (w *headSvcWrapper) headAddress(ctx context.Context, exec *execCtx, addr oid.Address) (*objectSDK.Object, error) {
	wr := getsvc.NewSimpleObjectWriter()

	p := getsvc.HeadPrm{}
	p.SetCommonParameters(exec.commonParameters())
	p.SetHeaderWriter(wr)
	p.WithRawFlag(true)
	p.WithAddress(addr)

	err := w.s.Head(ctx, p)
	if err != nil {
		return nil, err
	}

	return wr.Object(), nil
}

func (w *headSvcWrapper) splitInfo(ctx context.Context, exec *execCtx) (*objectSDK.SplitInfo, error) {
	_, err := w.headAddress(ctx, exec, exec.address())

	var errSplitInfo *objectSDK.SplitInfoError

	switch {
	case err == nil:
		return nil, nil
	case errors.As(err, &errSplitInfo):
		return errSplitInfo.SplitInfo(), nil
	default:
		return nil, err
	}
}

func (w *headSvcWrapper) children(ctx context.Context, exec *execCtx) ([]oid.ID, error) {
	link, _ := exec.splitInfo.Link()

	a := exec.newAddress(link)

	linking, err := w.headAddress(ctx, exec, a)
	if err != nil {
		return nil, err
	}

	return linking.Children(), nil
}

func (w *headSvcWrapper) previous(ctx context.Context, exec *execCtx, id oid.ID) (*oid.ID, error) {
	a := exec.newAddress(id)

	h, err := w.headAddress(ctx, exec, a)
	if err != nil {
		return nil, err
	}

	prev, ok := h.PreviousID()
	if ok {
		return &prev, nil
	}

	return nil, nil
}

func (w *searchSvcWrapper) splitMembers(ctx context.Context, exec *execCtx) ([]oid.ID, error) {
	fs := objectSDK.SearchFilters{}
	fs.AddSplitIDFilter(objectSDK.MatchStringEqual, exec.splitInfo.SplitID())

	wr := new(simpleIDWriter)

	p := searchsvc.Prm{}
	p.SetWriter(wr)
	p.SetCommonParameters(exec.commonParameters())
	p.WithContainerID(exec.containerID())
	p.WithSearchFilters(fs)

	err := w.s.Search(ctx, p)
	if err != nil {
		return nil, err
	}

	return wr.ids, nil
}

func (s *simpleIDWriter) WriteIDs(ids []oid.ID) error {
	s.ids = append(s.ids, ids...)

	return nil
}

func (w *putSvcWrapper) put(ctx context.Context, exec *execCtx) (*oid.ID, error) {
	streamer, err := w.s.Put()
	if err != nil {
		return nil, err
	}

	payload := exec.tombstoneObj.Payload()

	initPrm := new(putsvc.PutInitPrm).
		WithCommonPrm(exec.commonParameters()).
		WithObject(exec.tombstoneObj.CutPayload())

	err = streamer.Init(ctx, initPrm)
	if err != nil {
		return nil, err
	}

	err = streamer.SendChunk(ctx, new(putsvc.PutChunkPrm).WithChunk(payload))
	if err != nil {
		return nil, err
	}

	r, err := streamer.Close(ctx)
	if err != nil {
		return nil, err
	}

	id := r.ObjectID()

	return &id, nil
}