package container

import (
	"context"
	"errors"

	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)

// ContainersOf returns a list of container identifiers belonging
// to the specified user of FrostFS system. If idUser is nil, returns the list of all containers.
//
// If remote RPC does not support neo-go session API, fallback to List() method.
func (c *Client) ContainersOf(ctx context.Context, idUser *user.ID) ([]cid.ID, error) {
	var cidList []cid.ID
	var err error

	cb := func(id cid.ID) error {
		cidList = append(cidList, id)
		return nil
	}
	if err = c.IterateContainersOf(ctx, idUser, cb); err != nil {
		return nil, err
	}
	return cidList, nil
}

// iterateContainers iterates over a list of container identifiers
// belonging to the specified user of FrostFS system and executes
// `cb` on each element. If idUser is nil, calls it on the list of all containers.
func (c *Client) IterateContainersOf(ctx context.Context, idUser *user.ID, cb func(item cid.ID) error) error {
	var rawID []byte
	if idUser != nil {
		rawID = idUser.WalletBytes()
	}

	itemCb := func(item stackitem.Item) error {
		id, err := getCIDfromStackItem(item)
		if err != nil {
			return err
		}
		if err = cb(id); err != nil {
			return err
		}
		return nil
	}

	// We would like to have batch size as big as possible,
	// to reduce the number of round-trips and avoid creating sessions.
	// The limit depends on 2 things:
	// 1. VM limits: max 2048 items on stack.
	// 2. JSON encoded size for the item with type = 128k.
	// It turns out, that for container ID the second limit is hit first,
	// 512 is big enough value and it is beautiful.
	const batchSize = 512

	cnrHash := c.client.ContractAddress()
	err := c.client.Morph().TestInvokeIterator(itemCb, batchSize, cnrHash, containersOfMethod, rawID)
	if err != nil && errors.Is(err, unwrap.ErrNoSessionID) {
		return c.iterate(ctx, idUser, cb)
	}

	return err
}