// package store -- главный тип плагина хранилища
package store

import (
	"fmt"
	"sync"
	"wartank/pkg/components/kernel/logger"
	"wartank/pkg/components/safe_bool"
	"wartank/pkg/types"

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

const (
	стрХранилище = "Хранилище"
)

// Хранилище -- главный тип плагина хранилища
type Хранилище struct {
	ядро   types.ИЯдро
	путь   string              // Путь к хранилищу
	isWork *safe_bool.БезопБул // Признак открытой базы
	бд     *leveldb.DB         // Объект хранилища
	блок   sync.Mutex          // Блокировка для безопасности потоков
	лог    types.ИЛоггер
}

// НовХранилище -- возвращает новое хранилище
func НовХранилище(ядро types.ИЯдро, путь string) (*Хранилище, error) {
	лог := logger.НовЛоггер("Хранилище")
	лог.Инфо("НовХранилище()\n")
	лог.Паника(ядро == nil, "НовХранилище(): ИЯдро == nil\n")
	лог.Паника(путь == "", "НовХранилище(): путь пустой\n")
	сам := &Хранилище{
		ядро:   ядро,
		путь:   путь,
		isWork: safe_bool.НовБезопБул(),
		лог:    лог,
	}
	сам.открыть()
	if ош := ядро.Wg().Add(стрХранилище); ош != nil {
		return nil, fmt.Errorf("НовХранилище: при добавлении группы ожидания, ош=\n\t%v", ош)
	}
	_ = types.ИХранилище(сам)
	return сам, nil
}

// Уст -- добавляет в хранилище значение по ключу
func (сам *Хранилище) Уст(ключ string, знач []byte) error {
	сам.блок.Lock()
	defer сам.блок.Unlock()
	if ключ == "" {
		return fmt.Errorf("Хранилище.Уст(): ключ пустой")
	}
	if знач == nil {
		return fmt.Errorf("Хранилище.Уст(): знач == nil")
	}
	if ош := сам.бд.Put([]byte(ключ), знач, nil); ош != nil {
		return fmt.Errorf("Хранилище.Уст(): при устнановке значения, ош=\n\t%w", ош)
	}
	return nil
}

// Получ -- возвращает значение по ключу
func (sf *Хранилище) Получ(ключ string) ([]byte, error) {
	sf.блок.Lock()
	defer sf.блок.Unlock()
	value, err := sf.бд.Get([]byte(ключ), nil)
	if err != nil {
		return nil, fmt.Errorf("Хранилище.Get(): in get value, err=\n\t%w", err)
	}
	return value, nil
}

// Найти -- возвращает список ключей по префиксу
func (sf *Хранилище) Найти(prefix string) ([]string, error) {
	sf.блок.Lock()
	defer sf.блок.Unlock()
	var keys []string
	slice := &util.Range{Start: []byte(prefix)}
	iter := sf.бд.NewIterator(slice, nil)
	for iter.Next() {
		key := string(iter.Key())
		keys = append(keys, key)
	}
	iter.Release()
	if err := iter.Error(); err != nil {
		return nil, fmt.Errorf("Хранилище.Find(): in find keys, err=\n\t%w", err)
	}
	return keys, nil
}

// Удалить -- удаляет значение по ключу
func (sf *Хранилище) Удалить(key string) error {
	sf.блок.Lock()
	defer sf.блок.Unlock()
	if err := sf.бд.Delete([]byte(key), nil); err != nil {
		return fmt.Errorf("Хранилище.Del(): in delete key, err=\n\t%w", err)
	}
	return nil
}

// открыть -- открывает хранилище
func (сам *Хранилище) открыть() {
	сам.блок.Lock()
	defer сам.блок.Unlock()
	бд, ош := leveldb.OpenFile(сам.путь, nil)
	фнОткрыть := func() {
		сам.бд = бд
		сам.isWork.Уст()
		go сам.закрыть()
	}
	if ош == nil {
		фнОткрыть()
		return
	}
	сам.лог.Внимание("Хранилище.открыть(): при открытии БД, ош=\n\t%v\n", ош)
	бд, ош = leveldb.RecoverFile(сам.путь, nil)
	сам.лог.Паника(ош != nil, "Хранилище.открыть(): при восстановлении БД, ош=\n\t%v\n", ош)
	фнОткрыть()
}

// закрыть -- закрывает хранилище по требованию
func (сам *Хранилище) закрыть() {
	<-сам.ядро.Контекст().Done()
	сам.блок.Lock()
	defer сам.блок.Unlock()
	сам.isWork.Сброс()
	err := сам.бд.Close()
	сам.лог.Паника(err != nil, "Хранилище.закрыть(): при закрытии БД, err=\n\t%v\n", err)
	ош := сам.ядро.Wg().Done(стрХранилище)
	сам.лог.Паника(ош != nil, "Хранилище.закрыть(): при удалении группы ожидания, ош=\n\t%v\n", ош)
}
