package leveldb

import (
	"log"
	"strings"

	"github.com/syndtr/goleveldb/leveldb"
	dberr "github.com/syndtr/goleveldb/leveldb/errors"
	"github.com/syndtr/goleveldb/leveldb/filter"
	"github.com/syndtr/goleveldb/leveldb/opt"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/pkg/storage"
)

// storageImpl implements the db.Database interface.
// It uses a LevelDB database to store key-value pairs.
type storageImpl struct {
	// The LevelDB database instance.
	db          *leveldb.DB
	transaction *leveldb.Transaction
}

// NewDiskStorage creates a new instance of the storageImpl.
// It opens the database file at the specified path and returns an error if it fails.
func NewDiskStorage(provider LevelDBConfigurationProvider, logger *zap.Logger) storage.Storage {
	logger = logger.Named("leveldb")

	if provider == nil {
		log.Fatal("NewDiskStorage: missing LevelDB configuration provider\n")
	}
	if provider.GetDBPath() == "" {
		log.Fatal("NewDiskStorage: cannot have empty filepath for the database\n")
	}
	if provider.GetDBName() == "" {
		log.Fatal("NewDiskStorage: cannot have empty db name for the database\n")
	}

	o := &opt.Options{
		Filter: filter.NewBloomFilter(10),
	}

	filePath := provider.GetDBPath() + "/" + provider.GetDBName()

	db, err := leveldb.OpenFile(filePath, o)
	if err != nil {
		log.Fatalf("NewDiskStorage: failed loading up key value storer adapter at %v with error: %v\n", filePath, err)
	}
	return &storageImpl{
		db: db,
	}
}

// Get retrieves a value from the database by its key.
// It returns an error if the key is not found.
func (impl *storageImpl) Get(k string) ([]byte, error) {
	if impl.transaction == nil {
		bin, err := impl.db.Get([]byte(k), nil)
		if err == dberr.ErrNotFound {
			return nil, nil
		}
		return bin, nil
	}

	bin, err := impl.transaction.Get([]byte(k), nil)
	if err == dberr.ErrNotFound {
		return nil, nil
	}
	return bin, nil
}

// Set sets a value in the database by its key.
// It returns an error if the operation fails.
func (impl *storageImpl) Set(k string, val []byte) error {
	if impl.transaction == nil {
		impl.db.Delete([]byte(k), nil)
		err := impl.db.Put([]byte(k), val, nil)
		if err == dberr.ErrNotFound {
			return nil
		}
		return err
	}

	impl.transaction.Delete([]byte(k), nil)
	err := impl.transaction.Put([]byte(k), val, nil)
	if err == dberr.ErrNotFound {
		return nil
	}
	return err
}

// Delete deletes a value from the database by its key.
// It returns an error if the operation fails.
func (impl *storageImpl) Delete(k string) error {
	if impl.transaction == nil {
		err := impl.db.Delete([]byte(k), nil)
		if err == dberr.ErrNotFound {
			return nil
		}
		return err
	}

	err := impl.transaction.Delete([]byte(k), nil)
	if err == dberr.ErrNotFound {
		return nil
	}
	return err
}

// Iterate iterates over the key-value pairs in the database, starting from the specified key prefix.
// It calls the provided function for each pair.
// It returns an error if the iteration fails.
func (impl *storageImpl) Iterate(processFunc func(key, value []byte) error) error {
	if impl.transaction == nil {
		iter := impl.db.NewIterator(nil, nil)
		for ok := iter.First(); ok; ok = iter.Next() {
			// Call the passed function for each key-value pair.
			err := processFunc(iter.Key(), iter.Value())
			if err == dberr.ErrNotFound {
				return nil
			}
			if err != nil {
				return err // Exit early if the processing function returns an error.
			}
		}
		iter.Release()
		return iter.Error()
	}

	iter := impl.transaction.NewIterator(nil, nil)
	for ok := iter.First(); ok; ok = iter.Next() {
		// Call the passed function for each key-value pair.
		err := processFunc(iter.Key(), iter.Value())
		if err == dberr.ErrNotFound {
			return nil
		}
		if err != nil {
			return err // Exit early if the processing function returns an error.
		}
	}
	iter.Release()
	return iter.Error()
}

func (impl *storageImpl) IterateWithFilterByKeys(ks []string, processFunc func(key, value []byte) error) error {
	if impl.transaction == nil {
		iter := impl.db.NewIterator(nil, nil)
		for ok := iter.First(); ok; ok = iter.Next() {
			// Iterate over our keys to search by.
			for _, k := range ks {
				searchKey := strings.ToLower(k)
				targetKey := strings.ToLower(string(iter.Key()))

				// If the item we currently have matches our keys then execute.
				if searchKey == targetKey {
					// Call the passed function for each key-value pair.
					err := processFunc(iter.Key(), iter.Value())
					if err == dberr.ErrNotFound {
						return nil
					}
					if err != nil {
						return err // Exit early if the processing function returns an error.
					}
				}
			}
		}
		iter.Release()
		return iter.Error()
	}

	iter := impl.transaction.NewIterator(nil, nil)
	for ok := iter.First(); ok; ok = iter.Next() {
		// Call the passed function for each key-value pair.
		err := processFunc(iter.Key(), iter.Value())
		if err == dberr.ErrNotFound {
			return nil
		}
		if err != nil {
			return err // Exit early if the processing function returns an error.
		}
	}
	iter.Release()
	return iter.Error()
}

// Close closes the database.
// It returns an error if the operation fails.
func (impl *storageImpl) Close() error {
	if impl.transaction != nil {
		impl.transaction.Discard()
	}
	return impl.db.Close()
}

func (impl *storageImpl) OpenTransaction() error {
	transaction, err := impl.db.OpenTransaction()
	if err != nil {
		return nil
	}
	impl.transaction = transaction
	return nil
}

func (impl *storageImpl) CommitTransaction() error {
	defer func() {
		impl.transaction = nil
	}()

	// Commit the snapshot to the database
	return impl.transaction.Commit()
}

func (impl *storageImpl) DiscardTransaction() {
	defer func() {
		impl.transaction = nil
	}()
	impl.transaction.Discard()
}
