/*
 * Copyright 2021 CloudWeGo Authors
 *
 * 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 test

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"math"
	"net"
	"net/http"
	"reflect"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/bytedance/sonic"
	"github.com/cloudwego/dynamicgo/conv"
	"github.com/tidwall/gjson"

	"github.com/cloudwego/kitex/client/callopt"
	"github.com/cloudwego/kitex/client/genericclient"
	kt "github.com/cloudwego/kitex/internal/mocks/thrift"
	"github.com/cloudwego/kitex/internal/test"
	"github.com/cloudwego/kitex/pkg/generic"
	"github.com/cloudwego/kitex/pkg/generic/descriptor"
	"github.com/cloudwego/kitex/server"
	"github.com/cloudwego/kitex/transport"
)

var customJson = sonic.Config{
	EscapeHTML: true,
	UseNumber:  true,
}.Froze()

func TestRun(t *testing.T) {
	t.Run("TestThriftNormalBinaryEcho", testThriftNormalBinaryEcho)
	t.Run("TestThriftException", testThriftException)
	t.Run("TestRegression", testRegression)
	t.Run("TestThriftBase64BinaryEcho", testThriftBase64BinaryEcho)
	t.Run("TestUseRawBodyAndBodyCompatibility", testUseRawBodyAndBodyCompatibility)
}

func initThriftClientByIDL(t *testing.T, tp transport.Protocol, addr, idl string, opts []generic.Option, base64Binary, enableDynamicGo bool) genericclient.Client {
	var p generic.DescriptorProvider
	var err error
	if enableDynamicGo {
		p, err = generic.NewThriftFileProviderWithDynamicGo(idl)
	} else {
		p, err = generic.NewThriftFileProvider(idl)
	}
	test.Assert(t, err == nil)
	g, err := generic.HTTPThriftGeneric(p, opts...)
	test.Assert(t, err == nil)
	err = generic.SetBinaryWithBase64(g, base64Binary)
	test.Assert(t, err == nil)
	cli := newGenericClient(tp, "destServiceName", g, addr)
	test.Assert(t, err == nil)
	return cli
}

func initThriftServer(t *testing.T, address string, handler generic.Service, idlPath string) server.Server {
	addr, _ := net.ResolveTCPAddr("tcp", address)
	p, err := generic.NewThriftFileProvider(idlPath)
	test.Assert(t, err == nil)
	g, err := generic.MapThriftGeneric(p)
	test.Assert(t, err == nil)
	svr := newGenericServer(g, addr, handler)
	test.Assert(t, err == nil)
	return svr
}

func initMockServer(t *testing.T, handler kt.Mock, address string) server.Server {
	addr, _ := net.ResolveTCPAddr("tcp", address)
	svr := newMockServer(handler, addr)
	return svr
}

func testThriftNormalBinaryEcho(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceBinaryEchoImpl), "./idl/binary_echo.thrift")

	url := "http://example.com/BinaryEcho"

	// []byte value for binary field
	body := map[string]interface{}{
		"msg":        []byte(mockMyMsg),
		"got_base64": true,
		"num":        "",
	}
	data, err := json.Marshal(body)
	if err != nil {
		panic(err)
	}
	req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
	if err != nil {
		panic(err)
	}
	customReq, err := generic.FromHTTPRequest(req)
	if err != nil {
		t.Fatal(err)
	}

	// normal way
	var opts []generic.Option
	cli := initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, false, false)
	resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	gr, ok := resp.(*generic.HTTPResponse)
	test.Assert(t, ok)
	test.Assert(t, gr.Body["msg"] == base64.StdEncoding.EncodeToString([]byte(mockMyMsg)))
	test.Assert(t, gr.Body["num"] == "0")

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	opts = append(opts, generic.UseRawBodyForHTTPResp(true))
	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, false, true)
	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	gr, ok = resp.(*generic.HTTPResponse)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "msg").String(), base64.StdEncoding.EncodeToString([]byte(mockMyMsg))), gjson.Get(string(gr.RawBody), "msg").String())
	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "num").String(), "0"), gjson.Get(string(gr.RawBody), "num").String())

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: fallback
	cli = initThriftClientByIDL(t, transport.PurePayload, addr, "./idl/binary_echo.thrift", opts, false, true)
	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	gr, ok = resp.(*generic.HTTPResponse)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "msg").String(), base64.StdEncoding.EncodeToString([]byte(mockMyMsg))), gjson.Get(string(gr.RawBody), "msg").String())
	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "num").String(), "0"), gjson.Get(string(gr.RawBody), "num").String())

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: fallback
	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", nil, false, true)
	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	gr, ok = resp.(*generic.HTTPResponse)
	test.Assert(t, ok)
	test.Assert(t, gr.Body["msg"] == base64.StdEncoding.EncodeToString([]byte(mockMyMsg)))
	test.Assert(t, gr.Body["num"] == "0")

	body = map[string]interface{}{
		"msg":        string(mockMyMsg),
		"got_base64": false,
		"num":        0,
	}
	data, err = json.Marshal(body)
	if err != nil {
		panic(err)
	}
	req, err = http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
	if err != nil {
		panic(err)
	}
	customReq, err = generic.FromHTTPRequest(req)
	if err != nil {
		t.Fatal(err)
	}

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, false, true)
	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	gr, ok = resp.(*generic.HTTPResponse)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "msg").String(), mockMyMsg), gjson.Get(string(gr.RawBody), "msg").String())
	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "num").String(), "0"), gjson.Get(string(gr.RawBody), "num").String())

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: fallback
	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", nil, false, true)
	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	gr, ok = resp.(*generic.HTTPResponse)
	test.Assert(t, ok)
	test.Assert(t, gr.Body["msg"] == mockMyMsg)
	test.Assert(t, gr.Body["num"] == "0")

	body = map[string]interface{}{
		"msg":        []byte(mockMyMsg),
		"got_base64": true,
		"num":        "123",
	}
	data, err = json.Marshal(body)
	if err != nil {
		panic(err)
	}
	req, err = http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
	if err != nil {
		panic(err)
	}
	customReq, err = generic.FromHTTPRequest(req)
	if err != nil {
		t.Fatal(err)
	}

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, false, true)
	_, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err.Error() == "remote or network error[remote]: biz error: call failed, incorrect num", err.Error())

	svr.Stop()
}

func BenchmarkCompareDynamicgoAndOriginal_Small(b *testing.B) {
	// small data
	sobj := getSimpleValue()
	data, err := json.Marshal(sobj)
	if err != nil {
		panic(err)
	}
	fmt.Println("small data size: ", len(string(data)))
	url := "http://example.com/simple"

	t := testing.T{}
	var opts []generic.Option
	opts = append(opts, generic.UseRawBodyForHTTPResp(true))

	b.Run("thrift_small_dynamicgo", func(b *testing.B) {
		addr := test.GetLocalAddress()
		svr := initThriftServer(&t, addr, new(GenericServiceBenchmarkImpl), "./idl/baseline.thrift")
		cli := initThriftClientByIDL(&t, transport.TTHeader, addr, "./idl/baseline.thrift", opts, false, true)

		req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
		if err != nil {
			panic(err)
		}
		customReq, err := generic.FromHTTPRequest(req)
		if err != nil {
			t.Fatal(err)
		}
		resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
		test.Assert(&t, err == nil, err)
		gr, ok := resp.(*generic.HTTPResponse)
		test.Assert(&t, ok)
		test.Assert(&t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "I64Field").String(), strconv.Itoa(math.MaxInt64)), gjson.Get(string(gr.RawBody), "I64Field").String())

		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
		}
		svr.Stop()
	})

	b.Run("thrift_small_original", func(b *testing.B) {
		addr := test.GetLocalAddress()
		svr := initThriftServer(&t, addr, new(GenericServiceBenchmarkImpl), "./idl/baseline.thrift")
		cli := initThriftClientByIDL(&t, transport.TTHeader, addr, "./idl/baseline.thrift", nil, false, false)

		req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
		if err != nil {
			panic(err)
		}
		customReq, err := generic.FromHTTPRequest(req)
		if err != nil {
			t.Fatal(err)
		}
		resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
		test.Assert(&t, err == nil, err)
		gr, ok := resp.(*generic.HTTPResponse)
		test.Assert(&t, ok)
		test.Assert(&t, reflect.DeepEqual(gr.Body["I64Field"].(string), strconv.Itoa(math.MaxInt64)), gr.Body["I64Field"].(string))

		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
		}
		svr.Stop()
	})
}

func BenchmarkCompareDynamicgoAndOriginal_Medium(b *testing.B) {
	// medium data
	nobj := getNestingValue()
	data, err := json.Marshal(nobj)
	if err != nil {
		panic(err)
	}
	fmt.Println("medium data size: ", len(string(data)))
	url := "http://example.com/nesting/100"

	t := testing.T{}
	var opts []generic.Option
	opts = append(opts, generic.UseRawBodyForHTTPResp(true))

	b.Run("thrift_medium_dynamicgo", func(b *testing.B) {
		addr := test.GetLocalAddress()
		svr := initThriftServer(&t, addr, new(GenericServiceBenchmarkImpl), "./idl/baseline.thrift")
		cli := initThriftClientByIDL(&t, transport.TTHeader, addr, "./idl/baseline.thrift", opts, false, true)

		req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
		if err != nil {
			panic(err)
		}
		customReq, err := generic.FromHTTPRequest(req)
		if err != nil {
			t.Fatal(err)
		}
		resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
		test.Assert(&t, err == nil, err)
		gr, ok := resp.(*generic.HTTPResponse)
		test.Assert(&t, ok)
		test.Assert(&t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "I32").String(), strconv.Itoa(math.MaxInt32)), gjson.Get(string(gr.RawBody), "I32").String())

		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
		}
		svr.Stop()
	})

	b.Run("thrift_medium_original", func(b *testing.B) {
		addr := test.GetLocalAddress()
		svr := initThriftServer(&t, addr, new(GenericServiceBenchmarkImpl), "./idl/baseline.thrift")
		cli := initThriftClientByIDL(&t, transport.TTHeader, addr, "./idl/baseline.thrift", nil, false, false)

		req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
		if err != nil {
			panic(err)
		}
		customReq, err := generic.FromHTTPRequest(req)
		if err != nil {
			t.Fatal(err)
		}
		resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
		test.Assert(&t, err == nil, err)
		gr, ok := resp.(*generic.HTTPResponse)
		test.Assert(&t, ok)
		test.Assert(&t, gr.Body["I32"].(int32) == math.MaxInt32, gr.Body["I32"].(int32))

		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
		}
		svr.Stop()
	})
}

func testThriftException(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initMockServer(t, new(mockImpl), addr)

	body := map[string]interface{}{
		"Msg":     "hello",
		"strMap":  map[string]interface{}{"mk1": "mv1", "mk2": "mv2"},
		"strList": []string{"lv1", "lv2"},
	}
	data, err := json.Marshal(body)
	if err != nil {
		panic(err)
	}
	url := "http://example.com/ExceptionTest"
	req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
	if err != nil {
		panic(err)
	}
	customReq, err := generic.FromHTTPRequest(req)
	if err != nil {
		t.Fatal(err)
	}

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	var opts []generic.Option
	opts = append(opts, generic.UseRawBodyForHTTPResp(true))
	cli := initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/mock.thrift", opts, false, true)
	resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	fmt.Println(string(resp.(*descriptor.HTTPResponse).RawBody))
	test.DeepEqual(t, gjson.Get(string(resp.(*descriptor.HTTPResponse).RawBody), "code").Int(), int64(400))
	test.DeepEqual(t, gjson.Get(string(resp.(*descriptor.HTTPResponse).RawBody), "msg").String(), "this is an exception")

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: fallback
	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/mock.thrift", nil, false, true)
	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	fmt.Println(string(resp.(*descriptor.HTTPResponse).RawBody))
	test.DeepEqual(t, resp.(*descriptor.HTTPResponse).Body["code"].(int32), int32(400))
	test.DeepEqual(t, resp.(*descriptor.HTTPResponse).Body["msg"], "this is an exception")

	svr.Stop()
}

func testRegression(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceAnnotationImpl), "./idl/http_annotation.thrift")

	body := map[string]interface{}{
		"text": "text",
		"req_items_map": map[string]interface{}{
			"1": map[string]interface{}{
				"MyID": "1",
				"text": "text",
			},
		},
		"some": map[string]interface{}{
			"MyID": "1",
			"text": "text",
		},
	}
	data, err := json.Marshal(body)
	if err != nil {
		panic(err)
	}
	url := "http://example.com/life/client/1/1?v_int64=1&req_items=item1,item2,item3&cids=1,2,3&vids=1,2,3"
	req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
	if err != nil {
		panic(err)
	}
	req.Header.Set("token", "1")
	cookie := &http.Cookie{
		Name:  "cookie",
		Value: "cookie_val",
	}
	req.AddCookie(cookie)
	customReq, err := generic.FromHTTPRequest(req)
	if err != nil {
		t.Fatal(err)
	}

	// client without dynamicgo
	cli := initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/http_annotation.thrift", nil, false, false)
	respI, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	resp, ok := respI.(*generic.HTTPResponse)
	test.Assert(t, ok)

	// client with dynamicgo
	var opts []generic.Option
	opts = append(opts, generic.UseRawBodyForHTTPResp(true))
	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/http_annotation.thrift", opts, false, true)
	respI, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	dresp, ok := respI.(*generic.HTTPResponse)
	test.Assert(t, ok)

	// check body
	var dMapBody map[string]interface{}
	err = customJson.Unmarshal(dresp.RawBody, &dMapBody)
	test.Assert(t, err == nil)
	bytes, err := customJson.Marshal(resp.Body)
	test.Assert(t, err == nil)
	err = customJson.Unmarshal(bytes, &resp.Body)
	test.Assert(t, err == nil, err)
	test.Assert(t, isEqual(dMapBody, resp.Body))

	test.DeepEqual(t, resp.StatusCode, dresp.StatusCode)
	test.DeepEqual(t, resp.ContentType, dresp.ContentType)

	checkHeader(t, resp)
	checkHeader(t, dresp)

	svr.Stop()
}

func testThriftBase64BinaryEcho(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceBinaryEchoImpl), "./idl/binary_echo.thrift")

	var opts []generic.Option
	convOpts := conv.Options{EnableValueMapping: true, NoBase64Binary: false}
	opts = append(opts, generic.WithCustomDynamicGoConvOpts(&convOpts), generic.UseRawBodyForHTTPResp(true))

	url := "http://example.com/BinaryEcho"

	// []byte value for binary field
	body := map[string]interface{}{
		"msg":        []byte(mockMyMsg),
		"got_base64": false,
		"num":        "0",
	}
	data, err := json.Marshal(body)
	if err != nil {
		panic(err)
	}
	req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
	if err != nil {
		panic(err)
	}
	customReq, err := generic.FromHTTPRequest(req)
	if err != nil {
		t.Fatal(err)
	}

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	cli := initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, true, true)
	resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	gr, ok := resp.(*generic.HTTPResponse)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(gjson.Get(string(gr.RawBody), "msg").String(), base64.StdEncoding.EncodeToString([]byte(mockMyMsg))), gjson.Get(string(gr.RawBody), "msg").String())

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: fallback
	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", nil, true, true)
	resp, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	gr, ok = resp.(*generic.HTTPResponse)
	test.Assert(t, ok)
	test.Assert(t, gr.Body["msg"] == base64.StdEncoding.EncodeToString(body["msg"].([]byte)))

	// string value for binary field which should fail
	body = map[string]interface{}{
		"msg": string(mockMyMsg),
	}
	data, err = json.Marshal(body)
	if err != nil {
		panic(err)
	}
	req, err = http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
	if err != nil {
		panic(err)
	}
	customReq, err = generic.FromHTTPRequest(req)
	if err != nil {
		t.Fatal(err)
	}

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	cli = initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, true, true)
	_, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, strings.Contains(err.Error(), "illegal base64 data"))

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: fallback
	cli = initThriftClientByIDL(t, transport.PurePayload, addr, "./idl/binary_echo.thrift", nil, true, true)
	_, err = cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, strings.Contains(err.Error(), "illegal base64 data"))

	svr.Stop()
}

func testUseRawBodyAndBodyCompatibility(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceBinaryEchoImpl), "./idl/binary_echo.thrift")

	url := "http://example.com/BinaryEcho"

	// []byte value for binary field
	body := map[string]interface{}{
		"msg":        []byte(mockMyMsg),
		"got_base64": true,
		"num":        "",
	}
	data, err := json.Marshal(body)
	if err != nil {
		panic(err)
	}
	req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer(data))
	if err != nil {
		panic(err)
	}
	customReq, err := generic.FromHTTPRequest(req)
	if err != nil {
		t.Fatal(err)
	}

	var opts []generic.Option
	opts = append(opts, generic.UseRawBodyForHTTPResp(true))
	cli := initThriftClientByIDL(t, transport.TTHeader, addr, "./idl/binary_echo.thrift", opts, false, false)
	resp, err := cli.GenericCall(context.Background(), "", customReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil)
	gr, ok := resp.(*generic.HTTPResponse)
	test.Assert(t, ok)

	var mapBody map[string]interface{}
	err = customJson.Unmarshal(gr.RawBody, &mapBody)
	test.Assert(t, err == nil)
	test.DeepEqual(t, gr.Body, mapBody)

	svr.Stop()
}

func isEqual(a, b interface{}) bool {
	if reflect.TypeOf(a) != reflect.TypeOf(b) {
		return false
	}
	switch a := a.(type) {
	case []interface{}:
		b, ok := b.([]interface{})
		if !ok {
			return false
		}
		if len(a) != len(b) {
			return false
		}
		for i := range a {
			if !isEqual(a[i], b[i]) {
				return false
			}
		}
		return true
	case map[string]interface{}:
		b, ok := b.(map[string]interface{})
		if !ok {
			return false
		}
		if len(a) != len(b) {
			return false
		}
		for k, v1 := range a {
			v2, ok := b[k]
			if !ok || !isEqual(v1, v2) {
				return false
			}
		}
		return true
	default:
		return reflect.DeepEqual(a, b)
	}
}

func checkHeader(t *testing.T, resp *generic.HTTPResponse) {
	test.Assert(t, resp.Header.Get("b") == "true")
	test.Assert(t, resp.Header.Get("eight") == "8")
	test.Assert(t, resp.Header.Get("sixteen") == "16")
	test.Assert(t, resp.Header.Get("thirtytwo") == "32")
	test.Assert(t, resp.Header.Get("sixtyfour") == "64")
	test.Assert(t, resp.Header.Get("d") == "123.45")
	test.Assert(t, resp.Header.Get("T") == "1")
	test.Assert(t, resp.Header.Get("item_count") == "1,2,3")
	test.Assert(t, resp.Header.Get("header_map") == "map[map1:1 map2:2]")
	test.Assert(t, resp.Header.Get("header_struct") == "map[item_id:1 text:1]")
	test.Assert(t, resp.Header.Get("string_set") == "a,b,c")
}
