/*
 * 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 dubbo3

import (
	"context"
	"reflect"
	"testing"
	"time"
)

import (
	hessian "github.com/apache/dubbo-go-hessian2"

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

import (
	"dubbo.apache.org/dubbo-go/v3/common"
	"dubbo.apache.org/dubbo-go/v3/protocol/base"
)

const (
	mockDubbo3CommonUrl = "tri://127.0.0.1:20002/DubboGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=greeterProvider" +
		"&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=org.apache.dubbo.DubboGreeterImpl" +
		"&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=" +
		"&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown&registry.role=3&remote.timestamp=1576923717&retries=" +
		"&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider&timestamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!"
)

func TestDubboProtocolExport(t *testing.T) {
	// Export
	addService()

	proto := GetProtocol()
	url, err := common.NewURL(mockDubbo3CommonUrl)
	assert.NoError(t, err)
	exporter := proto.Export(base.NewBaseInvoker(url))
	time.Sleep(time.Second)

	// make sure url
	eq := exporter.GetInvoker().GetURL().URLEqual(url)
	assert.True(t, eq)

	// make sure exporterMap after 'UnExport'
	_, ok := proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey())
	assert.True(t, ok)
	exporter.UnExport()
	_, ok = proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey())
	assert.False(t, ok)

	// make sure serverMap after 'Destroy'
	_, ok = proto.(*DubboProtocol).serverMap[url.Location]
	assert.True(t, ok)
	proto.Destroy()
	_, ok = proto.(*DubboProtocol).serverMap[url.Location]
	assert.False(t, ok)
}

func TestDubboProtocolRefer(t *testing.T) {
	proto := GetProtocol()
	url, err := common.NewURL(mockDubbo3CommonUrl)
	assert.NoError(t, err)
	invoker := proto.Refer(url)

	// make sure url
	eq := invoker.GetURL().URLEqual(url)
	assert.True(t, eq)

	// make sure invokers after 'Destroy'
	invokersLen := len(proto.(*DubboProtocol).Invokers())
	assert.Equal(t, 1, invokersLen)
	proto.Destroy()
	invokersLen = len(proto.(*DubboProtocol).Invokers())
	assert.Equal(t, 0, invokersLen)
}

type MockUser struct {
	Name string
}

func (m *MockUser) JavaClassName() string {
	return "mockuser"
}

type MockService struct {
}

func (m *MockService) GetUser(ctx context.Context, user, user2 *MockUser) (*MockUser, error) {
	return user, nil
}

func TestDubbo3UnaryService_GetReqParamsInterfaces(t *testing.T) {
	hessian.RegisterPOJO(&MockUser{})
	srv := UnaryService{}
	valueOf := reflect.ValueOf(&MockService{})
	typeOf := valueOf.Type()
	numField := valueOf.NumMethod()
	for i := 0; i < numField; i++ {
		ft := typeOf.Method(i)
		// num in/out is checked in common/rpc_service.go
		typs := make([]reflect.Type, 0)
		for j := 2; j < ft.Type.NumIn(); j++ {
			typs = append(typs, ft.Type.In(j))
		}
		srv.setReqParamsTypes("GetUser", typs)
	}
	paramsInterfaces, ok := srv.GetReqParamsInterfaces("GetUser")
	assert.True(t, ok)
	enc := hessian.NewEncoder()
	err := enc.Encode(&MockUser{
		Name: "laurence",
	})
	assert.Nil(t, err)
	data := enc.Buffer()
	decoder := hessian.NewDecoder(data)
	val, err := decoder.Decode()
	assert.Nil(t, err)
	assert.Equal(t, 2, len(paramsInterfaces))
	subTest(t, val, paramsInterfaces)
	args := make([]any, 0, 1)
	for _, v := range paramsInterfaces {
		tempParamObj := reflect.ValueOf(v).Elem().Interface()
		args = append(args, tempParamObj)
	}
	assert.Equal(t, "laurence", args[0].(*MockUser).Name)
	assert.Equal(t, "laurence", args[1].(*MockUser).Name)
}

func subTest(t *testing.T, val, paramsInterfaces any) {
	list := paramsInterfaces.([]any)
	for k := range list {
		err := hessian.ReflectResponse(val, list[k])
		assert.Nil(t, err)
	}
}
