// package store -- хранилище на диске
package store

import (
	"fmt"
	"log"
	"os"
	"strings"
	"sync"

	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/util"

	"git.p78su.freemyip.com/svi/gostore/pkg/types"
)

// StoreDisk -- хранилище на диске
type Store struct {
	serv   types.IServCtx
	db     *leveldb.DB
	isWork bool
	block  sync.RWMutex
}

var IsBad_ = false // Для тестов

// NewStore -- возвращает новое хранилище на диске
func NewStore(serv types.IServCtx) (types.IStore, error) {
	if IsBad_ {
		return nil, fmt.Errorf("NewStore(): IsBad_==true")
	}
	if serv == nil {
		return nil, fmt.Errorf("NewStore(): IServCtx==nil")
	}
	_ = os.MkdirAll("./store/db", 0750)
	db, err := leveldb.OpenFile("./store/db", nil)
	if err != nil {
		if !strings.Contains(err.Error(), "leveldb: manifest corrupted") {
			return nil, fmt.Errorf("NewStore(): in create IStoreDisk, err=\n\t%w", err)
		}
		db, err = leveldb.RecoverFile("./stor/db", nil)
		if err != nil {
			return nil, fmt.Errorf("NewStore(): in recovery DB, err=\n\t%w", err)
		}
	}
	sf := &Store{
		serv: serv,
		db:   db,
	}
	sf.serv.Wg().Add(1)
	sf.isWork = true
	go sf.close()
	return sf, nil
}

// Проверка на нерабочую схему
func (sf *Store) isClosed() bool {
	sf.block.RLock()
	defer sf.block.RUnlock()
	return !sf.isWork
}

// Put -- размещает в хранилище ключ и значение
func (sf *Store) Put(key string, val []byte) error {
	if sf.isClosed() {
		return fmt.Errorf("Store.Put(): store is closed")
	}
	if key == "" {
		return fmt.Errorf("Store.Put(): key is empty")
	}
	err := sf.db.Put([]byte(key), val, nil)
	if err != nil {
		return fmt.Errorf("Store.Put(): key=%q\terr=\n\t%w", key, err)
	}
	return nil
}

// Get -- возвращает значение ключа
func (sf *Store) Get(key string) ([]byte, error) {
	if sf.isClosed() {
		return nil, fmt.Errorf("Store.Get(): store is closed")
	}
	val, err := sf.db.Get([]byte(key), nil)
	if err != nil {
		return nil, fmt.Errorf("Store.Get(): key=%q\terr=\n\t%w", key, err)
	}
	return val, nil
}

// Find -- ищет ключи по префиксу
func (sf *Store) Find(prefixKey string) ([]string, error) {
	if sf.isClosed() {
		return nil, fmt.Errorf("Store.Find(): store is closed")
	}
	lstKey := []string{}
	iter := sf.db.NewIterator(util.BytesPrefix([]byte(prefixKey)), nil)
	for iter.Next() {
		key := iter.Key()
		lstKey = append(lstKey, string(key))
	}
	iter.Release()
	err := iter.Error()
	if err != nil {
		return nil, fmt.Errorf("Store.Find(): preefixKey=%q\terr=\n\t%w", prefixKey, err)
	}
	return lstKey, nil
}

// Del -- удаление ключа из базы
func (sf *Store) Del(key string) error {
	if sf.isClosed() {
		return fmt.Errorf("Store.Del(): store is closed")
	}
	err := sf.db.Delete([]byte(key), nil)
	if err != nil {
		return fmt.Errorf("Store.Del(): key=%q\terr=\n\t%w", key, err)
	}
	return nil
}

// Ожидание закрытия приложения
func (sf *Store) close() {
	<-sf.serv.Ctx().Done()
	sf.block.Lock()
	defer sf.block.Unlock()
	if !sf.isWork {
		return
	}
	sf.isWork = false
	err := sf.db.Close()
	if err != nil {
		log.Printf("Store.close(): err=\n\t%v\n", err)
	}
	sf.serv.Wg().Done()
}
