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

import (
	"fmt"
	"github.com/apache/servicecomb-mesher/proxy/bootstrap"
	"github.com/apache/servicecomb-mesher/proxy/cmd"
	"github.com/apache/servicecomb-mesher/proxy/common"
	mesherCommon "github.com/apache/servicecomb-mesher/proxy/common"
	mesherRuntime "github.com/apache/servicecomb-mesher/proxy/pkg/runtime"
	dubboclient "github.com/apache/servicecomb-mesher/proxy/protocol/dubbo/client"
	"github.com/apache/servicecomb-mesher/proxy/protocol/dubbo/dubbo"
	"github.com/go-chassis/go-chassis/v2"
	chassisCommon "github.com/go-chassis/go-chassis/v2/core/common"
	"github.com/go-chassis/go-chassis/v2/core/config"
	"github.com/go-chassis/go-chassis/v2/core/config/model"
	"github.com/go-chassis/go-chassis/v2/core/handler"
	"github.com/go-chassis/go-chassis/v2/core/invocation"
	"github.com/go-chassis/go-chassis/v2/core/lager"
	"github.com/go-chassis/go-chassis/v2/core/loadbalancer"
	"github.com/go-chassis/go-chassis/v2/core/registry"
	"github.com/go-chassis/go-chassis/v2/third_party/forked/afex/hystrix-go/hystrix"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"os"
	"strings"
	"testing"

	// rate limiter handler
	_ "github.com/go-chassis/go-chassis/v2/middleware/ratelimiter"
)

func init() {
	lager.Init(&lager.Options{LoggerLevel: "DEBUG"})
}

func TestSetLocalServiceAddress(t *testing.T) {
	t.Run("Run not set env EnvServicePorts", func(t *testing.T) {
		inv := &invocation.Invocation{Protocol: "rest"}
		cmd.Init()
		_ = cmd.Configs.GeneratePortsMap()
		t.Log(cmd.Configs.PortsMap)
		err := SetLocalServiceAddress(inv)
		assert.Error(t, err)

		// case port
		inv.PortName = "8080"
		err = SetLocalServiceAddress(inv)
		assert.NoError(t, err)
	})

	t.Run("Run with env EnvServicePorts", func(t *testing.T) {
		inv := &invocation.Invocation{Protocol: "rest"}
		os.Setenv(common.EnvServicePorts, "rest:8080,grpc:90")
		cmd.Init()
		_ = cmd.Configs.GeneratePortsMap()
		t.Log(cmd.Configs.PortsMap)
		err := SetLocalServiceAddress(inv)
		assert.NoError(t, err)
	})
}

func TestHandle(t *testing.T) {
	t.Run("Run as Provider", func(t *testing.T) {
		os.Setenv(common.EnvServicePorts, "dubbo:8081,rest:8080")
		cmd.Init()
		_ = cmd.Configs.GeneratePortsMap()

		protoMap := make(map[string]model.Protocol)
		config.GlobalDefinition = &model.GlobalCfg{
			ServiceComb: model.ServiceComb{
				Protocols: protoMap,
			},
		}

		bootstrap.RegisterFramework()
		bootstrap.SetHandlers()
		chassis.Init()

		consumerChain := strings.Join([]string{
			handler.Router,
			//"ratelimiter-consumer",
			//"bizkeeper-consumer",
			//handler.Loadbalance,
			//handler.Transport,
		}, ",")
		providerChain := strings.Join([]string{
			//handler.RateLimiterProvider,
			//handler.Transport,
		}, ",")
		consumerChainMap := map[string]string{
			common.ChainConsumerOutgoing: consumerChain,
		}
		providerChainMap := map[string]string{
			common.ChainProviderIncoming: providerChain,
			"default":                    "ratelimiter-provider",
		}

		registry.DefaultContractDiscoveryService = new(MockContractDiscoveryService)
		mesherRuntime.Role = mesherCommon.RoleSidecar

		req := dubbo.NewDubboRequest()
		req.SetAttachment(dubbo.PathKey, "hello")
		ctx := &dubbo.InvokeContext{req, &dubbo.DubboRsp{}, nil, "", "127.0.0.1:9090"}
		ctx.Rsp.Init()

		// case get chain error
		err := Handle(ctx)
		assert.Error(t, err)

		// case get chain ok
		handler.CreateChains(chassisCommon.Provider, providerChainMap)
		handler.CreateChains(chassisCommon.Consumer, consumerChainMap)
		err = Handle(ctx)
		assert.NoError(t, err)

		handler.ChainMap = make(map[string]*handler.Chain)
	})

	t.Run("Run as Consumer", func(t *testing.T) {
		//os.Setenv(common.EnvServicePorts, "rest:8080,grpc:90")
		os.Unsetenv(common.EnvServicePorts)
		cmd.Init()
		_ = cmd.Configs.GeneratePortsMap()

		protoMap := make(map[string]model.Protocol)
		config.GlobalDefinition = &model.GlobalCfg{
			ServiceComb: model.ServiceComb{
				Protocols: protoMap,
			},
		}

		bootstrap.RegisterFramework()
		bootstrap.SetHandlers()
		chassis.Init()

		consumerChain := strings.Join([]string{
			handler.Router,
			//"ratelimiter-consumer",
			//"bizkeeper-consumer",
			//handler.Loadbalance,
			//handler.Transport,
		}, ",")
		providerChain := strings.Join([]string{
			//handler.RateLimiterProvider,
			//handler.Transport,
		}, ",")
		consumerChainMap := map[string]string{
			common.ChainConsumerOutgoing: consumerChain,
		}
		providerChainMap := map[string]string{
			common.ChainProviderIncoming: providerChain,
			"default":                    "ratelimiter-provider",
		}

		registry.DefaultContractDiscoveryService = new(MockContractDiscoveryService)
		mesherRuntime.Role = mesherCommon.RoleSidecar

		req := dubbo.NewDubboRequest()
		req.SetAttachment(dubbo.PathKey, "hello")
		ctx := &dubbo.InvokeContext{req, &dubbo.DubboRsp{}, nil, "", "127.0.0.1:9090"}
		ctx.Rsp.Init()

		// case get chain error
		err := Handle(ctx)
		assert.Error(t, err)

		// case get chain ok
		handler.CreateChains(chassisCommon.Provider, providerChainMap)
		handler.CreateChains(chassisCommon.Consumer, consumerChainMap)
		err = Handle(ctx)
		assert.NoError(t, err)

		handler.ChainMap = make(map[string]*handler.Chain)
	})
}

func Test_handleDubboRequest(t *testing.T) {
	req := dubbo.NewDubboRequest()
	req.SetAttachment(dubbo.PathKey, "hello")
	ctx := &dubbo.InvokeContext{req, &dubbo.DubboRsp{}, nil, "", "127.0.0.1:9090"}
	ctx.Rsp.Init()

	inv := &invocation.Invocation{Protocol: "rest"}
	ir := &invocation.Response{}
	inv.Endpoint = "127.0.0.1:8080"

	// case responese ir.Result = nil
	handleDubboRequest(inv, ctx, ir)

	// case responese ir.Result != nil
	ir.Result = &dubboclient.WrapResponse{Resp: &dubbo.DubboRsp{}}
	handleDubboRequest(inv, ctx, ir)

	// Case ir.Err == hystrix.FallbackNullError
	ir.Err = hystrix.FallbackNullError{"Error."}
	handleDubboRequest(inv, ctx, ir)
	// Case ir.Err == hystrix.CircuitError:
	ir.Err = hystrix.CircuitError{"Error."}
	handleDubboRequest(inv, ctx, ir)
	// Case ir.Err == loadbalancer.LBError
	ir.Err = loadbalancer.LBError{"Error."}
	handleDubboRequest(inv, ctx, ir)
	// Case ir.Err == other
	ir.Err = fmt.Errorf("Other error.")
	handleDubboRequest(inv, ctx, ir)

	// case ir == nil
	handleDubboRequest(inv, ctx, nil)

}

// ContractDiscoveryService struct for disco mock
type MockContractDiscoveryService struct {
	mock.Mock
}

func (m *MockContractDiscoveryService) GetMicroServicesByInterface(interfaceName string) (microservices []*registry.MicroService) {
	microservices = append(microservices, &registry.MicroService{})
	return
}

func (m *MockContractDiscoveryService) GetSchemaContentByInterface(interfaceName string) registry.SchemaContent {
	return registry.SchemaContent{}
}

func (m *MockContractDiscoveryService) GetSchemaContentByServiceName(svcName, version, appID, env string) []*registry.SchemaContent {
	var sc []*registry.SchemaContent
	sc = append(sc, &registry.SchemaContent{
		Paths: map[string]map[string]registry.MethodInfo{
			"hello": {},
		},
	})
	return nil
}

func (m *MockContractDiscoveryService) Close() error {
	return nil
}
