package kvstore

import (
	"bytes"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"

	"github.com/ooni/probe-cli/v3/internal/model"
	"github.com/rogpeppe/go-internal/lockedfile"
)

// FS is a file-system based KVStore.
type FS struct {
	basedir string
}

var _ model.KeyValueStore = &FS{}

// NewFS creates a new kvstore.FileSystem.
func NewFS(basedir string) (kvs *FS, err error) {
	return newFileSystem(basedir, os.MkdirAll)
}

// osMkdirAll is the type of os.MkdirAll.
type osMkdirAll func(path string, perm fs.FileMode) error

// newFileSystem is like NewFileSystem with a customizable
// osMkdirAll function for creating the kvstore dir.
func newFileSystem(basedir string, mkdir osMkdirAll) (*FS, error) {
	if err := mkdir(basedir, 0700); err != nil {
		return nil, err
	}
	return &FS{basedir: basedir}, nil
}

// filename returns the filename for a given key.
func (kvs *FS) filename(key string) string {
	return filepath.Join(kvs.basedir, key)
}

// Get returns the specified key's value. In case of error, the
// error type is such that errors.Is(err, ErrNoSuchKey).
func (kvs *FS) Get(key string) ([]byte, error) {
	data, err := lockedfile.Read(kvs.filename(key))
	if err != nil {
		return nil, fmt.Errorf("%w: %s", ErrNoSuchKey, err.Error())
	}
	return data, nil
}

// Set sets the value of a specific key.
func (kvs *FS) Set(key string, value []byte) error {
	return lockedfile.Write(kvs.filename(key), bytes.NewReader(value), 0600)
}
