package jsondb_test

import (
	"io/ioutil"
	"os"
	"path"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/osbuild/osbuild-composer/internal/jsondb"
)

type document struct {
	Animal  string `json:"animal"`
	CanSwim bool   `json:"can-swim"`
}

func cleanupTempDir(t *testing.T, dir string) {
	err := os.RemoveAll(dir)
	require.NoError(t, err)
}

// If the passed directory is not readable (writable), we should notice on the
// first read (write).
func TestDegenerate(t *testing.T) {
	t.Run("no-exist", func(t *testing.T) {
		db := jsondb.New("/non-existant-directory", 0755)

		var d document
		exist, err := db.Read("one", &d)
		assert.False(t, exist)
		assert.NoError(t, err)

		err = db.Write("one", &d)
		assert.Error(t, err)

		l, err := db.List()
		assert.Error(t, err)
		assert.Nil(t, l)
	})

	t.Run("invalid-json", func(t *testing.T) {
		dir, err := ioutil.TempDir("", "jsondb-test-")
		require.NoError(t, err)
		defer cleanupTempDir(t, dir)

		db := jsondb.New(dir, 0755)

		// write-only file
		err = ioutil.WriteFile(path.Join(dir, "one.json"), []byte("{"), 0644)
		require.NoError(t, err)

		var d document
		_, err = db.Read("one", &d)
		assert.Error(t, err)
	})
}

func TestCorrupt(t *testing.T) {
	dir, err := ioutil.TempDir("", "jsondb-test-")
	require.NoError(t, err)
	defer cleanupTempDir(t, dir)

	err = ioutil.WriteFile(path.Join(dir, "one.json"), []byte("{"), 0755)
	require.NoError(t, err)

	db := jsondb.New(dir, 0755)
	var d document
	_, err = db.Read("one", &d)
	require.Error(t, err)
}

func TestRead(t *testing.T) {
	dir, err := ioutil.TempDir("", "jsondb-test-")
	require.NoError(t, err)
	defer cleanupTempDir(t, dir)

	err = ioutil.WriteFile(path.Join(dir, "one.json"), []byte("true"), 0755)
	require.NoError(t, err)

	db := jsondb.New(dir, 0755)

	var b bool
	exists, err := db.Read("one", &b)
	require.NoError(t, err)
	require.True(t, exists)
	require.True(t, b)

	// nil means don't deserialize
	exists, err = db.Read("one", nil)
	require.NoError(t, err)
	require.True(t, exists)

	b = false
	exists, err = db.Read("two", &b)
	require.NoError(t, err)
	require.False(t, exists)
	require.False(t, b)

	// nil means don't deserialize
	exists, err = db.Read("two", nil)
	require.NoError(t, err)
	require.False(t, exists)
}

func TestMultiple(t *testing.T) {
	dir, err := ioutil.TempDir("", "jsondb-test-")
	require.NoError(t, err)
	defer cleanupTempDir(t, dir)

	perm := os.FileMode(0600)
	documents := map[string]document{
		"one":   document{"octopus", true},
		"two":   document{"zebra", false},
		"three": document{"clownfish", true},
	}

	db := jsondb.New(dir, perm)

	for name, doc := range documents {
		err = db.Write(name, doc)
		require.NoError(t, err)
	}
	names, err := db.List()
	require.NoError(t, err)
	require.ElementsMatch(t, []string{"one", "two", "three"}, names)

	for name, doc := range documents {
		var d document
		exist, err := db.Read(name, &d)
		require.NoError(t, err)
		require.True(t, exist)
		require.Equalf(t, doc, d, "error retrieving document '%s'", name)
	}
}
