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

import (
	"context"
	"testing"
)

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

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

var url1 = func() *common.URL {
	i, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" +
		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" +
		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" +
		"side=provider&timeout=3000&timestamp=1556509797245")
	return i
}
var url2 = func() *common.URL {
	u, _ := common.NewURL("dubbo://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&" +
		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" +
		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" +
		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1448&revision=0.0.1&" +
		"side=provider&timeout=3000&timestamp=1556509797246")
	return u
}
var url3 = func() *common.URL {
	i, _ := common.NewURL("dubbo://127.0.0.1:20002/com.ikurento.user.UserProvider?anyhost=true&" +
		"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" +
		"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" +
		"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1449&revision=0.0.1&" +
		"side=provider&timeout=3000&timestamp=1556509797247")
	return i
}

func getRouteCheckArgs() ([]base.Invoker, base.Invocation, context.Context) {
	return []base.Invoker{
			base.NewBaseInvoker(url1()), base.NewBaseInvoker(url2()), base.NewBaseInvoker(url3()),
		}, invocation.NewRPCInvocation("GetUser", nil, map[string]any{
			"attachmentKey": []string{"attachmentValue"},
		}),
		context.TODO()
}

func TestScriptRouter_Route(t *testing.T) {
	type fields struct {
		cfgContent string
	}
	type args struct {
		invokers   []base.Invoker
		invocation base.Invocation
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   func([]base.Invoker) bool
	}{
		{
			name: "base test",
			fields: fields{cfgContent: `configVersion: v3.0
key: dubbo.io
type: javascript
enabled: true
script: |
  (function route(invokers,invocation,context) {
  	var result = [];
  	for (var i = 0; i < invokers.length; i++) {
      invokers[i].GetURL().Port = "20001" 
      result.push(invokers[i]);
  	}
  	return result;
  }(invokers,invocation,context));
`},
			args: func() args {
				res := args{}
				res.invokers, res.invocation, _ = getRouteCheckArgs()
				return res
			}(),
			want: func(invokers []base.Invoker) bool {
				for _, invoker := range invokers {
					if invoker.GetURL().Port != "20001" {
						return false
					}
				}
				return true
			},
		}, {
			name: "disable test",
			fields: fields{cfgContent: `configVersion: v3.0
key: dubbo.io
type: javascript
enabled: false
script: |
  (function route(invokers,invocation,context) {
  	var result = [];
  	for (var i = 0; i < invokers.length; i++) {
      invokers[i].GetURL().Port = "20001" 
      result.push(invokers[i]);
  	}
  	return result;
  }(invokers,invocation,context));
`},
			args: func() args {
				res := args{}
				res.invokers, res.invocation, _ = getRouteCheckArgs()
				return res
			}(),
			want: func(invokers []base.Invoker) bool {
				expect_invokers, _, _ := getRouteCheckArgs()
				return checkInvokersSame(invokers, expect_invokers)
			},
		}, {
			name: "bad input",
			fields: fields{cfgContent: `configVersion: v3.0
key: dubbo.io
type: javascript
enabled: true
script: |
	badInPut  
	(().Port = "20001" 
      result.push(invokers[i]);
  	}
  	}
  	}
  	return result;
  }(invokers,invocation,context));
`},
			args: func() args {
				res := args{}
				res.invokers, res.invocation, _ = getRouteCheckArgs()
				return res
			}(),
			want: func(invokers []base.Invoker) bool {
				expect_invokers, _, _ := getRouteCheckArgs()
				return checkInvokersSame(invokers, expect_invokers)
			},
		}, {
			name: "bad call and recover",
			fields: fields{cfgContent: `configVersion: v3.0
key: dubbo.io
type: javascript
enabled: true
script: |
  (function route(invokers, invocation, context) {
    var result = [];
    for (var i = 0; i < invokers.length; i++) {
      if ("127.0.0.1" === invokers[i].GetURL().Ip) {
        if (invokers[i].GetURLS(" Err Here ").Port !== "20000") {
          invokers[i].GetURLS().Ip = "anotherIP"
          result.push(invokers[i]);
        }
      }
    }
    return result;
  }(invokers, invocation, context));
`},
			args: func() args {
				res := args{}
				res.invokers, res.invocation, _ = getRouteCheckArgs()
				return res
			}(),
			want: func(invokers []base.Invoker) bool {
				expect_invokers, _, _ := getRouteCheckArgs()
				return checkInvokersSame(invokers, expect_invokers)
			},
		}, {
			name: "bad type",
			fields: fields{cfgContent: `configVersion: v3.0
key: dubbo.io
type: errorType     # <---
enabled: true
script: |
  (function route(invokers,invocation,context) {
  	var result = [];
  	for (var i = 0; i < invokers.length; i++) {
      invokers[i].GetURL().Port = "20001" 
      result.push(invokers[i]);
  	}
  	return result;
  }(invokers,invocation,context));
`},
			args: func() args {
				res := args{}
				res.invokers, res.invocation, _ = getRouteCheckArgs()
				return res
			}(),
			want: func(invokers []base.Invoker) bool {
				expect_invokers, _, _ := getRouteCheckArgs()
				return checkInvokersSame(invokers, expect_invokers)
			},
		},
	}
	for _, tt := range tests {
		s := &ScriptRouter{}
		t.Run(tt.name, func(t *testing.T) {
			s.Process(&config_center.ConfigChangeEvent{Key: "", Value: tt.fields.cfgContent, ConfigType: remoting.EventTypeUpdate})
			got := s.Route(tt.args.invokers, nil, tt.args.invocation)
			assert.True(t, tt.want(got))
		})
	}
}

func checkInvokersSame(invokers []base.Invoker, otherInvokers []base.Invoker) bool {
	k := map[string]struct{}{}
	for _, invoker := range otherInvokers {
		k[invoker.GetURL().String()] = struct{}{}
	}
	for _, invoker := range invokers {
		_, ok := k[invoker.GetURL().String()]
		if !ok {
			return false
		}
		delete(k, invoker.GetURL().String())
	}
	return len(k) == 0
}
