/*
 * Copyright 2023 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/json"
	"errors"
	"fmt"
	"math"
	"net"
	"reflect"
	"strconv"
	"time"

	"github.com/cloudwego/kitex/client"
	"github.com/cloudwego/kitex/client/genericclient"
	"github.com/cloudwego/kitex/internal/test"
	"github.com/cloudwego/kitex/pkg/generic"
	"github.com/cloudwego/kitex/pkg/kerrors"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
	"github.com/cloudwego/kitex/pkg/transmeta"
	"github.com/cloudwego/kitex/server"
	"github.com/cloudwego/kitex/server/genericserver"
	"github.com/cloudwego/kitex/transport"
)

func newGenericClient(destService string, g generic.Generic, targetIPPort string) genericclient.Client {
	var opts []client.Option
	opts = append(opts, client.WithHostPorts(targetIPPort), client.WithMetaHandler(transmeta.ClientTTHeaderHandler), client.WithTransportProtocol(transport.TTHeader))
	genericCli, _ := genericclient.NewClient(destService, g, opts...)
	return genericCli
}

func newGenericServer(g generic.Generic, addr net.Addr, handler generic.Service) server.Server {
	var opts []server.Option
	opts = append(opts, server.WithServiceAddr(addr), server.WithExitWaitTime(time.Microsecond*10), server.WithMetaHandler(transmeta.ServerTTHeaderHandler))
	svr := genericserver.NewServer(handler, g, opts...)
	go func() {
		err := svr.Run()
		if err != nil {
			panic(err)
		}
	}()
	test.WaitServerStart(addr.String())
	return svr
}

// GenericServiceImpl ...
type TestEchoService struct{}

// GenericCall ...
func (g *TestEchoService) GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error) {
	buf := request.(string)
	if buf == getBizErrReq() {
		return nil, kerrors.NewBizStatusError(404, "not found")
	}
	rpcinfo := rpcinfo.GetRPCInfo(ctx)
	fmt.Printf("Method from Ctx: %s\n", rpcinfo.Invocation().MethodName())
	fmt.Printf("Recv: %v\n", buf)
	fmt.Printf("Method: %s\n", method)
	// Check that request received is correct
	if buf != getEchoReq() {
		return nil, errors.New("incorrect request received")
	}
	return getEchoRes(), nil
}

// GenericService for example.proto
type TestExampleMethodService struct{}

// GenericCall ...
func (g *TestExampleMethodService) GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error) {
	buf := request.(string)
	rpcinfo := rpcinfo.GetRPCInfo(ctx)
	fmt.Printf("Method from Ctx: %s\n", rpcinfo.Invocation().MethodName())
	fmt.Printf("Recv: %v\n", buf)
	fmt.Printf("Method: %s\n", method)
	// Check that request received is correct
	if buf != getExampleMethodReq() {
		return nil, errors.New("incorrect request received")
	}
	return getExampleMethodRes(), nil
}

// GenericService for example.proto
type TestVoidService struct{}

// GenericCall ...
func (g *TestVoidService) GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error) {
	buf := request.(string)
	rpcinfo := rpcinfo.GetRPCInfo(ctx)
	fmt.Printf("Method from Ctx: %s\n", rpcinfo.Invocation().MethodName())
	fmt.Printf("Recv: %v\n", buf)
	fmt.Printf("Method: %s\n", method)
	// Check that request received is correct
	if buf != getVoidReq() {
		return nil, errors.New("incorrect request received")
	}
	return getVoidRes(), nil
}

// GenericService for example2.proto
type TestExampleMethod2Service struct{}

// GenericCall ...
func (g *TestExampleMethod2Service) GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error) {
	buf := request.(string)
	rpcinfo := rpcinfo.GetRPCInfo(ctx)
	fmt.Printf("Method from Ctx: %s\n", rpcinfo.Invocation().MethodName())
	fmt.Printf("Recv: %v\n", buf)
	fmt.Printf("Method: %s\n", method)
	// Check that request received is correct
	var jsonMapResp map[string]interface{}
	var jsonMapOriginal map[string]interface{}
	err = json.Unmarshal([]byte(buf), &jsonMapResp)
	if err != nil {
		return nil, errors.New("json str to map conversion error")
	}
	err = json.Unmarshal([]byte(getExampleMethod2Req()), &jsonMapOriginal)
	if err != nil {
		return nil, errors.New("json str to map conversion error")
	}
	if !reflect.DeepEqual(jsonMapResp, jsonMapOriginal) {
		return nil, errors.New("incorrect request received")
	}
	return getExampleMethod2Res(), nil
}

// GenericService for TestInt2FloatMethod
type TestInt2FloatMethodService struct{}

type ExampleInt2Float struct {
	Int32   int32
	Float64 float64
	String_ string
	Int64   int64
	Subfix  float64
}

// GenericCall ...
func (g *TestInt2FloatMethodService) GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error) {
	buf := request.(string)

	rpcinfo := rpcinfo.GetRPCInfo(ctx)
	fmt.Printf("Method from Ctx: %s\n", rpcinfo.Invocation().MethodName())
	fmt.Printf("Recv: %v\n", buf)
	fmt.Printf("Method: %s\n", method)
	// Check that request received is correct
	if buf != getInt2FloatMethodRes() {
		return nil, errors.New("call failed")
	}
	return getInt2FloatMethodRes(), nil
}

// GenericService for TestInt2FloatMethod2
type TestInt2FloatMethod2Service struct{}

// GenericCall ...
func (g *TestInt2FloatMethod2Service) GenericCall(ctx context.Context, method string, request interface{}) (response interface{}, err error) {
	buf := request.(string)

	rpcinfo := rpcinfo.GetRPCInfo(ctx)
	fmt.Printf("Method from Ctx: %s\n", rpcinfo.Invocation().MethodName())
	fmt.Printf("Recv: %v\n", buf)
	fmt.Printf("Method: %s\n", method)
	// Check that request received is correct
	if buf != getInt2FloatMethod2Req() {
		return nil, errors.New("call failed")
	}
	return getInt2FloatMethod2Res(), nil
}

func getEchoReq() string {
	return `{"message":"this is the request"}`
}

func getBizErrReq() string {
	return `{"message":"return biz error"}`
}

func getEchoRes() string {
	return `{"message":"this is the response"}`
}

func getExampleMethodReq() string {
	return `{"reqs":["req_one","req_two","req_three"]}`
}

func getExampleMethodRes() string {
	return `{"resps":["res_one","res_two","res_three"]}`
}

func getVoidReq() string {
	return `{}`
}

func getVoidRes() string {
	return `{}`
}

func getExampleMethod2Req() string {
	return `{"Msg":"hello","A":25,"InnerBase2":{"Bool":true,"Uint32":123,"Uint64":123,"Int32":123,"SInt64":123,"Double":22.3,"String":"hello_inner","ListInt32":[12,13,14,15,16,17],"MapStringString":{"m1":"aaa","m2":"bbb"},"ListSInt64":[200,201,202,203,204,205],"MapInt32String":{"1":"aaa","2":"bbb","3":"ccc","4":"ddd"},"Binary":"AQIDBA==","MapUint32String":{"1":"u32aa","2":"u32bb","3":"u32cc","4":"u32dd"},"MapUint64String":{"1":"u64aa","2":"u64bb","3":"u64cc","4":"u64dd"},"MapInt64String":{"1":"64aaa","2":"64bbb","3":"64ccc","4":"64ddd"},"MapInt64Base":{"1":{"LogID":"logId","Caller":"caller","Addr":"addr","Client":"client","TrafficEnv":{"Env":"env"},"Extra":{"1a":"aaa","2a":"bbb","3a":"ccc","4a":"ddd"}},"2":{"LogID":"logId2","Caller":"caller2","Addr":"addr2","Client":"client2","TrafficEnv":{"Open":true,"Env":"env2"},"Extra":{"1a":"aaa2","2a":"bbb2","3a":"ccc2","4a":"ddd2"}}},"MapStringBase":{"1":{"LogID":"logId","Caller":"caller","Addr":"addr","Client":"client","TrafficEnv":{"Env":"env"},"Extra":{"1a":"aaa","2a":"bbb","3a":"ccc","4a":"ddd"}},"2":{"LogID":"logId2","Caller":"caller2","Addr":"addr2","Client":"client2","TrafficEnv":{"Open":true,"Env":"env2"},"Extra":{"1a":"aaa2","2a":"bbb2","3a":"ccc2","4a":"ddd2"}}},"ListBase":[{"LogID":"logId","Caller":"caller","Addr":"addr","Client":"client","TrafficEnv":{"Env":"env"},"Extra":{"1a":"aaa","2a":"bbb","3a":"ccc","4a":"ddd"}},{"LogID":"logId2","Caller":"caller2","Addr":"addr2","Client":"client2","TrafficEnv":{"Open":true,"Env":"env2"},"Extra":{"1a":"aaa2","2a":"bbb2","3a":"ccc2","4a":"ddd2"}}],"ListString":["111","222","333","44","51","6"],"Base":{"LogID":"logId","Caller":"caller","Addr":"addr","Client":"client","TrafficEnv":{"Env":"env"},"Extra":{"1b":"aaa","2b":"bbb","3b":"ccc","4b":"ddd"}}}}`
}

func getExampleMethod2Res() string {
	return `{"Msg":"hello","A":25,"InnerBase2":{"Bool":true,"Uint32":123,"Uint64":123,"Int32":123,"SInt64":123,"Double":22.3,"String":"hello_inner","ListInt32":[12,13,14,15,16,17],"MapStringString":{"m1":"aaa","m2":"bbb"},"ListSInt64":[200,201,202,203,204,205],"MapInt32String":{"1":"aaa","2":"bbb","3":"ccc","4":"ddd"},"Binary":"AQIDBA==","MapUint32String":{"1":"u32aa","2":"u32bb","3":"u32cc","4":"u32dd"},"MapUint64String":{"1":"u64aa","2":"u64bb","3":"u64cc","4":"u64dd"},"MapInt64String":{"1":"64aaa","2":"64bbb","3":"64ccc","4":"64ddd"},"MapInt64Base":{"1":{"LogID":"logId","Caller":"caller","Addr":"addr","Client":"client","TrafficEnv":{"Env":"env"},"Extra":{"1a":"aaa","2a":"bbb","3a":"ccc","4a":"ddd"}},"2":{"LogID":"logId2","Caller":"caller2","Addr":"addr2","Client":"client2","TrafficEnv":{"Open":true,"Env":"env2"},"Extra":{"1a":"aaa2","2a":"bbb2","3a":"ccc2","4a":"ddd2"}}},"MapStringBase":{"1":{"LogID":"logId","Caller":"caller","Addr":"addr","Client":"client","TrafficEnv":{"Env":"env"},"Extra":{"1a":"aaa","2a":"bbb","3a":"ccc","4a":"ddd"}},"2":{"LogID":"logId2","Caller":"caller2","Addr":"addr2","Client":"client2","TrafficEnv":{"Open":true,"Env":"env2"},"Extra":{"1a":"aaa2","2a":"bbb2","3a":"ccc2","4a":"ddd2"}}},"ListBase":[{"LogID":"logId","Caller":"caller","Addr":"addr","Client":"client","TrafficEnv":{"Env":"env"},"Extra":{"1a":"aaa","2a":"bbb","3a":"ccc","4a":"ddd"}},{"LogID":"logId2","Caller":"caller2","Addr":"addr2","Client":"client2","TrafficEnv":{"Open":true,"Env":"env2"},"Extra":{"1a":"aaa2","2a":"bbb2","3a":"ccc2","4a":"ddd2"}}],"ListString":["111","222","333","44","51","6"],"Base":{"LogID":"logId","Caller":"caller","Addr":"addr","Client":"client","TrafficEnv":{"Env":"env"},"Extra":{"1b":"aaa","2b":"bbb","3b":"ccc","4b":"ddd"}}}}`
}

func getInt2FloatMethodReq() string {
	return `{"Int32":1,"Float64":3.14,"String":"hello","Int64":2,"Subfix":0.92653}`
}

func getInt2FloatMethodRes() string {
	return `{"Int32":1,"Float64":3.14,"String":"hello","Int64":2,"Subfix":0.92653}`
}

func getInt2FloatMethod2Res() string {
	return `{"Int64":` + strconv.Itoa(math.MaxInt64) + `}`
}

func getInt2FloatMethod2Req() string {
	return `{"Int64":` + strconv.Itoa(math.MaxInt64) + `}`
}
