package getsvc

import (
	"context"
	"slices"

	objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
)

func (a *assembler) assembleObjectByChildrenListRange(ctx context.Context, childrenIDs []oid.ID, writer ObjectWriter) error {
	if err := a.assemblePayloadInReverseRange(ctx, writer, childrenIDs[len(childrenIDs)-1]); err != nil {
		return err
	}
	return writer.WriteChunk(ctx, a.parentObject.Payload())
}

func (a *assembler) assemleObjectByPreviousIDInReverseRange(ctx context.Context, prevID oid.ID, writer ObjectWriter) error {
	if err := a.assemblePayloadInReverseRange(ctx, writer, prevID); err != nil {
		return err
	}
	if err := writer.WriteChunk(ctx, a.parentObject.Payload()); err != nil { // last part
		return err
	}
	return nil
}

func (a *assembler) assemblePayloadByObjectIDsRange(ctx context.Context, writer ObjectWriter, partIDs []oid.ID, partRanges []objectSDK.Range) error {
	for i := range partIDs {
		_, err := a.getChildObject(ctx, partIDs[i], &partRanges[i], false, writer)
		if err != nil {
			return err
		}
	}
	return nil
}

func (a *assembler) assemblePayloadInReverseRange(ctx context.Context, writer ObjectWriter, prevID oid.ID) error {
	chain, rngs, err := a.buildChainRange(ctx, prevID)
	if err != nil {
		return err
	}

	slices.Reverse(chain)
	slices.Reverse(rngs)
	return a.assemblePayloadByObjectIDsRange(ctx, writer, chain, rngs)
}

func (a *assembler) buildChainRange(ctx context.Context, prevID oid.ID) ([]oid.ID, []objectSDK.Range, error) {
	var (
		chain []oid.ID
		rngs  []objectSDK.Range
		from  = a.rng.GetOffset()
		to    = from + a.rng.GetLength()

		hasPrev = true
	)

	// fill the chain end-to-start
	for hasPrev {
		if a.currentOffset <= from {
			break
		}

		head, err := a.objGetter.HeadObject(ctx, prevID)
		if err != nil {
			return nil, nil, err
		}
		if !a.isChild(head) {
			return nil, nil, errParentAddressDiffers
		}

		sz := head.PayloadSize()

		a.currentOffset -= sz

		if a.currentOffset < to {
			off := uint64(0)
			if from > a.currentOffset {
				off = from - a.currentOffset
				sz -= from - a.currentOffset
			}

			if to < a.currentOffset+off+sz {
				sz = to - off - a.currentOffset
			}

			index := len(rngs)
			rngs = append(rngs, objectSDK.Range{})
			rngs[index].SetOffset(off)
			rngs[index].SetLength(sz)

			id, _ := head.ID()
			chain = append(chain, id)
		}

		prevID, hasPrev = head.PreviousID()
	}

	return chain, rngs, nil
}