package engine

import (
	"context"
	"errors"
	"sync/atomic"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
	apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
	objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
	"golang.org/x/sync/errgroup"
)

func (e *StorageEngine) exists(ctx context.Context, addr oid.Address) (bool, error) {
	var shPrm shard.ExistsPrm
	shPrm.SetAddress(addr)

	var exists atomic.Bool
	eg, egCtx := errgroup.WithContext(ctx)
	egCtx, cancel := context.WithCancel(egCtx)
	defer cancel()

	e.iterateOverSortedShards(addr, func(_ int, sh hashedShard) (stop bool) {
		select {
		case <-egCtx.Done():
			return true
		default:
		}

		eg.Go(func() error {
			res, err := sh.Exists(egCtx, shPrm)
			if err != nil {
				if errors.Is(err, context.Canceled) {
					return err
				}

				if client.IsErrObjectAlreadyRemoved(err) {
					return err
				}

				var siErr *objectSDK.SplitInfoError
				if errors.As(err, &siErr) {
					return err
				}

				if shard.IsErrObjectExpired(err) {
					return err
				}

				if !client.IsErrObjectNotFound(err) {
					e.reportShardError(sh, "could not check existence of object in shard", err)
				}
				return nil
			}
			if res.Exists() {
				exists.Store(true)
				cancel()
			}
			return nil
		})
		return false
	})

	err := eg.Wait()
	if client.IsErrObjectAlreadyRemoved(err) {
		return false, new(apistatus.ObjectAlreadyRemoved)
	}

	return exists.Load(), nil
}