package meta

import (
	"bytes"
	"strings"

	"go.etcd.io/bbolt"
)

// CleanUpPrm groups the parameters of CleanUp operation.
type CleanUpPrm struct{}

// CleanUpRes groups resulting values of CleanUp operation.
type CleanUpRes struct{}

// CleanUp removes empty buckets from metabase.
func (db *DB) CleanUp(prm *CleanUpPrm) (res *CleanUpRes, err error) {
	err = db.boltDB.Update(func(tx *bbolt.Tx) error {
		return tx.ForEach(func(name []byte, b *bbolt.Bucket) error {
			switch {
			case isFKBTBucket(name):
				cleanUpFKBT(tx, name, b)
			case isListBucket(name):
				cleanUpListBucket(tx, name, b)
			default:
				cleanUpUniqueBucket(tx, name, b)
			}

			return nil
		})
	})

	return
}

func isFKBTBucket(name []byte) bool {
	bucketName := string(name)

	switch {
	case
		strings.Contains(bucketName, userAttributePostfix),
		strings.Contains(bucketName, ownerPostfix):
		return true
	default:
		return false
	}
}

func isListBucket(name []byte) bool {
	bucketName := string(name)

	switch {
	case
		strings.Contains(bucketName, payloadHashPostfix),
		strings.Contains(bucketName, parentPostfix):
		return true
	default:
		return false
	}
}

func cleanUpUniqueBucket(tx *bbolt.Tx, name []byte, b *bbolt.Bucket) {
	switch { // clean well-known global metabase buckets
	case bytes.Equal(name, containerVolumeBucketName):
		_ = b.ForEach(func(k, v []byte) error {
			if parseContainerSize(v) == 0 {
				_ = b.Delete(k)
			}

			return nil
		})
	default:
		// do nothing
	}

	if b.Stats().KeyN == 0 {
		_ = tx.DeleteBucket(name) // ignore error, best effort there
	}
}

func cleanUpFKBT(tx *bbolt.Tx, name []byte, b *bbolt.Bucket) {
	removedBuckets := 0
	remainingBuckets := b.Stats().BucketN - 1

	_ = b.ForEach(func(k, _ []byte) error {
		fkbtRoot := b.Bucket(k)
		if fkbtRoot == nil {
			return nil
		}

		if fkbtRoot.Stats().KeyN == 0 {
			err := b.DeleteBucket(k)
			if err == nil {
				removedBuckets++
			}
		}

		return nil
	})

	if remainingBuckets == removedBuckets {
		_ = tx.DeleteBucket(name) // ignore error, best effort there
	}
}

func cleanUpListBucket(tx *bbolt.Tx, name []byte, b *bbolt.Bucket) {
	cleanUpUniqueBucket(tx, name, b)
}