package blobtree

import (
	"context"
	"errors"
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
	"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
	apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
)

func (b *BlobTree) Exists(ctx context.Context, prm common.ExistsPrm) (common.ExistsRes, error) {
	var (
		startedAt = time.Now()
		success   = false
	)
	defer func() {
		b.cfg.metrics.Exists(time.Since(startedAt), success, prm.StorageID != nil)
	}()

	_, span := tracing.StartSpanFromContext(ctx, "BlobTree.Exists",
		trace.WithAttributes(
			attribute.String("path", b.cfg.rootPath),
			attribute.String("address", prm.Address.EncodeToString()),
			attribute.String("storage_id", storageIDToIdxStringSafe(prm.StorageID)),
		))
	defer span.End()

	var res common.ExistsRes
	var err error
	if idx, ok := tryParseIdxFromStorageID(prm.StorageID); ok {
		res, err = b.existsFromIdx(prm.Address, idx)
	} else {
		res, err = b.findAndCheck(prm.Address)
	}
	success = err == nil
	return res, err
}

func (b *BlobTree) existsFromIdx(addr oid.Address, idx uint64) (common.ExistsRes, error) {
	dir := b.getDirectoryPath(addr)
	path := b.getFilePath(dir, idx)

	b.fileLock.RLock(path)
	defer b.fileLock.RUnlock(path)

	records, err := b.readFileContent(path)
	if err != nil {
		return common.ExistsRes{}, err
	}

	for i := range records {
		if records[i].Address.Equals(addr) {
			return common.ExistsRes{
				Exists: true,
			}, nil
		}
	}

	return common.ExistsRes{}, nil
}

func (b *BlobTree) findAndCheck(addr oid.Address) (common.ExistsRes, error) {
	dir := b.getDirectoryPath(addr)
	_, err := b.findFileIdx(dir, addr)
	if err != nil {
		var notFound *apistatus.ObjectNotFound
		if errors.As(err, &notFound) {
			return common.ExistsRes{}, nil
		}
		return common.ExistsRes{}, err
	}
	return common.ExistsRes{Exists: true}, nil
}