// Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.

package events

import (
	"encoding/base64"
	"encoding/json"
	"testing"

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

func TestUnmarshalBinary(t *testing.T) {
	input := []byte(`{ "B": "AAEqQQ=="}`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeBinary, av.DataType())
	assert.Equal(t, "AAEqQQ==", base64.StdEncoding.EncodeToString(av.Binary()))
}

func TestUnmarshalBoolean(t *testing.T) {
	input := []byte(`{ "BOOL": true}`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeBoolean, av.DataType())
	assert.Equal(t, true, av.Boolean())
}

func TestUnmarshalBinarySet(t *testing.T) {
	input := []byte(`{ "BS": ["AAEqQQ==", "AAEqQQ=="] }`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeBinarySet, av.DataType())
	assert.Equal(t, 2, len(av.BinarySet()))
	assert.Equal(t, "AAEqQQ==", base64.StdEncoding.EncodeToString(av.BinarySet()[1]))
}

func TestUnmarshalList(t *testing.T) {
	input := []byte(`{ "L": [
            { "S": "Cookies"},
            { "S": "Coffee"},
            { "N": "3.14159" }
        ] }`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeList, av.DataType())
	assert.Equal(t, 3, len(av.List()))
	assert.Equal(t, "Cookies", av.List()[0].String())
	assert.Equal(t, "3.14159", av.List()[2].Number())
}

func TestUnmarshalMap(t *testing.T) {
	input := []byte(`
        { "M":
            {
                "Name": { "S": "Joe" },
                "Age":  { "N": "35" }
            }
        }`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeMap, av.DataType())
	assert.Equal(t, 2, len(av.Map()))
	assert.Equal(t, "Joe", av.Map()["Name"].String())
	assert.Equal(t, "35", av.Map()["Age"].Number())
}

func TestUnmarshalNumber(t *testing.T) {
	input := []byte(`{ "N": "123.45"}`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeNumber, av.DataType())
	assert.Equal(t, "123.45", av.Number())
}

func TestUnmarshalInteger(t *testing.T) {
	input := []byte(`{ "N": "123"}`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)

	var i int64
	i, err = av.Integer()
	assert.Nil(t, err)
	assert.Equal(t, int64(123), i)
}

func TestUnmarshalFloat(t *testing.T) {
	input := []byte(`{ "N": "123.45"}`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)
	assert.Nil(t, err)

	var f float64
	f, err = av.Float()

	assert.Nil(t, err)
	assert.Equal(t, 123.45, f)
}

func TestUnmarshalIntContainingAFloatString(t *testing.T) {
	input := []byte(`{ "N": "123.45"}`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)

	var i int64
	i, err = av.Integer()
	assert.Nil(t, err)
	assert.Equal(t, int64(123), i)
}

func TestUnmarshalNumberSet(t *testing.T) {
	input := []byte(`{ "NS": ["1234", "567.8"] }`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeNumberSet, av.DataType())
	assert.Equal(t, 2, len(av.NumberSet()))
	assert.Equal(t, "1234", av.NumberSet()[0])
	assert.Equal(t, "567.8", av.NumberSet()[1])
}

func TestUnmarshalNull(t *testing.T) {
	input := []byte(`{ "NULL": true}`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeNull, av.DataType())
	assert.True(t, av.IsNull())
}

func TestUnmarshalString(t *testing.T) {
	input := []byte(`{ "S": "Hello"}`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeString, av.DataType())
	assert.Equal(t, "Hello", av.String())
}

func TestUnmarshalStringSet(t *testing.T) {
	input := []byte(`{ "SS": [ "Giraffe", "Zebra" ] }`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, 2, len(av.StringSet()))
	assert.Equal(t, DataTypeStringSet, av.DataType())
	assert.Equal(t, "Giraffe", av.StringSet()[0])
	assert.Equal(t, "Zebra", av.StringSet()[1])
}

func TestUnmarshalEmptyStringSet(t *testing.T) {
	input := []byte(`{ "SS": [ ] }`)

	var av DynamoDBAttributeValue
	err := json.Unmarshal(input, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeStringSet, av.DataType())
	assert.Equal(t, 0, len(av.StringSet()))
}

func TestAccessWithWrongTypePanics(t *testing.T) {
	testCases := []struct {
		input         string
		accessor      func(av DynamoDBAttributeValue)
		expectedError IncompatibleDynamoDBTypeError
	}{
		{`{ "B": "AAEqQQ=="}`, func(av DynamoDBAttributeValue) { av.Number() }, IncompatibleDynamoDBTypeError{Requested: DataTypeNumber, Actual: DataTypeBinary}},
		{`{ "BOOL": true}`, func(av DynamoDBAttributeValue) { av.Number() }, IncompatibleDynamoDBTypeError{Requested: DataTypeNumber, Actual: DataTypeBoolean}},
		{`{ "BS": ["AAEqQQ==", "AAEqQQ=="] }`, func(av DynamoDBAttributeValue) { av.Number() }, IncompatibleDynamoDBTypeError{Requested: DataTypeNumber, Actual: DataTypeBinarySet}},
		{`{ "L": [ { "S": "Cookies"} ] }`, func(av DynamoDBAttributeValue) { av.Number() }, IncompatibleDynamoDBTypeError{Requested: DataTypeNumber, Actual: DataTypeList}},
		{`{ "M": { "Name": { "S": "Joe" } } }`, func(av DynamoDBAttributeValue) { av.Number() }, IncompatibleDynamoDBTypeError{Requested: DataTypeNumber, Actual: DataTypeMap}},
		{`{ "N": "123.45"}`, func(av DynamoDBAttributeValue) { av.Boolean() }, IncompatibleDynamoDBTypeError{Requested: DataTypeBoolean, Actual: DataTypeNumber}},
		{`{ "NS": ["1234", "567.8"] }`, func(av DynamoDBAttributeValue) { av.Boolean() }, IncompatibleDynamoDBTypeError{Requested: DataTypeBoolean, Actual: DataTypeNumberSet}},
		{`{ "NULL": true}`, func(av DynamoDBAttributeValue) { av.Number() }, IncompatibleDynamoDBTypeError{Requested: DataTypeNumber, Actual: DataTypeNull}},
		{`{ "S": "Hello"}`, func(av DynamoDBAttributeValue) { av.Number() }, IncompatibleDynamoDBTypeError{Requested: DataTypeNumber, Actual: DataTypeString}},
		{`{ "SS": [ "Giraffe", "Zebra" ] }`, func(av DynamoDBAttributeValue) { av.Number() }, IncompatibleDynamoDBTypeError{Requested: DataTypeNumber, Actual: DataTypeStringSet}},
	}

	for _, testCase := range testCases {
		testCase := testCase
		var av DynamoDBAttributeValue
		err := json.Unmarshal([]byte(testCase.input), &av)
		assert.Nil(t, err)
		assert.PanicsWithValue(t, testCase.expectedError, func() { testCase.accessor(av) })
	}
}

func TestMarshalAndUnmarshalString(t *testing.T) {
	const inputString = "INPUT STRING"
	inputValue := NewStringAttribute(inputString)
	marshaled, err := json.Marshal(inputValue)
	assert.Nil(t, err)

	var av DynamoDBAttributeValue
	err = json.Unmarshal(marshaled, &av)

	assert.Nil(t, err)
	assert.Equal(t, DataTypeString, av.DataType())
	assert.Equal(t, inputString, av.String())
}

func Test_DynamoDBAttributeValue_NewAttribute(t *testing.T) {
	{
		av := NewBinaryAttribute([]byte{1, 2, 3})
		assert.Equal(t, DataTypeBinary, av.DataType())
		assert.Equal(t, []byte{1, 2, 3}, av.Binary())
	}
	{
		av := NewBooleanAttribute(true)
		assert.Equal(t, DataTypeBoolean, av.DataType())
		assert.Equal(t, true, av.Boolean())
	}
	{
		av := NewBinarySetAttribute([][]byte{{1, 2, 3}})
		assert.Equal(t, DataTypeBinarySet, av.DataType())
		assert.Equal(t, [][]byte{{1, 2, 3}}, av.BinarySet())
	}
	{
		av := NewListAttribute([]DynamoDBAttributeValue{
			NewNumberAttribute("1"),
			NewStringAttribute("test"),
		})
		assert.Equal(t, DataTypeList, av.DataType())
		assert.Equal(t, 2, len(av.List()))
	}
	{
		value := map[string]DynamoDBAttributeValue{
			"n": NewNumberAttribute("1"),
			"s": NewStringAttribute("test"),
		}
		av := NewMapAttribute(value)
		assert.Equal(t, DataTypeMap, av.DataType())
		assert.Equal(t, 2, len(av.Map()))
	}
	{
		av := NewNumberAttribute("1")
		assert.Equal(t, DataTypeNumber, av.DataType())
		assert.Equal(t, "1", av.Number())
		v, err := av.Integer()
		assert.Nil(t, err)
		assert.Equal(t, int64(1), v)
	}
	{
		av := NewNumberAttribute("1.1")
		assert.Equal(t, DataTypeNumber, av.DataType())
		assert.Equal(t, "1.1", av.Number())
		v, err := av.Float()
		assert.Nil(t, err)
		assert.Equal(t, float64(1.1), v)
	}
	{
		value := []string{"2", "2.2"}
		av := NewNumberSetAttribute(value)
		assert.Equal(t, DataTypeNumberSet, av.DataType())
		assert.Equal(t, value, av.NumberSet())
	}
	{
		av := NewNullAttribute()
		assert.Equal(t, DataTypeNull, av.DataType())
		assert.Equal(t, true, av.IsNull())
	}
	{
		av := NewStringAttribute("test")
		assert.Equal(t, DataTypeString, av.DataType())
		assert.Equal(t, "test", av.String())
	}
	{
		av := NewStringSetAttribute([]string{"test", "test"})
		assert.Equal(t, DataTypeStringSet, av.DataType())
		assert.Equal(t, []string{"test", "test"}, av.StringSet())
	}
}
