package rbd

import (
	"errors"
	"testing"

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

func TestGroupSnapshots(t *testing.T) {
	// tests are done as subtests to avoid creating pools, images, etc
	// over and over again.
	conn := radosConnect(t)
	require.NotNil(t, conn)
	defer conn.Shutdown()

	poolname := GetUUID()
	err := conn.MakePool(poolname)
	require.NoError(t, err)
	defer conn.DeletePool(poolname)

	ioctx, err := conn.OpenIOContext(poolname)
	require.NoError(t, err)
	defer ioctx.Destroy()

	// create a group, some images, and add images to the group
	gname := "snapme"
	err = GroupCreate(ioctx, gname)
	assert.NoError(t, err)

	options := NewRbdImageOptions()
	assert.NoError(t,
		options.SetUint64(ImageOptionOrder, uint64(testImageOrder)))

	name1 := GetUUID()
	err = CreateImage(ioctx, name1, testImageSize, options)
	require.NoError(t, err)

	name2 := GetUUID()
	err = CreateImage(ioctx, name2, testImageSize, options)
	require.NoError(t, err)

	err = GroupImageAdd(ioctx, gname, ioctx, name1)
	assert.NoError(t, err)
	err = GroupImageAdd(ioctx, gname, ioctx, name2)
	assert.NoError(t, err)

	t.Run("groupSnapCreateRemove", func(t *testing.T) {
		err := GroupSnapCreate(ioctx, gname, "snap1")
		assert.NoError(t, err)
		err = GroupSnapRemove(ioctx, gname, "snap1")
		assert.NoError(t, err)
	})
	t.Run("groupSnapRename", func(t *testing.T) {
		err := GroupSnapCreate(ioctx, gname, "snap2a")
		assert.NoError(t, err)
		err = GroupSnapRename(ioctx, gname, "fred", "wilma")
		assert.Error(t, err)
		err = GroupSnapRename(ioctx, gname, "snap2a", "snap2b")
		assert.NoError(t, err)
		err = GroupSnapRemove(ioctx, gname, "snap2a")
		assert.Error(t, err, "remove of old name: expect error")
		err = GroupSnapRemove(ioctx, gname, "snap2b")
		assert.NoError(t, err, "remove of current name: expect success")
	})
	t.Run("groupSnappList", func(t *testing.T) {
		err := GroupSnapCreate(ioctx, gname, "snap1")
		assert.NoError(t, err)
		err = GroupSnapCreate(ioctx, gname, "snap2")
		assert.NoError(t, err)
		err = GroupSnapCreate(ioctx, gname, "snap3")
		assert.NoError(t, err)

		gsl, err := GroupSnapList(ioctx, gname)
		assert.NoError(t, err)
		if assert.Len(t, gsl, 3) {
			names := []string{}
			for _, gsi := range gsl {
				assert.Equal(t, GroupSnapStateComplete, gsi.State)
				names = append(names, gsi.Name)
			}
			assert.Contains(t, names, "snap1")
			assert.Contains(t, names, "snap2")
			assert.Contains(t, names, "snap3")
		}

		err = GroupSnapRemove(ioctx, gname, "snap3")
		assert.NoError(t, err)
		err = GroupSnapRemove(ioctx, gname, "snap2")
		assert.NoError(t, err)
		err = GroupSnapRename(ioctx, gname, "snap1", "snap1a")

		gsl, err = GroupSnapList(ioctx, gname)
		assert.NoError(t, err)
		if assert.Len(t, gsl, 1) {
			assert.Equal(t, GroupSnapStateComplete, gsl[0].State)
			assert.Equal(t, "snap1a", gsl[0].Name)
		}

		err = GroupSnapRemove(ioctx, gname, "snap1a")
		assert.NoError(t, err)

		gsl, err = GroupSnapList(ioctx, gname)
		assert.NoError(t, err)
		assert.Len(t, gsl, 0)
	})
	t.Run("groupSnapGetInfo", func(t *testing.T) {
		err := GroupSnapCreate(ioctx, gname, "snapDetails")
		assert.NoError(t, err)
		defer func() {
			err = GroupSnapRemove(ioctx, gname, "snapDetails")
			assert.NoError(t, err)
		}()

		info, err := GroupSnapGetInfo(ioctx, gname, "snapDetails")
		if errors.Is(err, ErrNotImplemented) {
			t.Skipf("GroupSnapGetInfo is not supported: %v", err)
		}
		assert.NoError(t, err)

		assert.Equal(t, GroupSnapStateComplete, info.State)

		names := make([]string, len(info.Snapshots))
		for i, snap := range info.Snapshots {
			names[i] = snap.Name
		}
		assert.Contains(t, names, name1)
		assert.Contains(t, names, name2)
	})
	t.Run("groupSnapRollback", func(t *testing.T) {
		img, err := OpenImage(ioctx, name1, NoSnapshot)
		assert.NoError(t, err)
		_, err = img.WriteAt([]byte("HELLO WORLD"), 0)
		assert.NoError(t, err)
		err = img.Close()
		assert.NoError(t, err)

		snapname := "snap1"
		err = GroupSnapCreate(ioctx, gname, snapname)
		assert.NoError(t, err)

		img, err = OpenImage(ioctx, name1, NoSnapshot)
		assert.NoError(t, err)
		_, err = img.WriteAt([]byte("GOODBYE WORLD"), 0)
		assert.NoError(t, err)
		err = img.Close()
		assert.NoError(t, err)

		img, err = OpenImage(ioctx, name2, NoSnapshot)
		assert.NoError(t, err)
		_, err = img.WriteAt([]byte("2222222222222"), 0)
		assert.NoError(t, err)
		err = img.Close()
		assert.NoError(t, err)

		err = GroupSnapRollback(ioctx, gname, snapname)
		assert.NoError(t, err)

		b := make([]byte, 8)
		img, err = OpenImage(ioctx, name1, NoSnapshot)
		assert.NoError(t, err)
		_, err = img.ReadAt(b, 0)
		assert.NoError(t, err)
		err = img.Close()
		assert.NoError(t, err)
		assert.Equal(t, []byte("HELLO WO"), b)

		img, err = OpenImage(ioctx, name2, NoSnapshot)
		assert.NoError(t, err)
		_, err = img.ReadAt(b, 0)
		assert.NoError(t, err)
		err = img.Close()
		assert.NoError(t, err)
		assert.Equal(t, []byte("\x00\x00\x00\x00\x00\x00\x00\x00"), b)

		err = GroupSnapRemove(ioctx, gname, snapname)
		assert.NoError(t, err)
	})
	t.Run("groupSnapRollbackWithProgress", func(t *testing.T) {
		img, err := OpenImage(ioctx, name1, NoSnapshot)
		assert.NoError(t, err)
		_, err = img.WriteAt([]byte("SAY CHEESE"), 0)
		assert.NoError(t, err)
		_, err = img.WriteAt([]byte("AND SMILE_"), 10240)
		assert.NoError(t, err)
		err = img.Close()
		assert.NoError(t, err)

		snapname := "snap2r"
		err = GroupSnapCreate(ioctx, gname, snapname)
		assert.NoError(t, err)

		img, err = OpenImage(ioctx, name1, NoSnapshot)
		assert.NoError(t, err)
		_, err = img.WriteAt([]byte("GOODBYE WORLD"), 0)
		assert.NoError(t, err)
		_, err = img.WriteAt([]byte("_THIS_IS_ALL_"), 10240)
		assert.NoError(t, err)
		_, err = img.WriteAt([]byte("I_HAVE_TO_SAY"), 11240)
		assert.NoError(t, err)
		err = img.Close()
		assert.NoError(t, err)

		img, err = OpenImage(ioctx, name2, NoSnapshot)
		assert.NoError(t, err)
		_, err = img.WriteAt([]byte("3333333333333"), 0)
		assert.NoError(t, err)
		err = img.Close()
		assert.NoError(t, err)

		cc := 0
		cb := func(_, total uint64, v interface{}) int {
			cc++
			val := v.(int)
			assert.Equal(t, 0, val)
			assert.Equal(t, uint64(1), total)
			return 0
		}
		err = GroupSnapRollbackWithProgress(ioctx, gname, snapname, cb, 0)
		assert.NoError(t, err)
		assert.GreaterOrEqual(t, cc, 2)

		b := make([]byte, 8)
		img, err = OpenImage(ioctx, name1, NoSnapshot)
		assert.NoError(t, err)
		_, err = img.ReadAt(b, 0)
		assert.NoError(t, err)
		err = img.Close()
		assert.NoError(t, err)
		assert.Equal(t, []byte("SAY CHEE"), b)

		img, err = OpenImage(ioctx, name2, NoSnapshot)
		assert.NoError(t, err)
		_, err = img.ReadAt(b, 0)
		assert.NoError(t, err)
		err = img.Close()
		assert.NoError(t, err)
		assert.Equal(t, []byte("\x00\x00\x00\x00\x00\x00\x00\x00"), b)

		err = GroupSnapRemove(ioctx, gname, snapname)
		assert.NoError(t, err)
	})
	t.Run("invalidIOContext", func(t *testing.T) {
		assert.Panics(t, func() {
			GroupSnapCreate(nil, gname, "foo")
		})
		assert.Panics(t, func() {
			GroupSnapRemove(nil, gname, "foo")
		})
		assert.Panics(t, func() {
			GroupSnapRename(nil, gname, "foo", "bar")
		})
		assert.Panics(t, func() {
			GroupSnapList(nil, gname)
		})
		assert.Panics(t, func() {
			GroupSnapRollback(nil, gname, "foo")
		})
		assert.Panics(t, func() {
			cb := func(_, _ uint64, _ interface{}) int { return 0 }
			GroupSnapRollbackWithProgress(nil, gname, "foo", cb, nil)
		})
	})
	t.Run("invalidCallback", func(t *testing.T) {
		err := GroupSnapRollbackWithProgress(ioctx, gname, "foo", nil, nil)
		assert.Error(t, err)
	})
}
