/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 dubbo

import (
	"bytes"
	"context"
	"net/http"
	"testing"
)

import (
	"github.com/dubbo-go-pixiu/pixiu-api/pkg/api/config"

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

import (
	"github.com/apache/dubbo-go-pixiu/pkg/client"
	"github.com/apache/dubbo-go-pixiu/pkg/common/mock"
)

func TestQueryStringsMapper(t *testing.T) {
	r, _ := http.NewRequest("GET", "/mock/test?id=12345&age=19", bytes.NewReader([]byte("")))
	api := mock.GetMockAPI(config.MethodGet, "/mock/test")
	api.MappingParams = []config.MappingParam{
		{
			Name:    "queryStrings.id",
			MapTo:   "0",
			MapType: "string",
		},
		{
			Name:    "queryStrings.name",
			MapTo:   "1",
			MapType: "string",
		},
		{
			Name:    "queryStrings.age",
			MapTo:   "jk",
			MapType: "int",
		},
	}

	req := client.NewReq(context.TODO(), r, api)

	params := newDubboTarget(api.MappingParams)
	qs := queryStringsMapper{}
	// Giving valid mapping params
	err := qs.Map(api.MappingParams[0], req, params, nil)
	// it should not return error
	assert.Nil(t, err)
	// it should update the target value in target position from corresponding query value in request.
	assert.Equal(t, params.Values[0], "12345")
	assert.Equal(t, params.Types[0], "string")
	// Giving valid mapping params and same target
	err = qs.Map(api.MappingParams[1], req, params, nil)
	// it should return error when request does not contain the source parameter
	assert.EqualError(t, err, "Query parameter [name] does not exist")
	// Giving invalid mapping params that is not a number and same target
	err = qs.Map(api.MappingParams[2], req, params, nil)
	// it should return error that points out the mapping param
	assert.EqualError(t, err, "Parameter mapping {queryStrings.age jk int} incorrect")

	r, _ = http.NewRequest("GET", "/mock/test?id=12345&age=19", bytes.NewReader([]byte("")))
	api = mock.GetMockAPI(config.MethodGet, "/mock/test")
	api.MappingParams = []config.MappingParam{
		{
			Name:    "queryStrings.id",
			MapTo:   "1",
			MapType: "string",
		},
		{
			Name:    "queryStrings.age",
			MapTo:   "0",
			MapType: "int",
		},
	}

	req = client.NewReq(context.TODO(), r, api)
	params = newDubboTarget(api.MappingParams)
	err = qs.Map(api.MappingParams[0], req, params, nil)
	assert.Nil(t, err)
	assert.Equal(t, params.Values[1], "12345")
	assert.Equal(t, params.Types[1], "string")
	assert.Nil(t, params.Values[0])
	err = qs.Map(api.MappingParams[1], req, params, nil)
	assert.Nil(t, err)
	assert.Equal(t, params.Types[0], "int")
	assert.Equal(t, params.Values[0], 19)
}

func TestHeaderMapper(t *testing.T) {
	r, _ := http.NewRequest("GET", "/mock/test?id=12345&age=19", bytes.NewReader([]byte("")))
	r.Header.Set("Auth", "1234567")
	api := mock.GetMockAPI(config.MethodGet, "/mock/test")
	api.MappingParams = []config.MappingParam{
		{
			Name:    "headers.Auth",
			MapTo:   "0",
			MapType: "string",
		},
	}
	hm := headerMapper{}
	target := newDubboTarget(api.MappingParams)
	req := client.NewReq(context.TODO(), r, api)

	err := hm.Map(api.MappingParams[0], req, target, nil)
	assert.Nil(t, err)
	assert.Equal(t, target.Values[0], "1234567")
	assert.Equal(t, target.Types[0], "string")

	err = hm.Map(config.MappingParam{Name: "headers.Test", MapTo: "0"}, req, target, nil)
	assert.EqualError(t, err, "Header Test not found")
}

func TestBodyMapper(t *testing.T) {
	r, _ := http.NewRequest("POST", "/mock/test?id=12345&age=19", bytes.NewReader([]byte(`{"sex": "male", "name":{"firstName": "Joe", "lastName": "Biden"}}`)))
	r.Header.Set("Auth", "1234567")
	api := mock.GetMockAPI(config.MethodGet, "/mock/test")
	api.MappingParams = []config.MappingParam{
		{
			Name:    "requestBody.sex",
			MapTo:   "0",
			MapType: "string",
		},
		{
			Name:    "requestBody.name.lastName",
			MapTo:   "1",
			MapType: "string",
		},
		{
			Name:    "requestBody.name",
			MapTo:   "2",
			MapType: "object",
		},
	}
	bm := bodyMapper{}
	target := newDubboTarget(api.MappingParams)
	req := client.NewReq(context.TODO(), r, api)

	err := bm.Map(api.MappingParams[0], req, target, nil)
	assert.Nil(t, err)
	assert.Equal(t, target.Values[0], "male")
	assert.Equal(t, target.Types[0], "string")

	err = bm.Map(api.MappingParams[1], req, target, nil)
	assert.Nil(t, err)
	assert.Equal(t, target.Values[1], "Biden")
	assert.Equal(t, target.Types[1], "string")

	err = bm.Map(api.MappingParams[2], req, target, nil)
	assert.Nil(t, err)
	assert.Equal(t, target.Types[2], "object")
	assert.Equal(t, target.Values[2], map[string]any(map[string]any{
		"firstName": "Joe", "lastName": "Biden",
	}))
}

func TestURIMapper(t *testing.T) {
	r, _ := http.NewRequest("POST", "/mock/12345/joe?age=19", bytes.NewReader([]byte(
		`{"sex": "male", "name":{"firstName": "Joe", "lastName": "Biden"}}`)))
	r.Header.Set("Auth", "1234567")
	api := mock.GetMockAPI(config.MethodGet, "/mock/:id/:name")
	api.MappingParams = []config.MappingParam{
		{
			Name:    "requestBody.sex",
			MapTo:   "0",
			MapType: "string",
		},
		{
			Name:    "requestBody.name.lastName",
			MapTo:   "1",
			MapType: "string",
		},
		{
			Name:    "uri.name",
			MapTo:   "2",
			MapType: "object",
		},
		{
			Name:    "uri.id",
			MapTo:   "3",
			MapType: "string",
		},
	}

	um := uriMapper{}
	target := newDubboTarget(api.MappingParams)
	req := client.NewReq(context.TODO(), r, api)
	err := um.Map(api.MappingParams[3], req, target, nil)
	assert.Nil(t, err)
	err = um.Map(api.MappingParams[2], req, target, nil)
	assert.Nil(t, err)
	assert.Equal(t, target.Values[2], "joe")
	assert.Equal(t, target.Types[2], "object")
	assert.Equal(t, target.Values[3], "12345")
	assert.Equal(t, target.Types[3], "string")
}

func TestValidateTarget(t *testing.T) {
	target := newDubboTarget([]config.MappingParam{
		{
			Name:    "requestBody.sex",
			MapTo:   "0",
			MapType: "string",
		},
	})
	val, err := validateTarget(target)
	assert.Nil(t, err)
	assert.NotNil(t, val)
	_, err = validateTarget(*target)
	assert.EqualError(t, err, "Target params for dubbo backend must be *dubboTarget")
	target2 := ""
	_, err = validateTarget(target2)
	assert.EqualError(t, err, "Target params for dubbo backend must be *dubboTarget")
}

func TestMapType(t *testing.T) {
	_, err := mapTypes("strings", 123)
	assert.EqualError(t, err, "Invalid parameter type: strings")

	val, err := mapTypes("string", 123)
	assert.Nil(t, err)
	assert.Equal(t, val, "123")
	_, err = mapTypes("string", []int{123, 222})
	assert.EqualError(t, err, "unable to cast []int{123, 222} of type []int to string")

	val, err = mapTypes("int", "123")
	assert.Nil(t, err)
	assert.Equal(t, val, 123)
	val, err = mapTypes("int", 123.6)
	assert.Nil(t, err)
	assert.Equal(t, val, 123)
	_, err = mapTypes("int", "123a")
	assert.EqualError(t, err, "unable to cast \"123a\" of type string to int64")

	val, err = mapTypes("object", map[string]string{"abc": "123"})
	assert.Nil(t, err)
	assert.Equal(t, val, map[string]string{"abc": "123"})
	val, err = mapTypes("object", struct{ Abc string }{Abc: "123"})
	assert.Nil(t, err)
	assert.Equal(t, val, struct{ Abc string }{Abc: "123"})
	val, err = mapTypes("object", 123.6)
	assert.Nil(t, err)
	assert.Equal(t, val, 123.6)
}

func TestNewDubboTarget(t *testing.T) {
	mps := []config.MappingParam{
		{
			Name:  "string1",
			MapTo: "0",
		},
		{
			Name:  "string2",
			MapTo: "opt.values",
		},
	}
	target := newDubboTarget(mps)
	assert.NotNil(t, target)
	assert.Equal(t, len(target.Values), 2)

	mps = []config.MappingParam{
		{
			Name:  "string1",
			MapTo: "opt.interface",
		},
	}
	target = newDubboTarget(mps)
	assert.Nil(t, target)
}

func TestSetCommonTarget(t *testing.T) {
	vals := make([]any, 10)
	types := make([]string, 10)
	target := &dubboTarget{
		Values: vals,
		Types:  types,
	}
	setCommonTarget(target, 1, 123, "int")
	assert.Equal(t, target.Values[1], 123)
	assert.Equal(t, target.Types[1], "int")
	assert.Nil(t, target.Values[0])
	assert.Equal(t, target.Types[0], "")
	setCommonTarget(target, 10, "123", "string")
	assert.Equal(t, target.Values[10], "123")
	assert.Equal(t, target.Types[10], "string")
}

func TestSetGenericTarget(t *testing.T) {
	api := mock.GetMockAPI(config.MethodGet, "/mock/test")
	r, _ := http.NewRequest("GET", "/mock/test?id=12345&age=19", bytes.NewReader([]byte("")))
	req := client.NewReq(context.TODO(), r, api)

	target := &dubboTarget{
		Values: make([]any, 3),
		Types:  make([]string, 3),
	}

	opt := DefaultMapOption[optionKeyValues]
	err := setGenericTarget(req, opt, target, []any{1, "abc", struct{ Name string }{"joe"}}, "int, string, object")
	assert.Nil(t, err)
	assert.Equal(t, target.Values[0], 1)
	assert.Equal(t, target.Values[1], "abc")
	assert.Equal(t, target.Values[2], struct{ Name string }{"joe"})
	assert.Equal(t, target.Types[0], "int")
	assert.Equal(t, target.Types[1], "string")
	assert.Equal(t, target.Types[2], "object")

	opt = DefaultMapOption[optionKeyTypes]
	err = setGenericTarget(req, opt, target, "int, object, object", "")
	assert.Nil(t, err)
	assert.Equal(t, target.Types[0], "int")
	assert.Equal(t, target.Types[1], "object")
	assert.Equal(t, target.Types[2], "object")

	opt = DefaultMapOption[optionKeyInterface]
	err = setGenericTarget(req, opt, target, "testingInterface", "")
	assert.Nil(t, err)
	assert.Equal(t, req.API.Interface, "testingInterface")

	opt = DefaultMapOption[optionKeyApplication]
	err = setGenericTarget(req, opt, target, "testingApplication", "")
	assert.Nil(t, err)
	assert.Equal(t, req.API.ApplicationName, "testingApplication")
}

func TestGetGenericMapTo(t *testing.T) {
	isGeneric, gMapTo := getGenericMapTo("1")
	assert.False(t, isGeneric)
	assert.Equal(t, gMapTo, "")

	isGeneric, gMapTo = getGenericMapTo("opt.interface")
	assert.True(t, isGeneric)
	assert.Equal(t, gMapTo, "interface")

	isGeneric, gMapTo = getGenericMapTo("opt.whatever")
	assert.False(t, isGeneric)
	assert.Equal(t, gMapTo, "")
}
