// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"testing"

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

func TestParseQueryByPath(t *testing.T) {
	cases := []struct {
		name     string
		path     string
		expected map[string][]string
		errMsg   string
	}{
		{
			name: "common",
			path: "/get?k1=v1&k2=v2&k3=v3",
			expected: map[string][]string{
				"k1": {"v1"},
				"k2": {"v2"},
				"k3": {"v3"},
			},
		},
		{
			name:     "empty query",
			path:     "www.example.com/get",
			expected: map[string][]string{},
		},
		{
			name: "multiple values",
			path: "www.example.com/get?k1=v11&k1=v12&k2=v2&k1=v13",
			expected: map[string][]string{
				"k1": {"v11", "v12", "v13"},
				"k2": {"v2"},
			},
		},
		{
			name: "encoded url",
			path: "/get%20with%3Freserved%20characters?key=Hello+World",
			expected: map[string][]string{
				"key": {"Hello World"},
			},
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			actual, err := parseQueryByPath(c.path)
			if c.errMsg != "" {
				require.EqualError(t, err, c.errMsg)
				return
			}
			require.NoError(t, err)
			require.Equal(t, c.expected, actual)
		})
	}
}

func TestConstructPath(t *testing.T) {
	cases := []struct {
		name     string
		path     string
		qs       map[string][]string
		expected string
		errMsg   string
	}{
		{
			name: "common",
			path: "/get",
			qs: map[string][]string{
				"k1": {"v1"},
				"k2": {"v2"},
				"k3": {"v3"},
			},
			expected: "/get?k1=v1&k2=v2&k3=v3",
		},
		{
			name:     "empty query",
			path:     "www.example.com/get",
			qs:       map[string][]string{},
			expected: "www.example.com/get",
		},
		{
			name: "encoded url",
			path: "/get with?",
			qs: map[string][]string{
				"key": {"Hello World"},
			},
			expected: "/get%20with?key=Hello+World",
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			actual, err := constructPath(c.path, c.qs)
			if c.errMsg != "" {
				require.EqualError(t, err, c.errMsg)
				return
			}
			require.NoError(t, err)
			require.Equal(t, c.expected, actual)
		})
	}
}

func TestParseBody(t *testing.T) {
	cases := []struct {
		name      string
		mediaType string
		body      []byte
		expected  interface{}
		errMsg    string
	}{
		{
			name:      "application/json",
			mediaType: "application/json",
			body: []byte(`{
  "k1": "v2",
  "k2": 20,
  "k3": true,
  "k4": [1, 2, 3],
  "k5": {
    "k6": "v6"
  }
}`),
			expected: map[string]interface{}{"body": []byte(`{
  "k1": "v2",
  "k2": 20,
  "k3": true,
  "k4": [1, 2, 3],
  "k5": {
    "k6": "v6"
  }
}`)},
		},
		{
			name:      "application/x-www-form-urlencoded",
			mediaType: "application/x-www-form-urlencoded",
			body:      []byte("k1=v11&k1=v12&k2=v2&k3=v3"),
			expected: map[string][]string{
				"k1": {"v11", "v12"},
				"k2": {"v2"},
				"k3": {"v3"},
			},
		},
		{
			name:      "multipart/form-data",
			mediaType: "multipart/form-data; boundary=--------------------------962785348548682888818907",
			body:      []byte("----------------------------962785348548682888818907\r\nContent-Disposition: form-data; name=\"k1\"\r\n\r\nv11\r\n----------------------------962785348548682888818907\r\nContent-Disposition: form-data; name=\"k1\"\r\n\r\nv12\r\n----------------------------962785348548682888818907\r\nContent-Disposition: form-data; name=\"k2\"\r\n\r\nv2\r\n----------------------------962785348548682888818907--\r\n"),
			expected: map[string][]string{
				"k1": {"v11", "v12"},
				"k2": {"v2"},
			},
		},
		{
			name:      "unsupported content type",
			mediaType: "plain/text",
			body:      []byte(`qwe`),
			errMsg:    fmt.Sprintf(errContentTypeFmt, "plain/text"),
		},
		{
			name:      "empty body",
			mediaType: "application/json",
			body:      []byte(``),
			errMsg:    errEmptyBody.Error(),
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			actual, err := parseBody(c.mediaType, c.body)
			if c.errMsg != "" {
				require.EqualError(t, err, c.errMsg)
				return
			}
			require.NoError(t, err)
			require.Equal(t, c.expected, actual)
		})
	}
}

func TestConstructBody(t *testing.T) {
	cases := []struct {
		name      string
		mediaType string
		body      interface{}
		expected  []byte
		errMsg    string
	}{
		{
			name:      "application/json",
			mediaType: "application/json",
			body: map[string]interface{}{"body": []byte(`{
  "k1": {
    "k2": [1, 2, 3]
  }
}
`)},
			expected: []byte(`{
  "k1": {
    "k2": [1, 2, 3]
  }
}
`),
		},
		{
			name:      "application/x-www-form-urlencoded",
			mediaType: "application/x-www-form-urlencoded",
			body: map[string][]string{
				"k1": {"v11", "v12"},
			},
			expected: []byte("k1=v11&k1=v12"),
		},
		{
			name:      "multipart/form-data",
			mediaType: "multipart/form-data; boundary=--------------------------962785348548682888818907",
			body: map[string][]string{
				"k1": {"v11", "v12"},
			},
			expected: []byte("----------------------------962785348548682888818907\r\nContent-Disposition: form-data; name=\"k1\"\r\n\r\nv11\r\n----------------------------962785348548682888818907\r\nContent-Disposition: form-data; name=\"k1\"\r\n\r\nv12\r\n----------------------------962785348548682888818907--\r\n"),
		},
		{
			name:      "unsupported media type",
			mediaType: "plain/text",
			body:      []byte(`qwe`),
			errMsg:    fmt.Sprintf(errContentTypeFmt, "plain/text"),
		},
		{
			name:      "empty body",
			mediaType: "application/json",
			body:      map[string]interface{}{"body": []byte{}},
			expected:  []byte{},
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			actual, err := constructBody(c.mediaType, c.body)
			if c.errMsg != "" {
				require.EqualError(t, err, c.errMsg)
				return
			}
			require.NoError(t, err)
			require.Equal(t, c.expected, actual)
		})
	}
}

func TestConvertByJsonType(t *testing.T) {
	cases := []struct {
		name     string
		valueTyp string
		value    string
		expected interface{}
		errMsg   string
	}{
		{
			name:     "object",
			valueTyp: "object",
			value:    "{\"array\": [1, 2, 3],   \"object\": {     \"first\": \"hello\",     \"second\": \"world\"   } }",
			expected: map[string]interface{}{
				"array": []interface{}{float64(1), float64(2), float64(3)},
				"object": map[string]interface{}{
					"first":  "hello",
					"second": "world",
				},
			},
		},
		{
			name:     "boolean",
			valueTyp: "boolean",
			value:    "true",
			expected: true,
		},
		{
			name:     "boolean: failed",
			valueTyp: "boolean",
			value:    "null",
			errMsg:   "strconv.ParseBool: parsing \"null\": invalid syntax",
		},
		{
			name:     "number",
			valueTyp: "number",
			value:    "10",
			expected: float64(10),
		},
		{
			name:     "string",
			valueTyp: "string",
			value:    "hello world",
			expected: "hello world",
		},
		{
			name:     "unsupported type",
			valueTyp: "integer",
			value:    "10",
			expected: "10", // default string
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			actual, err := convertByJsonType(c.valueTyp, c.value)
			if c.errMsg != "" {
				require.EqualError(t, err, c.errMsg)
				return
			}
			require.NoError(t, err)
			require.Equal(t, c.expected, actual)
		})
	}
}
