// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// 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 config

import (
	"testing"

	httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
	"github.com/stretchr/testify/assert"
	"google.golang.org/protobuf/proto"
	networking "istio.io/api/networking/v1alpha3"
	"istio.io/istio/pkg/config"
	"istio.io/istio/pkg/config/schema/gvk"
	"istio.io/istio/pkg/config/xds"
	"istio.io/istio/pkg/kube"
	ingress "k8s.io/api/networking/v1"
	ingressv1beta1 "k8s.io/api/networking/v1beta1"

	"github.com/alibaba/higress/pkg/ingress/kube/annotations"
	"github.com/alibaba/higress/pkg/ingress/kube/common"
	controllerv1beta1 "github.com/alibaba/higress/pkg/ingress/kube/ingress"
	controllerv1 "github.com/alibaba/higress/pkg/ingress/kube/ingressv1"
)

func TestNormalizeWeightedCluster(t *testing.T) {
	validate := func(route *common.WrapperHTTPRoute) int32 {
		var total int32
		for _, routeDestination := range route.HTTPRoute.Route {
			total += routeDestination.Weight
		}

		return total
	}

	var testCases []*common.WrapperHTTPRoute
	testCases = append(testCases, &common.WrapperHTTPRoute{
		HTTPRoute: &networking.HTTPRoute{
			Route: []*networking.HTTPRouteDestination{
				{
					Weight: 100,
				},
			},
		},
	})
	testCases = append(testCases, &common.WrapperHTTPRoute{
		HTTPRoute: &networking.HTTPRoute{
			Route: []*networking.HTTPRouteDestination{
				{
					Weight: 98,
				},
			},
		},
	})

	testCases = append(testCases, &common.WrapperHTTPRoute{
		HTTPRoute: &networking.HTTPRoute{
			Route: []*networking.HTTPRouteDestination{
				{
					Weight: 0,
				},
				{
					Weight: 48,
				},
				{
					Weight: 48,
				},
			},
		},
		WeightTotal: 100,
	})

	testCases = append(testCases, &common.WrapperHTTPRoute{
		HTTPRoute: &networking.HTTPRoute{
			Route: []*networking.HTTPRouteDestination{
				{
					Weight: 0,
				},
				{
					Weight: 48,
				},
				{
					Weight: 48,
				},
			},
		},
		WeightTotal: 80,
	})

	for _, route := range testCases {
		t.Run("", func(t *testing.T) {
			normalizeWeightedCluster(nil, route)
			if validate(route) != 100 {
				t.Fatalf("Weight sum should be 100, but actual is %d", validate(route))
			}
		})
	}
}

func TestConvertGatewaysForIngress(t *testing.T) {
	fake := kube.NewFakeClient()
	v1Beta1Options := common.Options{
		Enable:       true,
		ClusterId:    "ingress-v1beta1",
		RawClusterId: "ingress-v1beta1__",
	}
	v1Options := common.Options{
		Enable:       true,
		ClusterId:    "ingress-v1",
		RawClusterId: "ingress-v1__",
	}
	ingressV1Beta1Controller := controllerv1beta1.NewController(fake, fake, v1Beta1Options, nil)
	ingressV1Controller := controllerv1.NewController(fake, fake, v1Options, nil)
	m := NewIngressConfig(fake, nil, "wakanda", "gw-123-istio")
	m.remoteIngressControllers = map[string]common.IngressController{
		"ingress-v1beta1": ingressV1Beta1Controller,
		"ingress-v1":      ingressV1Controller,
	}

	testCases := []struct {
		name        string
		inputConfig []common.WrapperConfig
		expect      map[string]config.Config
	}{
		{
			name: "ingress v1beta1",
			inputConfig: []common.WrapperConfig{
				{
					Config: &config.Config{
						Meta: config.Meta{
							Name:      "test-1",
							Namespace: "wakanda",
							Annotations: map[string]string{
								common.ClusterIdAnnotation: "ingress-v1beta1",
							},
						},
						Spec: ingressv1beta1.IngressSpec{
							TLS: []ingressv1beta1.IngressTLS{
								{
									Hosts:      []string{"test.com"},
									SecretName: "test-com",
								},
							},
							Rules: []ingressv1beta1.IngressRule{
								{
									Host: "foo.com",
									IngressRuleValue: ingressv1beta1.IngressRuleValue{
										HTTP: &ingressv1beta1.HTTPIngressRuleValue{
											Paths: []ingressv1beta1.HTTPIngressPath{
												{
													Path: "/test",
												},
											},
										},
									},
								},
								{
									Host: "test.com",
									IngressRuleValue: ingressv1beta1.IngressRuleValue{
										HTTP: &ingressv1beta1.HTTPIngressRuleValue{
											Paths: []ingressv1beta1.HTTPIngressPath{
												{
													Path: "/test",
												},
											},
										},
									},
								},
							},
						},
					},
					AnnotationsConfig: &annotations.Ingress{
						DownstreamTLS: &annotations.DownstreamTLSConfig{
							CipherSuites: []string{"ECDHE-RSA-AES128-GCM-SHA256", "AES256-SHA"},
						},
					},
				},
				{
					Config: &config.Config{
						Meta: config.Meta{
							Name:      "test-2",
							Namespace: "wakanda",
							Annotations: map[string]string{
								common.ClusterIdAnnotation: "ingress-v1beta1",
							},
						},
						Spec: ingressv1beta1.IngressSpec{
							TLS: []ingressv1beta1.IngressTLS{
								{
									Hosts:      []string{"foo.com"},
									SecretName: "foo-com",
								},
								{
									Hosts:      []string{"test.com"},
									SecretName: "test-com-2",
								},
							},
							Rules: []ingressv1beta1.IngressRule{
								{
									Host: "foo.com",
									IngressRuleValue: ingressv1beta1.IngressRuleValue{
										HTTP: &ingressv1beta1.HTTPIngressRuleValue{
											Paths: []ingressv1beta1.HTTPIngressPath{
												{
													Path: "/test",
												},
											},
										},
									},
								},
								{
									Host: "bar.com",
									IngressRuleValue: ingressv1beta1.IngressRuleValue{
										HTTP: &ingressv1beta1.HTTPIngressRuleValue{
											Paths: []ingressv1beta1.HTTPIngressPath{
												{
													Path: "/test",
												},
											},
										},
									},
								},
								{
									Host: "test.com",
									IngressRuleValue: ingressv1beta1.IngressRuleValue{
										HTTP: &ingressv1beta1.HTTPIngressRuleValue{
											Paths: []ingressv1beta1.HTTPIngressPath{
												{
													Path: "/test",
												},
											},
										},
									},
								},
							},
						},
					},
					AnnotationsConfig: &annotations.Ingress{
						DownstreamTLS: &annotations.DownstreamTLSConfig{
							CipherSuites: []string{"ECDHE-RSA-AES128-GCM-SHA256"},
						},
					},
				},
			},
			expect: map[string]config.Config{
				"foo.com": {
					Meta: config.Meta{
						GroupVersionKind: gvk.Gateway,
						Name:             "istio-autogenerated-k8s-ingress-foo-com",
						Namespace:        "wakanda",
						Annotations: map[string]string{
							common.ClusterIdAnnotation: "ingress-v1beta1",
							common.HostAnnotation:      "foo.com",
						},
					},
					Spec: &networking.Gateway{
						Servers: []*networking.Server{
							{
								Port: &networking.Port{
									Number:   80,
									Protocol: "HTTP",
									Name:     "http-80-ingress-ingress-v1beta1-wakanda-test-1-foo-com",
								},
								Hosts: []string{"foo.com"},
							},
							{
								Port: &networking.Port{
									Number:   443,
									Protocol: "HTTPS",
									Name:     "https-443-ingress-ingress-v1beta1-wakanda-test-2-foo-com",
								},
								Hosts: []string{"foo.com"},
								Tls: &networking.ServerTLSSettings{
									Mode:           networking.ServerTLSSettings_SIMPLE,
									CredentialName: "kubernetes-ingress://ingress-v1beta1__/wakanda/foo-com",
									CipherSuites:   []string{"ECDHE-RSA-AES128-GCM-SHA256", "AES256-SHA"},
								},
							},
						},
					},
				},
				"test.com": {
					Meta: config.Meta{
						GroupVersionKind: gvk.Gateway,
						Name:             "istio-autogenerated-k8s-ingress-test-com",
						Namespace:        "wakanda",
						Annotations: map[string]string{
							common.ClusterIdAnnotation: "ingress-v1beta1",
							common.HostAnnotation:      "test.com",
						},
					},
					Spec: &networking.Gateway{
						Servers: []*networking.Server{
							{
								Port: &networking.Port{
									Number:   80,
									Protocol: "HTTP",
									Name:     "http-80-ingress-ingress-v1beta1-wakanda-test-1-test-com",
								},
								Hosts: []string{"test.com"},
							},
							{
								Port: &networking.Port{
									Number:   443,
									Protocol: "HTTPS",
									Name:     "https-443-ingress-ingress-v1beta1-wakanda-test-1-test-com",
								},
								Hosts: []string{"test.com"},
								Tls: &networking.ServerTLSSettings{
									Mode:           networking.ServerTLSSettings_SIMPLE,
									CredentialName: "kubernetes-ingress://ingress-v1beta1__/wakanda/test-com",
									CipherSuites:   []string{"ECDHE-RSA-AES128-GCM-SHA256", "AES256-SHA"},
								},
							},
						},
					},
				},
				"bar.com": {
					Meta: config.Meta{
						GroupVersionKind: gvk.Gateway,
						Name:             "istio-autogenerated-k8s-ingress-bar-com",
						Namespace:        "wakanda",
						Annotations: map[string]string{
							common.ClusterIdAnnotation: "ingress-v1beta1",
							common.HostAnnotation:      "bar.com",
						},
					},
					Spec: &networking.Gateway{
						Servers: []*networking.Server{
							{
								Port: &networking.Port{
									Number:   80,
									Protocol: "HTTP",
									Name:     "http-80-ingress-ingress-v1beta1-wakanda-test-2-bar-com",
								},
								Hosts: []string{"bar.com"},
							},
						},
					},
				},
			},
		},
		{
			name: "ingress v1",
			inputConfig: []common.WrapperConfig{
				{
					Config: &config.Config{
						Meta: config.Meta{
							Name:      "test-1",
							Namespace: "wakanda",
							Annotations: map[string]string{
								common.ClusterIdAnnotation: "ingress-v1",
							},
						},
						Spec: ingress.IngressSpec{
							TLS: []ingress.IngressTLS{
								{
									Hosts:      []string{"test.com"},
									SecretName: "test-com",
								},
							},
							Rules: []ingress.IngressRule{
								{
									Host: "foo.com",
									IngressRuleValue: ingress.IngressRuleValue{
										HTTP: &ingress.HTTPIngressRuleValue{
											Paths: []ingress.HTTPIngressPath{
												{
													Path: "/test",
												},
											},
										},
									},
								},
								{
									Host: "test.com",
									IngressRuleValue: ingress.IngressRuleValue{
										HTTP: &ingress.HTTPIngressRuleValue{
											Paths: []ingress.HTTPIngressPath{
												{
													Path: "/test",
												},
											},
										},
									},
								},
							},
						},
					},
					AnnotationsConfig: &annotations.Ingress{},
				},
				{
					Config: &config.Config{
						Meta: config.Meta{
							Name:      "test-2",
							Namespace: "wakanda",
							Annotations: map[string]string{
								common.ClusterIdAnnotation: "ingress-v1",
							},
						},
						Spec: ingress.IngressSpec{
							TLS: []ingress.IngressTLS{
								{
									Hosts:      []string{"foo.com"},
									SecretName: "foo-com",
								},
								{
									Hosts:      []string{"test.com"},
									SecretName: "test-com-2",
								},
							},
							Rules: []ingress.IngressRule{
								{
									Host: "foo.com",
									IngressRuleValue: ingress.IngressRuleValue{
										HTTP: &ingress.HTTPIngressRuleValue{
											Paths: []ingress.HTTPIngressPath{
												{
													Path: "/test",
												},
											},
										},
									},
								},
								{
									Host: "bar.com",
									IngressRuleValue: ingress.IngressRuleValue{
										HTTP: &ingress.HTTPIngressRuleValue{
											Paths: []ingress.HTTPIngressPath{
												{
													Path: "/test",
												},
											},
										},
									},
								},
								{
									Host: "test.com",
									IngressRuleValue: ingress.IngressRuleValue{
										HTTP: &ingress.HTTPIngressRuleValue{
											Paths: []ingress.HTTPIngressPath{
												{
													Path: "/test",
												},
											},
										},
									},
								},
							},
						},
					},
					AnnotationsConfig: &annotations.Ingress{
						DownstreamTLS: &annotations.DownstreamTLSConfig{
							CipherSuites: []string{"ECDHE-RSA-AES128-GCM-SHA256"},
						},
					},
				},
			},
			expect: map[string]config.Config{
				"foo.com": {
					Meta: config.Meta{
						GroupVersionKind: gvk.Gateway,
						Name:             "istio-autogenerated-k8s-ingress-foo-com",
						Namespace:        "wakanda",
						Annotations: map[string]string{
							common.ClusterIdAnnotation: "ingress-v1",
							common.HostAnnotation:      "foo.com",
						},
					},
					Spec: &networking.Gateway{
						Servers: []*networking.Server{
							{
								Port: &networking.Port{
									Number:   80,
									Protocol: "HTTP",
									Name:     "http-80-ingress-ingress-v1-wakanda-test-1-foo-com",
								},
								Hosts: []string{"foo.com"},
							},
							{
								Port: &networking.Port{
									Number:   443,
									Protocol: "HTTPS",
									Name:     "https-443-ingress-ingress-v1-wakanda-test-2-foo-com",
								},
								Hosts: []string{"foo.com"},
								Tls: &networking.ServerTLSSettings{
									Mode:           networking.ServerTLSSettings_SIMPLE,
									CredentialName: "kubernetes-ingress://ingress-v1__/wakanda/foo-com",
									CipherSuites:   []string{"ECDHE-RSA-AES128-GCM-SHA256"},
								},
							},
						},
					},
				},
				"test.com": {
					Meta: config.Meta{
						GroupVersionKind: gvk.Gateway,
						Name:             "istio-autogenerated-k8s-ingress-test-com",
						Namespace:        "wakanda",
						Annotations: map[string]string{
							common.ClusterIdAnnotation: "ingress-v1",
							common.HostAnnotation:      "test.com",
						},
					},
					Spec: &networking.Gateway{
						Servers: []*networking.Server{
							{
								Port: &networking.Port{
									Number:   80,
									Protocol: "HTTP",
									Name:     "http-80-ingress-ingress-v1-wakanda-test-1-test-com",
								},
								Hosts: []string{"test.com"},
							},
							{
								Port: &networking.Port{
									Number:   443,
									Protocol: "HTTPS",
									Name:     "https-443-ingress-ingress-v1-wakanda-test-1-test-com",
								},
								Hosts: []string{"test.com"},
								Tls: &networking.ServerTLSSettings{
									Mode:           networking.ServerTLSSettings_SIMPLE,
									CredentialName: "kubernetes-ingress://ingress-v1__/wakanda/test-com",
									CipherSuites:   []string{"ECDHE-RSA-AES128-GCM-SHA256"},
								},
							},
						},
					},
				},
				"bar.com": {
					Meta: config.Meta{
						GroupVersionKind: gvk.Gateway,
						Name:             "istio-autogenerated-k8s-ingress-bar-com",
						Namespace:        "wakanda",
						Annotations: map[string]string{
							common.ClusterIdAnnotation: "ingress-v1",
							common.HostAnnotation:      "bar.com",
						},
					},
					Spec: &networking.Gateway{
						Servers: []*networking.Server{
							{
								Port: &networking.Port{
									Number:   80,
									Protocol: "HTTP",
									Name:     "http-80-ingress-ingress-v1-wakanda-test-2-bar-com",
								},
								Hosts: []string{"bar.com"},
							},
						},
					},
				},
			},
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			result := m.convertGateways(testCase.inputConfig)
			target := map[string]config.Config{}
			for _, item := range result {
				host := common.GetHost(item.Annotations)
				target[host] = item
			}
			assert.Equal(t, testCase.expect, target)
		})
	}
}

func TestConstructBasicAuthEnvoyFilter(t *testing.T) {
	rules := &common.BasicAuthRules{
		Rules: []*common.Rule{
			{
				Realm:       "test",
				MatchRoute:  []string{"route"},
				Credentials: []string{"user:password"},
				Encrypted:   true,
			},
		},
	}

	config, err := constructBasicAuthEnvoyFilter(rules, "")
	if err != nil {
		t.Fatalf("construct error %v", err)
	}
	envoyFilter := config.Spec.(*networking.EnvoyFilter)
	pb, err := xds.BuildXDSObjectFromStruct(networking.EnvoyFilter_HTTP_FILTER, envoyFilter.ConfigPatches[0].Patch.Value, false)
	if err != nil {
		t.Fatalf("build object error %v", err)
	}
	target := proto.Clone(pb).(*httppb.HttpFilter)
	t.Log(target)
}
