package meta

import (
	"encoding/binary"
	"strings"

	cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id"
	"go.etcd.io/bbolt"
)

func (db *DB) Containers() (list []*cid.ID, err error) {
	err = db.boltDB.View(func(tx *bbolt.Tx) error {
		list, err = db.containers(tx)

		return err
	})

	return list, err
}

func (db *DB) containers(tx *bbolt.Tx) ([]*cid.ID, error) {
	result := make([]*cid.ID, 0)

	err := tx.ForEach(func(name []byte, _ *bbolt.Bucket) error {
		id, err := parseContainerID(name)
		if err != nil {
			return err
		}

		if id != nil {
			result = append(result, id)
		}

		return nil
	})

	return result, err
}

func (db *DB) ContainerSize(id *cid.ID) (size uint64, err error) {
	err = db.boltDB.Update(func(tx *bbolt.Tx) error {
		size, err = db.containerSize(tx, id)

		return err
	})

	return size, err
}

func (db *DB) containerSize(tx *bbolt.Tx, id *cid.ID) (uint64, error) {
	containerVolume, err := tx.CreateBucketIfNotExists(containerVolumeBucketName)
	if err != nil {
		return 0, err
	}

	key := id.ToV2().GetValue()

	return parseContainerSize(containerVolume.Get(key)), nil
}

func parseContainerID(name []byte) (*cid.ID, error) {
	strName := string(name)

	if strings.Contains(strName, invalidBase58String) {
		return nil, nil
	}

	id := cid.New()

	return id, id.Parse(strName)
}

func parseContainerSize(v []byte) uint64 {
	if len(v) == 0 {
		return 0
	}

	return binary.LittleEndian.Uint64(v)
}

func changeContainerSize(tx *bbolt.Tx, id *cid.ID, delta uint64, increase bool) error {
	containerVolume, err := tx.CreateBucketIfNotExists(containerVolumeBucketName)
	if err != nil {
		return err
	}

	key := id.ToV2().GetValue()
	size := parseContainerSize(containerVolume.Get(key))

	if increase {
		size += delta
	} else if size > delta {
		size -= delta
	} else {
		size = 0
	}

	buf := make([]byte, 8) // consider using sync.Pool to decrease allocations
	binary.LittleEndian.PutUint64(buf, size)

	return containerVolume.Put(key, buf)
}