package container

import (
	"errors"
	"fmt"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
	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(idUser *user.ID) ([]cid.ID, error) {
	var rawID []byte

	if idUser != nil {
		rawID = idUser.WalletBytes()
	}

	var cidList []cid.ID
	cb := func(item stackitem.Item) error {
		rawID, err := client.BytesFromStackItem(item)
		if err != nil {
			return fmt.Errorf("could not get byte array from stack item (%s): %w", containersOfMethod, err)
		}

		var id cid.ID

		err = id.Decode(rawID)
		if err != nil {
			return fmt.Errorf("decode container ID: %w", err)
		}

		cidList = append(cidList, id)
		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(cb, batchSize, cnrHash, containersOfMethod, rawID)
	if err != nil {
		if errors.Is(err, unwrap.ErrNoSessionID) {
			return c.list(idUser)
		}
		return nil, err
	}

	return cidList, nil
}