/*
 * 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 (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"math"
	"net"
	"reflect"
	"strings"
	"testing"
	"time"

	"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/pkg/generic/thrift"
	"github.com/cloudwego/kitex/server"
	"github.com/cloudwego/kitex/transport"
)

func TestRun(t *testing.T) {
	t.Run("TestThrift", testThrift)
	t.Run("TestThriftWithDynamicGo", testThriftWithDynamicGo)
	t.Run("TestThriftPingMethod", testThriftPingMethod)
	t.Run("TestThriftPingMethodWithDynamicGo", testThriftPingMethodWithDynamicGo)
	t.Run("TestThriftError", testThriftError)
	t.Run("TestThriftOnewayMethod", testThriftOnewayMethod)
	t.Run("TestThriftOnewayMethodWithDynamicGo", testThriftOnewayMethodWithDynamicGo)
	t.Run("TestThriftVoidMethod", testThriftVoidMethod)
	t.Run("TestThriftVoidMethodWithDynamicGo", testThriftVoidMethodWithDynamicGo)
	t.Run("TestThrift2NormalServer", testThrift2NormalServer)
	t.Run("TestThriftException", testThriftException)
	t.Run("TestThriftRawBinaryEcho", testThriftRawBinaryEcho)
	t.Run("TestThriftBase64BinaryEcho", testThriftBase64BinaryEcho)
	t.Run("TestRegression", testRegression)
	t.Run("TestParseModeWithDynamicGo", testParseModeWithDynamicGo)
}

func testThrift(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceImpl), "./idl/example.thrift", nil, nil, false)

	// normal way
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err := cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respStr, ok := resp.(string)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(gjson.Get(respStr, "Msg").String(), "world"), gjson.Get(respStr, "Msg").String())
	test.Assert(t, reflect.DeepEqual(gjson.Get(respStr, "I8").String(), "8"), gjson.Get(respStr, "I8").String())

	// extend method
	resp, err = cli.GenericCall(context.Background(), "ExtendMethod", reqExtendMsg, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respStr, ok = resp.(string)
	test.Assert(t, ok)
	test.Assert(t, respStr == reqExtendMsg)

	svr.Stop()
}

func testThriftWithDynamicGo(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceImpl), "./idl/example.thrift", nil, nil, true)

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, true)
	resp, err := cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respStr, ok := resp.(string)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(gjson.Get(respStr, "Msg").String(), "world"), gjson.Get(respStr, "Msg").String())
	test.Assert(t, reflect.DeepEqual(gjson.Get(respStr, "I8").String(), "8"), gjson.Get(respStr, "I8").String())

	// client without dynamicgo

	// server side:
	//  write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	//  read: dynamicgo
	cli = initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err = cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respStr, ok = resp.(string)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(gjson.Get(respStr, "Msg").String(), "world"), gjson.Get(respStr, "Msg").String())

	// server side:
	//  write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	//  read: fallback
	cli = initThriftClient(transport.PurePayload, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err = cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respStr, ok = resp.(string)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(gjson.Get(respStr, "Msg").String(), "world"), gjson.Get(respStr, "Msg").String())

	svr.Stop()
}

func BenchmarkCompareDynamicGoAndOriginal_Small(b *testing.B) {
	// small data
	sobj := getSimpleValue()
	sout, err := json.Marshal(sobj)
	if err != nil {
		panic(err)
	}
	simpleJSON := string(sout)
	fmt.Println("small data size: ", len(simpleJSON))

	t := testing.T{}

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

		resp, err := cli.GenericCall(context.Background(), "SimpleMethod", simpleJSON, callopt.WithRPCTimeout(100*time.Second))
		test.Assert(&t, err == nil, err)
		respStr, ok := resp.(string)
		test.Assert(&t, ok)
		test.Assert(&t, gjson.Get(respStr, "I64Field").Int() == math.MaxInt64, math.MaxInt64)

		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			cli.GenericCall(context.Background(), "SimpleMethod", simpleJSON, 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", nil, nil, false)
		cli := initThriftClient(transport.TTHeader, &t, addr, "./idl/baseline.thrift", nil, nil, false)

		resp, err := cli.GenericCall(context.Background(), "SimpleMethod", simpleJSON, callopt.WithRPCTimeout(100*time.Second))
		test.Assert(&t, err == nil, err)
		respStr, ok := resp.(string)
		test.Assert(&t, ok)
		test.Assert(&t, gjson.Get(respStr, "I64Field").Int() == math.MaxInt64, math.MaxInt64)

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

func BenchmarkCompareDynamicGoAndOriginal_Medium(b *testing.B) {
	// medium data
	nobj := getNestingValue()
	nout, err := json.Marshal(nobj)
	if err != nil {
		panic(err)
	}
	nestingJSON := string(nout)
	fmt.Println("medium data size: ", len(nestingJSON))

	t := testing.T{}

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

		resp, err := cli.GenericCall(context.Background(), "NestingMethod", nestingJSON, callopt.WithRPCTimeout(100*time.Second))
		test.Assert(&t, err == nil, err)
		respStr, ok := resp.(string)
		test.Assert(&t, ok)
		test.Assert(&t, gjson.Get(respStr, "Double").Float() == math.MaxFloat64, math.MaxFloat64)

		b.ResetTimer()
		for i := 0; i < b.N; i++ {
			cli.GenericCall(context.Background(), "NestingMethod", nestingJSON, 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", nil, nil, false)
		cli := initThriftClient(transport.TTHeader, &t, addr, "./idl/baseline.thrift", nil, nil, false)

		resp, err := cli.GenericCall(context.Background(), "NestingMethod", nestingJSON, callopt.WithRPCTimeout(100*time.Second))
		test.Assert(&t, err == nil, err)
		respStr, ok := resp.(string)
		test.Assert(&t, ok)
		test.Assert(&t, gjson.Get(respStr, "Double").Float() == math.MaxFloat64, math.MaxFloat64)

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

func testThriftPingMethod(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServicePingImpl), "./idl/example.thrift", nil, nil, false)

	// normal way
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err := cli.GenericCall(context.Background(), "Ping", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respMap, ok := resp.(string)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(respMap, "hello"), respMap)

	svr.Stop()
}

func testThriftPingMethodWithDynamicGo(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServicePingImpl), "./idl/example.thrift", nil, nil, true)

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, true)
	resp, err := cli.GenericCall(context.Background(), "Ping", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respMap, ok := resp.(string)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(respMap, "hello"), respMap)

	// client without dynamicgo

	// server side:
	//  write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	//  read: dynamicgo
	cli = initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err = cli.GenericCall(context.Background(), "Ping", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respMap, ok = resp.(string)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(respMap, "hello"), respMap)

	// server side:
	//  write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	//  read: fallback
	cli = initThriftClient(transport.PurePayload, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err = cli.GenericCall(context.Background(), "Ping", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respMap, ok = resp.(string)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(respMap, "hello"), respMap)

	svr.Stop()
}

func testThriftError(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceErrorImpl), "./idl/example.thrift", nil, nil, false)

	// normal way
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	_, err := cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err != nil)
	test.Assert(t, strings.Contains(err.Error(), errResp), err.Error())

	// with dynamicgo
	cli = initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, true)
	_, err = cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err != nil)
	test.Assert(t, strings.Contains(err.Error(), errResp), err.Error())

	svr.Stop()

	// server with dynamicgo
	addr = test.GetLocalAddress()
	svr = initThriftServer(t, addr, new(GenericServiceErrorImpl), "./idl/example.thrift", nil, nil, true)

	// normal way
	cli = initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	_, err = cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err != nil)
	test.Assert(t, strings.Contains(err.Error(), errResp), err.Error())

	// with dynamicgo
	cli = initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, true)
	_, err = cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err != nil)
	test.Assert(t, strings.Contains(err.Error(), errResp), err.Error())

	svr.Stop()
}

func testThriftOnewayMethod(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceOnewayImpl), "./idl/example.thrift", nil, nil, false)

	// normal way
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err := cli.GenericCall(context.Background(), "Oneway", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	test.Assert(t, resp == nil)

	svr.Stop()
}

func testThriftOnewayMethodWithDynamicGo(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceOnewayImpl), "./idl/example.thrift", nil, nil, true)

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, true)
	resp, err := cli.GenericCall(context.Background(), "Oneway", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	test.Assert(t, resp == nil)

	// client without dynamicgo

	// server side:
	//  write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	//  read: dynamicgo
	cli = initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err = cli.GenericCall(context.Background(), "Oneway", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	test.Assert(t, resp == nil)

	// server side:
	//  write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	//  read: fallback
	cli = initThriftClient(transport.PurePayload, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err = cli.GenericCall(context.Background(), "Oneway", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	test.Assert(t, resp == nil)

	svr.Stop()
}

func testThriftVoidMethod(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceVoidImpl), "./idl/example.thrift", nil, nil, false)

	// normal way
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err := cli.GenericCall(context.Background(), "Void", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	test.Assert(t, resp == descriptor.Void{})

	resp, err = cli.GenericCall(context.Background(), "VoidWithString", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	test.Assert(t, resp == descriptor.Void{})

	svr.Stop()
}

func testThriftVoidMethodWithDynamicGo(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceVoidImpl), "./idl/example.thrift", nil, nil, true)

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, true)
	resp, err := cli.GenericCall(context.Background(), "Void", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	test.Assert(t, resp == descriptor.Void{})

	// client without dynamicgo

	// server side:
	//  write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	//  read: dynamicgo
	cli = initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err = cli.GenericCall(context.Background(), "Void", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	test.Assert(t, resp == descriptor.Void{})

	// server side:
	//  write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	//  read: fallback
	cli = initThriftClient(transport.PurePayload, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err = cli.GenericCall(context.Background(), "Void", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	test.Assert(t, resp == descriptor.Void{})

	resp, err = cli.GenericCall(context.Background(), "VoidWithString", "hello", callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	test.Assert(t, resp == descriptor.Void{})

	svr.Stop()
}

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

	// client without dynamicgo
	cli := initThriftMockClient(t, transport.TTHeader, false, addr)
	_, err := cli.GenericCall(context.Background(), "Test", mockReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)

	// client with dynamicgo
	cli = initThriftMockClient(t, transport.TTHeader, true, addr)
	_, err = cli.GenericCall(context.Background(), "Test", mockReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)

	svr.Stop()
}

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

	// client without dynamicgo
	cli := initThriftMockClient(t, transport.TTHeader, false, addr)

	_, err := cli.GenericCall(context.Background(), "ExceptionTest", mockReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err != nil, err)
	test.DeepEqual(t, err.Error(), `remote or network error[remote]: map[string]interface {}{"code":400, "msg":"this is an exception"}`)

	// client with dynaimcgo
	cli = initThriftMockClient(t, transport.TTHeader, true, addr)

	_, err = cli.GenericCall(context.Background(), "ExceptionTest", mockReq, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err != nil, err)
	test.DeepEqual(t, err.Error(), `remote or network error[remote]: {"code":400,"msg":"this is an exception"}`)

	svr.Stop()
}

func testThriftRawBinaryEcho(t *testing.T) {
	var opts []generic.Option
	opts = append(opts, generic.WithCustomDynamicGoConvOpts(&conv.Options{WriteRequireField: true, WriteDefaultField: true, NoBase64Binary: true}))
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceBinaryEchoImpl), "./idl/binary_echo.thrift", opts, &(&struct{ x bool }{false}).x, false)

	// client without dynamicgo
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/binary_echo.thrift", opts, &(&struct{ x bool }{false}).x, false)

	req := "{\"msg\":\"" + mockMyMsg + "\", \"got_base64\":false}"
	resp, err := cli.GenericCall(context.Background(), "BinaryEcho", req, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	sr, ok := resp.(string)
	test.Assert(t, ok)
	test.Assert(t, strings.Contains(sr, mockMyMsg))

	// client with dynamicgo
	cli = initThriftClient(transport.PurePayload, t, addr, "./idl/binary_echo.thrift", opts, &(&struct{ x bool }{false}).x, true)

	req = "{\"msg\":\"" + mockMyMsg + "\", \"got_base64\":false}"
	resp, err = cli.GenericCall(context.Background(), "BinaryEcho", req, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	sr, ok = resp.(string)
	test.Assert(t, ok)
	test.Assert(t, strings.Contains(sr, mockMyMsg))

	svr.Stop()
}

func testThriftBase64BinaryEcho(t *testing.T) {
	var opts []generic.Option
	opts = append(opts, generic.WithCustomDynamicGoConvOpts(&conv.Options{WriteRequireField: true, WriteDefaultField: true, NoBase64Binary: false}))
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericServiceBinaryEchoImpl), "./idl/binary_echo.thrift", opts, nil, false)

	// client without dynamicgo
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/binary_echo.thrift", opts, nil, false)

	base64MockMyMsg := base64.StdEncoding.EncodeToString([]byte(mockMyMsg))
	req := "{\"msg\":\"" + base64MockMyMsg + "\", \"got_base64\":true}"
	resp, err := cli.GenericCall(context.Background(), "BinaryEcho", req, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	sr, ok := resp.(string)
	test.Assert(t, ok)
	test.Assert(t, strings.Contains(sr, base64MockMyMsg))

	// client with dynamicgo
	cli = initThriftClient(transport.PurePayload, t, addr, "./idl/binary_echo.thrift", opts, nil, true)

	base64MockMyMsg = base64.StdEncoding.EncodeToString([]byte(mockMyMsg))
	req = "{\"msg\":\"" + base64MockMyMsg + "\", \"got_base64\":true}"
	resp, err = cli.GenericCall(context.Background(), "BinaryEcho", req, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	sr, ok = resp.(string)
	test.Assert(t, ok)
	test.Assert(t, strings.Contains(sr, base64MockMyMsg))

	svr.Stop()
}

func testRegression(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initThriftServer(t, addr, new(GenericRegressionImpl), "./idl/example.thrift", nil, nil, false)

	// normal way
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, false)
	resp, err := cli.GenericCall(context.Background(), "ExampleMethod", reqRegression, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respStr, ok := resp.(string)
	test.Assert(t, ok)
	test.Assert(t, gjson.Get(respStr, "num").Type == gjson.String)
	test.Assert(t, gjson.Get(respStr, "num").String() == "64")
	test.Assert(t, gjson.Get(respStr, "I8").Type == gjson.Number)
	test.Assert(t, gjson.Get(respStr, "I8").Int() == int64(8))
	test.Assert(t, gjson.Get(respStr, "I16").Type == gjson.Number)
	test.Assert(t, gjson.Get(respStr, "I32").Type == gjson.Number)
	test.Assert(t, gjson.Get(respStr, "I64").Type == gjson.Number)
	test.Assert(t, gjson.Get(respStr, "Double").Type == gjson.Number)
	test.Assert(t, gjson.Get(respStr, "Double").Float() == 12.3)

	svr.Stop()

	addr = test.GetLocalAddress()
	svr = initThriftServer(t, addr, new(GenericRegressionImpl), "./idl/example.thrift", nil, nil, true)

	// dynamicgo way
	cli = initThriftClient(transport.TTHeader, t, addr, "./idl/example.thrift", nil, nil, true)
	resp, err = cli.GenericCall(context.Background(), "ExampleMethod", reqRegression, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respStr, ok = resp.(string)
	test.Assert(t, ok)
	test.Assert(t, gjson.Get(respStr, "num").Type == gjson.String)
	test.Assert(t, gjson.Get(respStr, "num").String() == "64")
	test.Assert(t, gjson.Get(respStr, "I8").Type == gjson.Number)
	test.Assert(t, gjson.Get(respStr, "I8").Int() == int64(8))
	test.Assert(t, gjson.Get(respStr, "I16").Type == gjson.Number)
	test.Assert(t, gjson.Get(respStr, "I32").Type == gjson.Number)
	test.Assert(t, gjson.Get(respStr, "I64").Type == gjson.Number)
	test.Assert(t, gjson.Get(respStr, "Double").Type == gjson.Number)
	test.Assert(t, gjson.Get(respStr, "Double").Float() == 12.3)

	svr.Stop()
}

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

	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/mock_unknown_method.thrift", nil, nil, false)
	resp, err := cli.GenericCall(context.Background(), "UnknownMethod", reqMsg)
	test.Assert(t, resp == nil)
	test.Assert(t, err != nil)
	test.DeepEqual(t, err.Error(), "remote or network error[remote]: unknown service , method UnknownMethod")

	svr.Stop()
}

func initThriftMockClient(t *testing.T, tp transport.Protocol, enableDynamicGo bool, address string) genericclient.Client {
	var p generic.DescriptorProvider
	var err error
	if enableDynamicGo {
		p, err = generic.NewThriftFileProviderWithDynamicGo("./idl/mock.thrift")
	} else {
		p, err = generic.NewThriftFileProvider("./idl/mock.thrift")
	}
	test.Assert(t, err == nil)
	g, err := generic.JSONThriftGeneric(p)
	test.Assert(t, err == nil)
	cli := newGenericClient(tp, "destServiceName", g, address)
	test.Assert(t, err == nil)
	return cli
}

func initThriftClient(tp transport.Protocol, t *testing.T, addr, idl string, opts []generic.Option, base64Binary *bool, 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.JSONThriftGeneric(p, opts...)
	test.Assert(t, err == nil)
	if base64Binary != nil {
		generic.SetBinaryWithBase64(g, *base64Binary)
	}
	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, opts []generic.Option, base64Binary *bool, enableDynamicGo bool) server.Server {
	addr, _ := net.ResolveTCPAddr("tcp", address)
	var p generic.DescriptorProvider
	var err error
	if enableDynamicGo {
		p, err = generic.NewThriftFileProviderWithDynamicGo(idlPath)
	} else {
		p, err = generic.NewThriftFileProvider(idlPath)
	}
	test.Assert(t, err == nil)
	g, err := generic.JSONThriftGeneric(p, opts...)
	test.Assert(t, err == nil)
	if base64Binary != nil {
		generic.SetBinaryWithBase64(g, *base64Binary)
	}
	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)
	test.WaitServerStart(addr.String())
	return svr
}

func testParseModeWithDynamicGo(t *testing.T) {
	addr := test.GetLocalAddress()
	thrift.SetDefaultParseMode(thrift.FirstServiceOnly)
	svr := initThriftServer(t, addr, new(GenericServiceImpl), "./idl/example_multi_service.thrift", nil, nil, true)

	// write: dynamicgo (amd64 && go1.16), fallback (arm || !go1.16)
	// read: dynamicgo
	cli := initThriftClient(transport.TTHeader, t, addr, "./idl/example_multi_service.thrift", nil, nil, true)
	resp, err := cli.GenericCall(context.Background(), "ExampleMethod", reqMsg, callopt.WithRPCTimeout(100*time.Second))
	test.Assert(t, err == nil, err)
	respStr, ok := resp.(string)
	test.Assert(t, ok)
	test.Assert(t, reflect.DeepEqual(gjson.Get(respStr, "Msg").String(), "world"), "world")

	svr.Stop()
}
