/*
 * 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"
	"fmt"
	"net"
	"reflect"
	"testing"

	"github.com/cloudwego/dynamicgo/proto"

	"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/server"
)

func TestRun(t *testing.T) {
	t.Run("TestEcho", TestEcho)
	t.Run("TestExampleMethod", TestExampleMethod)
	t.Run("TestVoid", TestVoid)
	t.Run("TestExampleMethod2", TestExampleMethod2)
	t.Run("TestInt2FloatMethod", TestInt2FloatMethod)
	t.Run("TestInt2FloatMethod2", TestInt2FloatMethod2)
}

func initPbServerByIDLDynamicGo(t *testing.T, address string, handler generic.Service, pbIdl string) server.Server {
	// initialise DescriptorProvider for DynamicGo
	opts := proto.Options{}
	p, err := generic.NewPbFileProviderWithDynamicGo(pbIdl, context.Background(), opts)

	test.Assert(t, err == nil)

	addr, _ := net.ResolveTCPAddr("tcp", address)

	g, err := generic.JSONPbGeneric(p)
	test.Assert(t, err == nil)
	svr := newGenericServer(g, addr, handler)
	return svr
}

func initPbClientByIDLDynamicGo(t *testing.T, addr, destSvcName, pbIdl string) genericclient.Client {
	// initialise dynamicgo proto.ServiceDescriptor
	opts := proto.Options{}
	p, err := generic.NewPbFileProviderWithDynamicGo(pbIdl, context.Background(), opts)

	test.Assert(t, err == nil)

	g, err := generic.JSONPbGeneric(p)
	test.Assert(t, err == nil)
	cli := newGenericClient(destSvcName, g, addr)

	return cli
}

func TestEcho(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initPbServerByIDLDynamicGo(t, addr, new(TestEchoService), "./idl/echo.proto")
	cli := initPbClientByIDLDynamicGo(t, addr, "EchoService", "./idl/echo.proto")

	ctx := context.Background()

	// 'Echo' method name must be passed as param
	resp, err := cli.GenericCall(ctx, "Echo", getEchoReq())
	// resp is a JSON string
	test.Assert(t, err == nil)
	respMap, ok := resp.(string)
	test.Assert(t, ok)

	fmt.Println(respMap)
	test.Assert(t, reflect.DeepEqual(respMap, getEchoRes()), respMap)

	svr.Stop()
}

func TestBizErr(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initPbServerByIDLDynamicGo(t, addr, new(TestEchoService), "./idl/echo.proto")
	cli := initPbClientByIDLDynamicGo(t, addr, "EchoService", "./idl/echo.proto")

	ctx := context.Background()

	// 'Echo' method name must be passed as param
	_, err := cli.GenericCall(ctx, "Echo", getBizErrReq())
	// resp is a JSON string
	bizerr, ok := kerrors.FromBizStatusError(err)
	test.Assert(t, ok)
	test.Assert(t, bizerr.BizStatusCode() == 404)
	test.Assert(t, bizerr.BizMessage() == "not found")

	svr.Stop()
}

func TestExampleMethod(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initPbServerByIDLDynamicGo(t, addr, new(TestExampleMethodService), "./idl/example.proto")
	cli := initPbClientByIDLDynamicGo(t, addr, "ExampleService", "./idl/example.proto")

	ctx := context.Background()

	// 'ExampleMethod' method name must be passed as param
	resp, err := cli.GenericCall(ctx, "ExampleMethod", getExampleMethodReq())
	// resp is a JSON string
	test.Assert(t, err == nil)
	respMap, ok := resp.(string)
	test.Assert(t, ok)

	fmt.Println(respMap)
	test.Assert(t, reflect.DeepEqual(respMap, getExampleMethodRes()), respMap)

	svr.Stop()
}

func TestVoid(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initPbServerByIDLDynamicGo(t, addr, new(TestVoidService), "./idl/example.proto")
	cli := initPbClientByIDLDynamicGo(t, addr, "ExampleService", "./idl/example.proto")

	ctx := context.Background()

	// 'VoidMethod' method name must be passed as param
	resp, err := cli.GenericCall(ctx, "VoidMethod", getVoidReq())
	test.Assert(t, err == nil)
	respMap, ok := resp.(string)
	test.Assert(t, ok)

	fmt.Println(respMap)
	test.Assert(t, reflect.DeepEqual(respMap, getVoidRes()), respMap)

	svr.Stop()
}

func TestExampleMethod2(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initPbServerByIDLDynamicGo(t, addr, new(TestExampleMethod2Service), "./idl/example2.proto")
	cli := initPbClientByIDLDynamicGo(t, addr, "ExampleMethod", "./idl/example2.proto")

	ctx := context.Background()

	// 'ExampleMethod' method name must be passed as param
	resp, err := cli.GenericCall(ctx, "ExampleMethod", getExampleMethod2Req())
	// resp is a JSON string
	test.Assert(t, err == nil, err)

	fmt.Println(resp)

	var jsonMapResp map[string]interface{}
	var jsonMapOriginal map[string]interface{}
	err = json.Unmarshal([]byte(resp.(string)), &jsonMapResp)
	test.Assert(t, err == nil)
	err = json.Unmarshal([]byte(getExampleMethod2Res()), &jsonMapOriginal)
	test.Assert(t, err == nil)
	test.Assert(t, MapsEqual(jsonMapResp, jsonMapOriginal))

	svr.Stop()
}

func TestInt2FloatMethod(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initPbServerByIDLDynamicGo(t, addr, new(TestInt2FloatMethodService), "./idl/example2.proto")
	cli := initPbClientByIDLDynamicGo(t, addr, "Int2FloatMethod", "./idl/example2.proto")

	ctx := context.Background()

	// 'Int2FloatMethod' method name must be passed as param
	resp, err := cli.GenericCall(ctx, "Int2FloatMethod", getInt2FloatMethodReq())
	// resp is a JSON string
	test.Assert(t, err == nil)
	respMap, ok := resp.(string)
	test.Assert(t, ok)

	fmt.Println(respMap)
	test.Assert(t, reflect.DeepEqual(respMap, getInt2FloatMethodRes()), respMap)

	svr.Stop()
}

func TestInt2FloatMethod2(t *testing.T) {
	addr := test.GetLocalAddress()
	svr := initPbServerByIDLDynamicGo(t, addr, new(TestInt2FloatMethod2Service), "./idl/example2.proto")
	cli := initPbClientByIDLDynamicGo(t, addr, "Int2FloatMethod2", "./idl/example2.proto")

	ctx := context.Background()

	// 'Int2FloatMethod' method name must be passed as param
	resp, err := cli.GenericCall(ctx, "Int2FloatMethod", getInt2FloatMethod2Req())
	test.Assert(t, err == nil)
	respMap, ok := resp.(string)
	test.Assert(t, ok)

	fmt.Println(respMap)
	test.Assert(t, reflect.DeepEqual(respMap, getInt2FloatMethod2Res()), respMap)

	svr.Stop()
}

func MapsEqual(map1, map2 map[string]interface{}) bool {
	if len(map1) != len(map2) {
		return false
	}

	for key, value1 := range map1 {
		value2, ok := map2[key]
		if !ok || !reflect.DeepEqual(value1, value2) {
			return false
		}
	}

	return true
}
