package meta_test

import (
	"strconv"
	"testing"

	objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
	object2 "github.com/nspcc-dev/neofs-node/pkg/core/object"
	meta "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/metabase"
	"github.com/nspcc-dev/neofs-sdk-go/object"
	addressSDK "github.com/nspcc-dev/neofs-sdk-go/object/address"
	objecttest "github.com/nspcc-dev/neofs-sdk-go/object/address/test"
	oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
	oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test"
	"github.com/stretchr/testify/require"
)

func TestDB_IterateExpired(t *testing.T) {
	db := newDB(t)

	const epoch = 13

	mAlive := map[object.Type]*addressSDK.Address{}
	mExpired := map[object.Type]*addressSDK.Address{}

	for _, typ := range []object.Type{
		object.TypeRegular,
		object.TypeTombstone,
		object.TypeStorageGroup,
		object.TypeLock,
	} {
		mAlive[typ] = putWithExpiration(t, db, typ, epoch)
		mExpired[typ] = putWithExpiration(t, db, typ, epoch-1)
	}

	expiredLocked := putWithExpiration(t, db, object.TypeRegular, epoch-1)
	cnrExpiredLocked, _ := expiredLocked.ContainerID()
	idExpiredLocked, _ := expiredLocked.ObjectID()

	require.NoError(t, db.Lock(cnrExpiredLocked, oidtest.ID(), []oid.ID{idExpiredLocked}))

	err := db.IterateExpired(epoch, func(exp *meta.ExpiredObject) error {
		if addr, ok := mAlive[exp.Type()]; ok {
			require.NotEqual(t, addr, exp.Address())
		}

		require.NotEqual(t, expiredLocked, exp.Address())

		addr, ok := mExpired[exp.Type()]
		require.True(t, ok)
		require.Equal(t, addr, exp.Address())

		delete(mExpired, exp.Type())

		return nil
	})
	require.NoError(t, err)

	require.Empty(t, mExpired)
}

func putWithExpiration(t *testing.T, db *meta.DB, typ object.Type, expiresAt uint64) *addressSDK.Address {
	obj := generateObject(t)
	obj.SetType(typ)
	addAttribute(obj, objectV2.SysAttributeExpEpoch, strconv.FormatUint(expiresAt, 10))

	require.NoError(t, putBig(db, obj))

	return object2.AddressOf(obj)
}

func TestDB_IterateCoveredByTombstones(t *testing.T) {
	db := newDB(t)

	ts := objecttest.Address()
	protected1 := objecttest.Address()
	protected2 := objecttest.Address()
	protectedLocked := objecttest.Address()
	garbage := objecttest.Address()

	prm := new(meta.InhumePrm)

	var err error

	_, err = db.Inhume(prm.
		WithTombstoneAddress(ts).
		WithAddresses(protected1, protected2, protectedLocked),
	)
	require.NoError(t, err)

	_, err = db.Inhume(prm.
		WithAddresses(garbage).
		WithGCMark(),
	)
	require.NoError(t, err)

	var handled []*addressSDK.Address

	tss := map[string]*addressSDK.Address{
		ts.String(): ts,
	}

	err = db.IterateCoveredByTombstones(tss, func(addr *addressSDK.Address) error {
		handled = append(handled, addr)
		return nil
	})
	require.NoError(t, err)

	require.Len(t, handled, 3)
	require.Contains(t, handled, protected1)
	require.Contains(t, handled, protected2)
	require.Contains(t, handled, protectedLocked)

	cnrProtectedLocked, _ := protectedLocked.ContainerID()
	idProtectedLocked, _ := protectedLocked.ObjectID()

	err = db.Lock(cnrProtectedLocked, oidtest.ID(), []oid.ID{idProtectedLocked})
	require.NoError(t, err)

	handled = handled[:0]

	err = db.IterateCoveredByTombstones(tss, func(addr *addressSDK.Address) error {
		handled = append(handled, addr)
		return nil
	})
	require.NoError(t, err)

	require.Len(t, handled, 2)
	require.NotContains(t, handled, protectedLocked)
}