/*
Copyright 2022 The Kruise Authors.
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 gateway

import (
	"reflect"
	"testing"

	rolloutsv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
	"github.com/openkruise/rollouts/pkg/util"
	utilpointer "k8s.io/utils/pointer"
	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
)

var (
	kindSvc = gatewayv1alpha2.Kind("Service")
	portNum = gatewayv1alpha2.PortNumber(8080)

	routeDemo = gatewayv1alpha2.HTTPRoute{
		Spec: gatewayv1alpha2.HTTPRouteSpec{
			Rules: []gatewayv1alpha2.HTTPRouteRule{
				{
					Matches: []gatewayv1alpha2.HTTPRouteMatch{
						{
							Path: &gatewayv1alpha2.HTTPPathMatch{
								Value: utilpointer.String("/web"),
							},
						},
					},
					BackendRefs: []gatewayv1alpha2.HTTPBackendRef{
						{
							BackendRef: gatewayv1alpha2.BackendRef{
								BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
									Kind: &kindSvc,
									Name: "web-svc",
									Port: &portNum,
								},
							},
						},
					},
				},
				{
					Matches: []gatewayv1alpha2.HTTPRouteMatch{
						{
							Path: &gatewayv1alpha2.HTTPPathMatch{
								Value: utilpointer.String("/store"),
							},
							Headers: []gatewayv1alpha2.HTTPHeaderMatch{
								{
									Name:  "version",
									Value: "v2",
								},
							},
						},
						{
							Path: &gatewayv1alpha2.HTTPPathMatch{
								Value: utilpointer.String("/v2/store"),
							},
						},
					},
					BackendRefs: []gatewayv1alpha2.HTTPBackendRef{
						{
							BackendRef: gatewayv1alpha2.BackendRef{
								BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
									Kind: &kindSvc,
									Name: "store-svc",
									Port: &portNum,
								},
							},
						},
					},
				},
				{
					Matches: []gatewayv1alpha2.HTTPRouteMatch{
						{
							Path: &gatewayv1alpha2.HTTPPathMatch{
								Value: utilpointer.String("/list"),
							},
						},
					},
					BackendRefs: []gatewayv1alpha2.HTTPBackendRef{
						{
							BackendRef: gatewayv1alpha2.BackendRef{
								BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
									Kind: &kindSvc,
									Name: "list-svc",
									Port: &portNum,
								},
							},
						},
					},
				},
				{
					Matches: []gatewayv1alpha2.HTTPRouteMatch{
						{
							Path: &gatewayv1alpha2.HTTPPathMatch{
								Value: utilpointer.String("/storage"),
							},
						},
					},
					BackendRefs: []gatewayv1alpha2.HTTPBackendRef{
						{
							BackendRef: gatewayv1alpha2.BackendRef{
								BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
									Kind: &kindSvc,
									Name: "store-svc",
									Port: &portNum,
								},
							},
						},
					},
				},
			},
		},
	}
)

func TestBuildDesiredHTTPRoute(t *testing.T) {
	cases := []struct {
		name          string
		getRouteRules func() []gatewayv1alpha2.HTTPRouteRule
		getRoutes     func() (*int32, []rolloutsv1alpha1.HttpRouteMatch)
		desiredRules  func() []gatewayv1alpha2.HTTPRouteRule
	}{
		{
			name: "test1 headers",
			getRouteRules: func() []gatewayv1alpha2.HTTPRouteRule {
				rules := routeDemo.DeepCopy().Spec.Rules
				return rules
			},
			getRoutes: func() (*int32, []rolloutsv1alpha1.HttpRouteMatch) {
				iType := gatewayv1alpha2.HeaderMatchRegularExpression
				return nil, []rolloutsv1alpha1.HttpRouteMatch{
					// header
					{
						Headers: []gatewayv1alpha2.HTTPHeaderMatch{
							{
								Name:  "user_id",
								Value: "123*",
								Type:  &iType,
							},
							{
								Name:  "canary",
								Value: "true",
							},
						},
					},
				}
			},
			desiredRules: func() []gatewayv1alpha2.HTTPRouteRule {
				rules := routeDemo.DeepCopy().Spec.Rules
				iType := gatewayv1alpha2.HeaderMatchRegularExpression
				rules = append(rules, gatewayv1alpha2.HTTPRouteRule{
					Matches: []gatewayv1alpha2.HTTPRouteMatch{
						{
							Path: &gatewayv1alpha2.HTTPPathMatch{
								Value: utilpointer.String("/store"),
							},
							Headers: []gatewayv1alpha2.HTTPHeaderMatch{
								{
									Name:  "version",
									Value: "v2",
								},
								{
									Name:  "user_id",
									Value: "123*",
									Type:  &iType,
								},
								{
									Name:  "canary",
									Value: "true",
								},
							},
						},
						{
							Path: &gatewayv1alpha2.HTTPPathMatch{
								Value: utilpointer.String("/v2/store"),
							},
							Headers: []gatewayv1alpha2.HTTPHeaderMatch{
								{
									Name:  "user_id",
									Value: "123*",
									Type:  &iType,
								},
								{
									Name:  "canary",
									Value: "true",
								},
							},
						},
					},
					BackendRefs: []gatewayv1alpha2.HTTPBackendRef{
						{
							BackendRef: gatewayv1alpha2.BackendRef{
								BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
									Kind: &kindSvc,
									Name: "store-svc-canary",
									Port: &portNum,
								},
							},
						},
					},
				})
				rules = append(rules, gatewayv1alpha2.HTTPRouteRule{
					Matches: []gatewayv1alpha2.HTTPRouteMatch{
						{
							Path: &gatewayv1alpha2.HTTPPathMatch{
								Value: utilpointer.String("/storage"),
							},
							Headers: []gatewayv1alpha2.HTTPHeaderMatch{
								{
									Name:  "user_id",
									Value: "123*",
									Type:  &iType,
								},
								{
									Name:  "canary",
									Value: "true",
								},
							},
						},
					},
					BackendRefs: []gatewayv1alpha2.HTTPBackendRef{
						{
							BackendRef: gatewayv1alpha2.BackendRef{
								BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
									Kind: &kindSvc,
									Name: "store-svc-canary",
									Port: &portNum,
								},
							},
						},
					},
				})
				return rules
			},
		},
		{
			name: "canary weight: 20",
			getRouteRules: func() []gatewayv1alpha2.HTTPRouteRule {
				rules := routeDemo.DeepCopy().Spec.Rules
				rules[1].BackendRefs = []gatewayv1alpha2.HTTPBackendRef{
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(80),
						},
					},
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc-canary",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(20),
						},
					},
				}
				rules[3].BackendRefs = []gatewayv1alpha2.HTTPBackendRef{
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(80),
						},
					},
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc-canary",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(20),
						},
					},
				}
				return rules
			},
			getRoutes: func() (*int32, []rolloutsv1alpha1.HttpRouteMatch) {
				return utilpointer.Int32(20), nil
			},
			desiredRules: func() []gatewayv1alpha2.HTTPRouteRule {
				rules := routeDemo.DeepCopy().Spec.Rules
				rules[1].BackendRefs = []gatewayv1alpha2.HTTPBackendRef{
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(80),
						},
					},
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc-canary",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(20),
						},
					},
				}
				rules[3].BackendRefs = []gatewayv1alpha2.HTTPBackendRef{
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(80),
						},
					},
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc-canary",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(20),
						},
					},
				}
				return rules
			},
		},
		{
			name: "canary weight: -1",
			getRouteRules: func() []gatewayv1alpha2.HTTPRouteRule {
				rules := routeDemo.DeepCopy().Spec.Rules
				rules[1].BackendRefs = []gatewayv1alpha2.HTTPBackendRef{
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(100),
						},
					},
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc-canary",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(0),
						},
					},
				}
				rules[3].BackendRefs = []gatewayv1alpha2.HTTPBackendRef{
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(100),
						},
					},
					{
						BackendRef: gatewayv1alpha2.BackendRef{
							BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
								Kind: &kindSvc,
								Name: "store-svc-canary",
								Port: &portNum,
							},
							Weight: utilpointer.Int32(0),
						},
					},
				}
				iType := gatewayv1alpha2.HeaderMatchRegularExpression
				rules = append(rules, gatewayv1alpha2.HTTPRouteRule{
					Matches: []gatewayv1alpha2.HTTPRouteMatch{
						{
							Path: &gatewayv1alpha2.HTTPPathMatch{
								Value: utilpointer.String("/storage"),
							},
							Headers: []gatewayv1alpha2.HTTPHeaderMatch{
								{
									Name:  "user_id",
									Value: "123*",
									Type:  &iType,
								},
								{
									Name:  "canary",
									Value: "true",
								},
							},
						},
					},
					BackendRefs: []gatewayv1alpha2.HTTPBackendRef{
						{
							BackendRef: gatewayv1alpha2.BackendRef{
								BackendObjectReference: gatewayv1alpha2.BackendObjectReference{
									Kind: &kindSvc,
									Name: "store-svc-canary",
									Port: &portNum,
								},
							},
						},
					},
				})
				return rules
			},
			getRoutes: func() (*int32, []rolloutsv1alpha1.HttpRouteMatch) {
				return utilpointer.Int32(-1), nil
			},
			desiredRules: func() []gatewayv1alpha2.HTTPRouteRule {
				rules := routeDemo.DeepCopy().Spec.Rules
				rules[3].BackendRefs[0].Weight = utilpointer.Int32(1)
				rules[1].BackendRefs[0].Weight = utilpointer.Int32(1)
				return rules
			},
		},
	}

	conf := Config{
		RolloutName:   "rollout-demo",
		CanaryService: "store-svc-canary",
		StableService: "store-svc",
	}

	for _, cs := range cases {
		t.Run(cs.name, func(t *testing.T) {
			controller := &gatewayController{conf: conf}
			weight, matches := cs.getRoutes()
			current := controller.buildDesiredHTTPRoute(cs.getRouteRules(), weight, matches)
			desired := cs.desiredRules()
			if !reflect.DeepEqual(current, desired) {
				t.Fatalf("expect: %v, but get %v", util.DumpJSON(desired), util.DumpJSON(current))
			}
		})
	}
}
