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

import (
	stderr "errors"
	"testing"
)

import (
	"github.com/cch123/supermonkey"

	"github.com/dubbo-go-pixiu/pixiu-api/pkg/xds"
	pixiupb "github.com/dubbo-go-pixiu/pixiu-api/pkg/xds/model"

	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"

	"github.com/golang/mock/gomock"

	"github.com/stretchr/testify/require"

	"google.golang.org/protobuf/types/known/anypb"
)

import (
	"github.com/apache/dubbo-go-pixiu/pkg/config/xds/apiclient"
	"github.com/apache/dubbo-go-pixiu/pkg/model"
	"github.com/apache/dubbo-go-pixiu/pkg/server/controls"
	"github.com/apache/dubbo-go-pixiu/pkg/server/controls/mocks"
)

func makeClusters() *pixiupb.PixiuExtensionClusters {
	return &pixiupb.PixiuExtensionClusters{
		Clusters: []*pixiupb.Cluster{
			{
				Name:    "http-baidu",
				TypeStr: "http",
				Endpoints: []*pixiupb.Endpoint{{
					Id: "backend",
					Address: &pixiupb.SocketAddress{
						Address: "httpbin.org",
						Port:    80,
					},
				}},
			},
		},
	}
}

func getCdsConfig() *core.TypedExtensionConfig {
	makeClusters := func() *pixiupb.PixiuExtensionClusters {
		return &pixiupb.PixiuExtensionClusters{
			Clusters: []*pixiupb.Cluster{
				{
					Name:    "http-baidu",
					TypeStr: "http",
					Endpoints: []*pixiupb.Endpoint{{
						Id: "backend",
						Address: &pixiupb.SocketAddress{
							Address: "httpbin.org",
							Port:    80,
						},
					}},
				},
			},
		}
	}
	cdsResource, _ := anypb.New(makeClusters())
	return &core.TypedExtensionConfig{
		Name:        xds.ClusterType,
		TypedConfig: cdsResource,
	}
}

func TestCdsManager_Fetch(t *testing.T) {
	var fetchResult []*apiclient.ProtoAny
	var fetchError error
	var cluster = map[string]struct{}{}
	var updateCluster *model.ClusterConfig
	var addCluster *model.ClusterConfig
	xdsConfig := getCdsConfig()

	ctrl := gomock.NewController(t)
	clusterMg := mocks.NewMockClusterManager(ctrl)
	//var deltaResult chan *apiclient.DeltaResources
	//var deltaErr error
	supermonkey.Patch((*apiclient.GrpcExtensionApiClient).Fetch, func(_ *apiclient.GrpcExtensionApiClient, localVersion string) ([]*apiclient.ProtoAny, error) {
		return fetchResult, fetchError
	})
	//supermonkey.Patch(server.GetClusterManager, func() *server.ClusterManager {
	//	return nil
	//})
	clusterMg.EXPECT().HasCluster(gomock.Any()).DoAndReturn(func(clusterName string) bool {
		_, ok := cluster[clusterName]
		return ok
	})

	clusterMg.EXPECT().UpdateCluster(gomock.Any()).AnyTimes().Do(func(new *model.ClusterConfig) {
		updateCluster = new
	})
	clusterMg.EXPECT().AddCluster(gomock.Any()).AnyTimes().Do(func(c *model.ClusterConfig) {
		addCluster = c
	})
	clusterMg.EXPECT().RemoveCluster(gomock.Any()).AnyTimes()
	clusterMg.EXPECT().CloneXdsControlStore().AnyTimes().DoAndReturn(func() (controls.ClusterStore, error) {
		store := mocks.NewMockClusterStore(ctrl)
		store.EXPECT().Config().AnyTimes()
		return store, nil
	})
	//supermonkey.Patch((*server.ClusterManager).HasCluster, func(_ *server.ClusterManager, clusterName string) bool {
	//	_, ok := cluster[clusterName]
	//	return ok
	//})
	//supermonkey.Patch((*server.ClusterManager).UpdateCluster, func(_ *server.ClusterManager, new *model.Cluster) {
	//	updateCluster = new
	//})
	//supermonkey.Patch((*server.ClusterManager).AddCluster, func(_ *server.ClusterManager, c *model.Cluster) {
	//	addCluster = c
	//})
	//supermonkey.Patch((*server.ClusterManager).RemoveCluster, func(_ *server.ClusterManager, names []string) {
	//	//do nothing.
	//})
	//supermonkey.Patch((*server.ClusterManager).CloneStore, func(_ *server.ClusterManager) (*server.ClusterStore, error) {
	//	return &server.ClusterStore{}, nil
	//})
	//supermonkey.Patch((*apiclient.GrpcExtensionApiClient).Delta, func(_ *apiclient.GrpcExtensionApiClient) (chan *apiclient.DeltaResources, error) {
	//	return deltaResult, deltaErr
	//})
	defer supermonkey.UnpatchAll()

	tests := []struct {
		name              string
		mockResult        []*apiclient.ProtoAny
		mockError         error
		wantErr           bool
		wantNewCluster    bool
		wantUpdateCluster bool
	}{
		{"error", nil, stderr.New("error test"), true, false, false},
		{"simple", nil, nil, false, false, false},
		{"withValue", func() []*apiclient.ProtoAny {
			return []*apiclient.ProtoAny{
				apiclient.NewProtoAny(xdsConfig),
			}
		}(), nil, false, true, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := &CdsManager{
				DiscoverApi: &apiclient.GrpcExtensionApiClient{},
				clusterMg:   clusterMg,
			}
			//reset context value.
			fetchError = tt.mockError
			fetchResult = tt.mockResult
			updateCluster = nil
			addCluster = nil

			err := c.Fetch()
			assert := require.New(t)
			if tt.wantErr {
				assert.Error(err)
				return
			}
			assert.NoError(err)
			if tt.wantUpdateCluster {
				assert.NotNil(updateCluster)
			} else {
				assert.Nil(updateCluster)
			}
			if tt.wantNewCluster {
				assert.NotNil(addCluster)
			} else {
				assert.Nil(addCluster)
			}
		})
	}
}

func TestCdsManager_makeCluster(t *testing.T) {
	c := &CdsManager{}
	cluster := makeClusters().Clusters[0]
	modelCluster := c.makeCluster(cluster)
	assert := require.New(t)
	assert.Equal(cluster.Name, modelCluster.Name)
	assert.Equal(cluster.TypeStr, modelCluster.TypeStr)
	assert.Equal(cluster.Endpoints[0].Name, modelCluster.Endpoints[0].Name)
	assert.Equal(cluster.Endpoints[0].Address.Address, modelCluster.Endpoints[0].Address.Address)
	assert.Equal(cluster.Endpoints[0].Address.Port, int64(modelCluster.Endpoints[0].Address.Port))
}
