// 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 annotations

import (
	"reflect"
	"testing"

	"github.com/gogo/protobuf/proto"
	"github.com/gogo/protobuf/types"
	networking "istio.io/api/networking/v1alpha3"
)

func TestSplitStringWithSpaceTrim(t *testing.T) {
	testCases := []struct {
		input  string
		expect []string
	}{
		{
			input:  "*",
			expect: []string{"*"},
		},
		{
			input:  "a, b, c",
			expect: []string{"a", "b", "c"},
		},
		{
			input:  "a, *, c",
			expect: []string{"*"},
		},
	}

	for _, testCase := range testCases {
		t.Run("", func(t *testing.T) {
			result := splitStringWithSpaceTrim(testCase.input)
			if !reflect.DeepEqual(testCase.expect, result) {
				t.Fatalf("Must be equal, but got %s", result)
			}
		})
	}
}

func TestCorsParse(t *testing.T) {
	cors := cors{}
	testCases := []struct {
		input  Annotations
		expect *CorsConfig
	}{
		{
			input:  Annotations{},
			expect: nil,
		},
		{
			input: Annotations{
				buildNginxAnnotationKey(enableCors): "false",
			},
			expect: nil,
		},
		{
			input: Annotations{
				buildNginxAnnotationKey(enableCors): "true",
			},
			expect: &CorsConfig{
				Enabled:          true,
				AllowOrigin:      []string{defaultAllowOrigin},
				AllowMethods:     splitStringWithSpaceTrim(defaultAllowMethods),
				AllowHeaders:     splitStringWithSpaceTrim(defaultAllowHeaders),
				AllowCredentials: defaultAllowCredentials,
				MaxAge:           defaultMaxAge,
			},
		},
		{
			input: Annotations{
				buildNginxAnnotationKey(enableCors):  "true",
				buildNginxAnnotationKey(allowOrigin): "https://origin-site.com:4443, http://origin-site.com, https://example.org:1199",
			},
			expect: &CorsConfig{
				Enabled:          true,
				AllowOrigin:      []string{"https://origin-site.com:4443", "http://origin-site.com", "https://example.org:1199"},
				AllowMethods:     splitStringWithSpaceTrim(defaultAllowMethods),
				AllowHeaders:     splitStringWithSpaceTrim(defaultAllowHeaders),
				AllowCredentials: defaultAllowCredentials,
				MaxAge:           defaultMaxAge,
			},
		},
		{
			input: Annotations{
				buildNginxAnnotationKey(enableCors):   "true",
				buildNginxAnnotationKey(allowOrigin):  "https://origin-site.com:4443, http://origin-site.com, https://example.org:1199",
				buildNginxAnnotationKey(allowMethods): "GET, PUT",
				buildNginxAnnotationKey(allowHeaders): "foo,bar",
			},
			expect: &CorsConfig{
				Enabled:          true,
				AllowOrigin:      []string{"https://origin-site.com:4443", "http://origin-site.com", "https://example.org:1199"},
				AllowMethods:     []string{"GET", "PUT"},
				AllowHeaders:     []string{"foo", "bar"},
				AllowCredentials: defaultAllowCredentials,
				MaxAge:           defaultMaxAge,
			},
		},
		{
			input: Annotations{
				buildNginxAnnotationKey(enableCors):       "true",
				buildNginxAnnotationKey(allowOrigin):      "https://origin-site.com:4443, http://origin-site.com, https://example.org:1199",
				buildNginxAnnotationKey(allowMethods):     "GET, PUT",
				buildNginxAnnotationKey(allowHeaders):     "foo,bar",
				buildNginxAnnotationKey(allowCredentials): "false",
				buildNginxAnnotationKey(maxAge):           "100",
			},
			expect: &CorsConfig{
				Enabled:          true,
				AllowOrigin:      []string{"https://origin-site.com:4443", "http://origin-site.com", "https://example.org:1199"},
				AllowMethods:     []string{"GET", "PUT"},
				AllowHeaders:     []string{"foo", "bar"},
				AllowCredentials: false,
				MaxAge:           100,
			},
		},
		{
			input: Annotations{
				buildHigressAnnotationKey(enableCors):     "true",
				buildNginxAnnotationKey(allowOrigin):      "https://origin-site.com:4443, http://origin-site.com, https://example.org:1199",
				buildHigressAnnotationKey(allowMethods):   "GET, PUT",
				buildNginxAnnotationKey(allowHeaders):     "foo,bar",
				buildNginxAnnotationKey(allowCredentials): "false",
				buildNginxAnnotationKey(maxAge):           "100",
			},
			expect: &CorsConfig{
				Enabled:          true,
				AllowOrigin:      []string{"https://origin-site.com:4443", "http://origin-site.com", "https://example.org:1199"},
				AllowMethods:     []string{"GET", "PUT"},
				AllowHeaders:     []string{"foo", "bar"},
				AllowCredentials: false,
				MaxAge:           100,
			},
		},
	}

	for _, testCase := range testCases {
		t.Run("", func(t *testing.T) {
			config := &Ingress{}
			_ = cors.Parse(testCase.input, config, nil)
			if !reflect.DeepEqual(config.Cors, testCase.expect) {
				t.Fatalf("Must be equal.")
			}
		})
	}
}

func TestCorsApplyRoute(t *testing.T) {
	cors := cors{}
	testCases := []struct {
		route  *networking.HTTPRoute
		config *Ingress
		expect *networking.HTTPRoute
	}{
		{
			route:  &networking.HTTPRoute{},
			config: &Ingress{},
			expect: &networking.HTTPRoute{},
		},
		{
			route: &networking.HTTPRoute{},
			config: &Ingress{
				Cors: &CorsConfig{
					Enabled: false,
				},
			},
			expect: &networking.HTTPRoute{},
		},
		{
			route: &networking.HTTPRoute{},
			config: &Ingress{
				Cors: &CorsConfig{
					Enabled:          true,
					AllowOrigin:      []string{"https://origin-site.com:4443", "http://origin-site.com", "https://example.org:1199"},
					AllowMethods:     []string{"GET", "POST"},
					AllowHeaders:     []string{"test", "unique"},
					ExposeHeaders:    []string{"hello", "bye"},
					AllowCredentials: defaultAllowCredentials,
					MaxAge:           defaultMaxAge,
				},
			},
			expect: &networking.HTTPRoute{
				CorsPolicy: &networking.CorsPolicy{
					AllowOrigins: []*networking.StringMatch{
						{
							MatchType: &networking.StringMatch_Exact{
								Exact: "https://origin-site.com:4443",
							},
						},
						{
							MatchType: &networking.StringMatch_Exact{
								Exact: "http://origin-site.com",
							},
						},
						{
							MatchType: &networking.StringMatch_Exact{
								Exact: "https://example.org:1199",
							},
						},
					},
					AllowMethods:  []string{"GET", "POST"},
					AllowHeaders:  []string{"test", "unique"},
					ExposeHeaders: []string{"hello", "bye"},
					AllowCredentials: &types.BoolValue{
						Value: true,
					},
					MaxAge: &types.Duration{
						Seconds: defaultMaxAge,
					},
				},
			},
		},
		{
			route: &networking.HTTPRoute{},
			config: &Ingress{
				Cors: &CorsConfig{
					Enabled:          true,
					AllowOrigin:      []string{"https://*.origin-site.com:4443", "http://*.origin-site.com", "https://example.org:1199"},
					AllowMethods:     []string{"GET", "POST"},
					AllowHeaders:     []string{"test", "unique"},
					ExposeHeaders:    []string{"hello", "bye"},
					AllowCredentials: defaultAllowCredentials,
					MaxAge:           defaultMaxAge,
				},
			},
			expect: &networking.HTTPRoute{
				CorsPolicy: &networking.CorsPolicy{
					AllowOrigins: []*networking.StringMatch{
						{
							MatchType: &networking.StringMatch_Regex{
								Regex: ".*\\.origin-site\\.com:4443",
							},
						},
						{
							MatchType: &networking.StringMatch_Regex{
								Regex: ".*\\.origin-site\\.com",
							},
						},
						{
							MatchType: &networking.StringMatch_Exact{
								Exact: "https://example.org:1199",
							},
						},
					},
					AllowMethods:  []string{"GET", "POST"},
					AllowHeaders:  []string{"test", "unique"},
					ExposeHeaders: []string{"hello", "bye"},
					AllowCredentials: &types.BoolValue{
						Value: true,
					},
					MaxAge: &types.Duration{
						Seconds: defaultMaxAge,
					},
				},
			},
		},
	}

	for _, testCase := range testCases {
		t.Run("", func(t *testing.T) {
			cors.ApplyRoute(testCase.route, testCase.config)
			if !proto.Equal(testCase.route, testCase.expect) {
				t.Fatal("Must be equal.")
			}
		})
	}
}
