package rados

import (
	"testing"

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

// timeStamp generates a dummy Timespec value.
func timeStamp() Timespec {
	// Future TODO (maybe?) - vary the value?
	return Timespec{342334800, 0}
}

func (suite *RadosTestSuite) TestWriteOpCreate() {
	suite.SetupConnection()

	op := CreateWriteOp()
	defer op.Release()
	op.Create(CreateIdempotent)
	err := op.Operate(suite.ioctx, "TestWriteOpCreate", OperationNoFlag)
	assert.NoError(suite.T(), err)

	op2 := CreateWriteOp()
	defer op2.Release()
	op2.Create(CreateExclusive)
	err = op2.Operate(suite.ioctx, "TestWriteOpCreate", OperationNoFlag)
	assert.Error(suite.T(), err)

	// ensure a nil ioctx triggers a panic
	assert.Panics(suite.T(), func() {
		op := CreateWriteOp()
		defer op.Release()
		_ = op.Operate(nil, "foo", OperationNoFlag)
	})
}

func (suite *RadosTestSuite) TestWriteOpCreateWithTimestamp() {
	suite.SetupConnection()

	oid := "TestWriteOpCreateWithTimestamp"
	gts := timeStamp()
	op := CreateWriteOp()
	defer op.Release()
	op.Create(CreateIdempotent)
	err := op.OperateWithMtime(suite.ioctx, oid, gts, OperationNoFlag)
	assert.NoError(suite.T(), err)

	s, err := suite.ioctx.Stat(oid)
	assert.NoError(suite.T(), err)
	statTime := s.ModTime.Unix()
	assert.Equal(suite.T(), gts.Sec, statTime)
}

func (suite *RadosTestSuite) TestWriteOpSetOmap() {
	suite.SetupConnection()
	ta := assert.New(suite.T())

	oid := "TestWriteOpSetOmap"
	op := CreateWriteOp()
	defer op.Release()
	op.Create(CreateIdempotent)
	op.SetOmap(map[string][]byte{
		"alice":   []byte("car"),
		"boss":    []byte("office"),
		"catbert": []byte("dungeon"),
	})
	err := op.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.NoError(err)

	// the 2nd set of omap values should not be applied because
	// the Create will fail to exclusively make the object
	op2 := CreateWriteOp()
	defer op2.Release()
	op.Create(CreateExclusive)
	op.SetOmap(map[string][]byte{
		"alice":   []byte("home"),
		"boss":    []byte("golf course"),
		"catbert": []byte("dungeon"),
	})
	err = op.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.Error(err)

	fetched, err := suite.ioctx.GetOmapValues(oid, "", "", 10)
	ta.NoError(err)
	ta.Equal("car", string(fetched["alice"]))
	ta.Equal("office", string(fetched["boss"]))
}

func (suite *RadosTestSuite) TestWriteOpRmOmapKeys() {
	suite.SetupConnection()
	ta := assert.New(suite.T())

	oid := "TestWriteOpRmOmapKeys"
	op := CreateWriteOp()
	defer op.Release()
	op.Create(CreateIdempotent)
	op.SetOmap(map[string][]byte{
		"alice":   []byte("car"),
		"boss":    []byte("office"),
		"catbert": []byte("dungeon"),
	})
	err := op.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.NoError(err)

	op2 := CreateWriteOp()
	defer op2.Release()
	op2.Create(CreateIdempotent)
	op2.SetOmap(map[string][]byte{
		"dogbert": []byte("lab"),
	})
	op2.RmOmapKeys([]string{"catbert"})
	err = op2.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.NoError(err)

	fetched, err := suite.ioctx.GetOmapValues(oid, "", "", 10)
	ta.NoError(err)
	ta.Len(fetched, 3)
	ta.Equal("car", string(fetched["alice"]))
	ta.Equal("office", string(fetched["boss"]))
	ta.Equal("lab", string(fetched["dogbert"]))
	ta.NotContains(fetched, "catbert")
}

func (suite *RadosTestSuite) TestWriteOpCleanOmap() {
	suite.SetupConnection()
	ta := assert.New(suite.T())

	oid := "TestWriteOpCleanOmap"
	op := CreateWriteOp()
	defer op.Release()
	op.Create(CreateIdempotent)
	op.SetOmap(map[string][]byte{
		"alice":   []byte("car"),
		"boss":    []byte("office"),
		"catbert": []byte("dungeon"),
	})
	err := op.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.NoError(err)

	// this test simulates wanting to start a fresh new set of
	// omap keys, atomically clearing and setting a new key & value.
	op2 := CreateWriteOp()
	defer op2.Release()
	op2.Create(CreateIdempotent)
	op2.CleanOmap()
	op2.SetOmap(map[string][]byte{
		"dogbert": []byte("lab"),
	})
	op2.RmOmapKeys([]string{"catbert"})
	err = op2.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.NoError(err)

	fetched, err := suite.ioctx.GetOmapValues(oid, "", "", 10)
	ta.NoError(err)
	ta.Len(fetched, 1)
	ta.Equal("lab", string(fetched["dogbert"]))
}

func (suite *RadosTestSuite) TestWriteOpAssertExists() {
	suite.SetupConnection()
	ta := assert.New(suite.T())

	oid := "TestWriteOpRmOmapKeys"
	op := CreateWriteOp()
	defer op.Release()
	op.Create(CreateIdempotent)
	err := op.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.NoError(err)

	op2 := CreateWriteOp()
	defer op2.Release()
	op2.AssertExists()
	op2.CleanOmap()
	err = op2.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.NoError(err)

	op3 := CreateWriteOp()
	defer op3.Release()
	op3.AssertExists()
	op3.CleanOmap()
	err = op3.Operate(suite.ioctx, oid+"dne", OperationNoFlag)
	ta.Error(err)
}

func (suite *RadosTestSuite) TestWriteOpWrite() {
	suite.SetupConnection()
	ta := assert.New(suite.T())

	oid := "TestWriteOpWrite"
	op := CreateWriteOp()
	defer op.Release()
	op.Create(CreateIdempotent)
	op.Write([]byte("go-go-gadget"), 0)
	op.Write([]byte("ceph project!"), 6)
	err := op.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.NoError(err)

	d := make([]byte, 32)
	l, err := suite.ioctx.Read(oid, d, 0)
	ta.NoError(err)
	ta.Equal("go-go-ceph project!", string(d[:l]))
}

func (suite *RadosTestSuite) TestWriteOpWriteFull() {
	suite.SetupConnection()
	ta := assert.New(suite.T())

	oid := "TestWriteOpWriteFull"
	op := CreateWriteOp()
	defer op.Release()
	op.Create(CreateIdempotent)
	op.WriteFull([]byte("one, two"))
	op.WriteFull([]byte("buckle my shoe"))
	err := op.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.NoError(err)

	d := make([]byte, 32)
	l, err := suite.ioctx.Read(oid, d, 0)
	ta.NoError(err)
	ta.Equal("buckle my shoe", string(d[:l]))
}

func (suite *RadosTestSuite) TestWriteOpWriteSame() {
	suite.SetupConnection()
	ta := assert.New(suite.T())

	oid := "TestWriteOpWriteSame"
	op := CreateWriteOp()
	defer op.Release()
	op.Create(CreateIdempotent)
	op.WriteSame([]byte("repetition "), 44, 0)
	op.Write([]byte("is fun"), 44)
	err := op.Operate(suite.ioctx, oid, OperationNoFlag)
	ta.NoError(err)

	d := make([]byte, 64)
	l, err := suite.ioctx.Read(oid, d, 0)
	ta.NoError(err)
	ta.Equal("repetition repetition repetition repetition is fun", string(d[:l]))
}

func TestWriteOpInvalid(t *testing.T) {
	r := &WriteOp{}
	err := r.Operate(&IOContext{}, "foo", 0)
	assert.Error(t, err)
}
