package blobovnicza

import (
	"context"
	"errors"
	"fmt"
	"syscall"

	"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
	"go.etcd.io/bbolt"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
)

var incompletedMoveBucketName = []byte("INCOMPLETED_MOVE")

type MoveInfo struct {
	Address         oid.Address
	TargetStorageID []byte
}

func (b *Blobovnicza) PutMoveInfo(ctx context.Context, prm MoveInfo) error {
	_, span := tracing.StartSpanFromContext(ctx, "Blobovnicza.PutMoveInfo",
		trace.WithAttributes(
			attribute.String("path", b.path),
			attribute.String("address", prm.Address.EncodeToString()),
			attribute.String("target_storage_id", string(prm.TargetStorageID)),
		))
	defer span.End()

	key := addressKey(prm.Address)

	err := b.boltDB.Update(func(tx *bbolt.Tx) error {
		bucket, err := tx.CreateBucketIfNotExists(incompletedMoveBucketName)
		if err != nil {
			return err
		}

		if err := bucket.Put(key, prm.TargetStorageID); err != nil {
			return fmt.Errorf("(%T) failed to save move info: %w", b, err)
		}

		return nil
	})

	if errors.Is(err, syscall.ENOSPC) {
		err = ErrNoSpace
	}
	return err
}

func (b *Blobovnicza) DropMoveInfo(ctx context.Context, address oid.Address) error {
	_, span := tracing.StartSpanFromContext(ctx, "Blobovnicza.DropMoveInfo",
		trace.WithAttributes(
			attribute.String("path", b.path),
			attribute.String("address", address.EncodeToString()),
		))
	defer span.End()

	key := addressKey(address)

	err := b.boltDB.Update(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket(incompletedMoveBucketName)
		if bucket == nil {
			return nil
		}

		if err := bucket.Delete(key); err != nil {
			return fmt.Errorf("(%T) failed to drop move info: %w", b, err)
		}

		c := bucket.Cursor()
		k, v := c.First()
		bucketEmpty := k == nil && v == nil
		if bucketEmpty {
			return tx.DeleteBucket(incompletedMoveBucketName)
		}

		return nil
	})
	if errors.Is(err, syscall.ENOSPC) {
		err = ErrNoSpace
	}
	return err
}

func (b *Blobovnicza) ListMoveInfo(ctx context.Context) ([]MoveInfo, error) {
	_, span := tracing.StartSpanFromContext(ctx, "Blobovnicza.ListMoveInfo",
		trace.WithAttributes(
			attribute.String("path", b.path),
		))
	defer span.End()

	var result []MoveInfo
	if err := b.boltDB.View(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket(incompletedMoveBucketName)
		if bucket == nil {
			return nil
		}
		return bucket.ForEach(func(k, v []byte) error {
			var addr oid.Address
			storageID := make([]byte, len(v))
			if err := addressFromKey(&addr, k); err != nil {
				return err
			}
			copy(storageID, v)
			result = append(result, MoveInfo{
				Address:         addr,
				TargetStorageID: storageID,
			})
			return nil
		})
	}); err != nil {
		return nil, err
	}

	return result, nil
}