// load loads the session from dynamodb.
// It checks expiration date before it returns the session.
// returns error if session data does not exist in dynamodb or was expired.
func (s *Store) load(session *sessions.Session) error {
	data := make(map[string]interface{})

	err := s.Table.GetItem(
		attributes.String(session.ID), nil,
		&data,
		option.ConsistentRead(),
	)
	if err != nil {
		return err
	}

	expiresAtData, ok := data[SessionExpiresName]
	if !ok {
		return errSessionBroken
	}

	var expiresAtInt int64
	switch v := expiresAtData.(type) {
	case int64:
		expiresAtInt = v
	case int:
		expiresAtInt = int64(v)
		// otherwise it will be used as zero-value
	}

	expiresAt := time.Unix(expiresAtInt, 0)

	if time.Now().After(expiresAt) {
		s.delete(session)
		// Don't want to return nil even we delete the session successfully
		return errSessionExpired
	}

	value, ok := data[SessionDataKeyName]
	if !ok {
		return errSessionNotFound
	}

	blob, ok := value.([]byte)
	if !ok {
		return errSessionBroken
	}

	return gob.NewDecoder(bytes.NewReader(blob)).Decode(&session.Values)
}
func TestTable(t *testing.T) {
	name := os.Getenv("TEST_DYNAMODB_TABLE_NAME")
	if len(name) == 0 {
		t.Skip("TEST_DYNAMODB_TABLE_NAME must be set")
	}

	assert := assert.New(t)

	dtable := New(dynamodb.New(nil), name).
		WithHashKey("user_id", "S").
		WithRangeKey("date", "N")

	now := time.Now()

	items := []TestItem{
		{
			UserID: "foobar-1",
			Date:   now.Unix(),
			Status: "waiting",
		},
		TestItem{
			UserID: "foobar-1",
			Date:   now.Add(1 * time.Minute).Unix(),
			Status: "waiting",
		},
	}

	hashKey := attributes.String(items[0].UserID)
	rangeKey := attributes.Number(items[0].Date)
	status := attributes.String(items[0].Status)

	role := []string{"user", "manager"}
	sort.Strings(role)

	// Try to get non-exist key and it should return table.ErrItemNotFound
	{
		var actualItem TestItem
		err := dtable.GetItem(hashKey, rangeKey, &actualItem, option.ConsistentRead())
		assert.Equal(ErrItemNotFound, err)
	}

	{
		for _, item := range items {
			if err := dtable.PutItem(item); err != nil {
				t.Error(err)
			}
		}
	}

	// Add conditon and it should fail
	{
		err := dtable.PutItem(
			items[0],
			option.PutExpressionAttributeName("date", "#date"),
			option.PutCondition("attribute_not_exists(#date)"),
		)

		assert.Error(err)

		dynamoErr, ok := err.(awserr.Error)
		assert.True(ok, "err must be awserr.Error")
		assert.Equal("ConditionalCheckFailedException", dynamoErr.Code())
	}

	// Update the item with incrementing counter and setting role as StringSet
	{
		err := dtable.UpdateItem(
			hashKey,
			rangeKey,
			option.UpdateExpressionAttributeName("login_count", "#count"),
			option.UpdateExpressionAttributeName("role", "#role"),
			option.UpdateExpressionAttributeValue(":i", attributes.Number(1)),
			option.UpdateExpressionAttributeValue(":role", attributes.StringSet(role)),
			option.UpdateExpression("ADD #count :i SET #role = :role"),
		)
		if err != nil {
			t.Error(err)
		}
	}

	// Get the item
	{
		var actualItem TestItem
		if err := dtable.GetItem(hashKey, rangeKey, &actualItem, option.ConsistentRead()); err != nil {
			t.Error(err)
		}
		sort.Strings(actualItem.Role)

		assert.Equal("waiting", actualItem.Status)
		assert.Equal(1, actualItem.LoginCount)
		assert.Equal(role, actualItem.Role)
	}

	// Update the item with decrementing counter and removing role
	{
		err := dtable.UpdateItem(
			hashKey,
			rangeKey,
			option.UpdateExpressionAttributeName("login_count", "#count"),
			option.UpdateExpressionAttributeName("role", "#role"),
			option.UpdateExpressionAttributeValue(":i", attributes.Number(-1)),
			option.UpdateExpression("ADD #count :i REMOVE #role"),
		)
		if err != nil {
			t.Error(err)
		}
	}

	// Query the items
	{
		var actualItems []TestItem
		lastEvaluatedKey, err := dtable.Query(
			&actualItems,
			option.QueryExpressionAttributeValue(":hashval", hashKey),
			option.QueryKeyConditionExpression("user_id = :hashval"),
		)

		assert.NoError(err)
		assert.Nil(lastEvaluatedKey)
		assert.Len(actualItems, 2)

		// default is ascending order
		assert.Equal(items, actualItems)
	}

	// Delete the item with the conditon but it should fail
	{
		err := dtable.DeleteItem(
			hashKey,
			rangeKey,
			option.DeleteExpressionAttributeName("status", "#status"),
			option.DeleteExpressionAttributeValue(":s", attributes.String("done")),
			option.DeleteCondition("#status = :s"),
		)

		if err == nil {
			t.Error("DeleteItem should fail but not fail")
		}

		dynamoErr, ok := err.(awserr.Error)
		if !ok {
			t.Error("err must be awserr.Error")
		}

		if dynamoErr.Code() != "ConditionalCheckFailedException" {
			t.Error("dynamoErr must be conditional error")
		}
	}

	// Delete the item with the conditon and it should succeed
	{
		for _, item := range items {
			hk := attributes.String(item.UserID)
			rk := attributes.Number(item.Date)
			err := dtable.DeleteItem(
				hk,
				rk,
				option.DeleteExpressionAttributeName("status", "#status"),
				option.DeleteExpressionAttributeValue(":s", status),
				option.DeleteCondition("#status = :s"),
			)

			if err != nil {
				t.Error(err)
			}
		}
	}
}