package label

import (
	"testing"
	"time"

	"github.com/containous/flaeg"
	"github.com/containous/traefik/types"
	"github.com/stretchr/testify/assert"
)

func TestParseErrorPages(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected map[string]*types.ErrorPage
	}{
		{
			desc: "2 errors pages",
			labels: map[string]string{
				Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus:  "404",
				Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageBackend: "foo_backend",
				Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageQuery:   "foo_query",
				Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageStatus:  "500,600",
				Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageBackend: "bar_backend",
				Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageQuery:   "bar_query",
			},
			expected: map[string]*types.ErrorPage{
				"foo": {
					Status:  []string{"404"},
					Query:   "foo_query",
					Backend: "foo_backend",
				},
				"bar": {
					Status:  []string{"500", "600"},
					Query:   "bar_query",
					Backend: "bar_backend",
				},
			},
		},
		{
			desc: "only status field",
			labels: map[string]string{
				Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus: "404",
			},
			expected: map[string]*types.ErrorPage{
				"foo": {
					Status: []string{"404"},
				},
			},
		},
		{
			desc: "invalid field",
			labels: map[string]string{
				Prefix + BaseFrontendErrorPage + "foo." + "courgette": "404",
			},
			expected: map[string]*types.ErrorPage{"foo": {}},
		},
		{
			desc:     "no error pages labels",
			labels:   map[string]string{},
			expected: nil,
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			pages := ParseErrorPages(test.labels, Prefix+BaseFrontendErrorPage, RegexpFrontendErrorPage)

			assert.EqualValues(t, test.expected, pages)
		})
	}
}

func TestParseRateSets(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected map[string]*types.Rate
	}{
		{
			desc: "2 rate limits",
			labels: map[string]string{
				Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod:  "6",
				Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
				Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst:   "18",
				Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod:  "3",
				Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
				Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst:   "9",
			},
			expected: map[string]*types.Rate{
				"foo": {
					Period:  flaeg.Duration(6 * time.Second),
					Average: 12,
					Burst:   18,
				},
				"bar": {
					Period:  flaeg.Duration(3 * time.Second),
					Average: 6,
					Burst:   9,
				},
			},
		},
		{
			desc:     "no rate limits labels",
			labels:   map[string]string{},
			expected: nil,
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			rateSets := ParseRateSets(test.labels, Prefix+BaseFrontendRateLimit, RegexpFrontendRateLimit)

			assert.EqualValues(t, test.expected, rateSets)
		})
	}
}

func TestWhiteList(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected *types.WhiteList
	}{
		{
			desc:     "should return nil when no white list labels",
			labels:   map[string]string{},
			expected: nil,
		},
		{
			desc: "should return a struct when deprecated label",
			labels: map[string]string{
				TraefikFrontendWhitelistSourceRange: "10.10.10.10",
			},
			expected: &types.WhiteList{
				SourceRange: []string{
					"10.10.10.10",
				},
				UseXForwardedFor: false,
			},
		},
		{
			desc: "should return a struct when only range",
			labels: map[string]string{
				TraefikFrontendWhiteListSourceRange: "10.10.10.10",
			},
			expected: &types.WhiteList{
				SourceRange: []string{
					"10.10.10.10",
				},
				UseXForwardedFor: false,
			},
		},
		{
			desc: "should return a struct when range and UseXForwardedFor",
			labels: map[string]string{
				TraefikFrontendWhiteListSourceRange:      "10.10.10.10",
				TraefikFrontendWhiteListUseXForwardedFor: "true",
			},
			expected: &types.WhiteList{
				SourceRange: []string{
					"10.10.10.10",
				},
				UseXForwardedFor: true,
			},
		},
		{
			desc: "should return a struct when mix deprecated label and new labels",
			labels: map[string]string{
				TraefikFrontendWhitelistSourceRange:      "20.20.20.20",
				TraefikFrontendWhiteListSourceRange:      "10.10.10.10",
				TraefikFrontendWhiteListUseXForwardedFor: "true",
			},
			expected: &types.WhiteList{
				SourceRange: []string{
					"10.10.10.10",
				},
				UseXForwardedFor: true,
			},
		},
		{
			desc: "should return nil when only UseXForwardedFor",
			labels: map[string]string{
				TraefikFrontendWhiteListUseXForwardedFor: "true",
			},
			expected: nil,
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			actual := GetWhiteList(test.labels)
			assert.Equal(t, test.expected, actual)
		})
	}
}

func TestGetCircuitBreaker(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected *types.CircuitBreaker
	}{
		{
			desc:     "should return nil when no CB label",
			labels:   map[string]string{},
			expected: nil,
		},
		{
			desc: "should return a struct when CB label is set",
			labels: map[string]string{
				TraefikBackendCircuitBreakerExpression: "NetworkErrorRatio() > 0.5",
			},
			expected: &types.CircuitBreaker{
				Expression: "NetworkErrorRatio() > 0.5",
			},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			actual := GetCircuitBreaker(test.labels)

			assert.Equal(t, test.expected, actual)
		})
	}
}

func TestGetLoadBalancer(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected *types.LoadBalancer
	}{
		{
			desc:     "should return nil when no LB labels",
			labels:   map[string]string{},
			expected: nil,
		},
		{
			desc: "should return a struct when labels are set",
			labels: map[string]string{
				TraefikBackendLoadBalancerMethod:               "drr",
				TraefikBackendLoadBalancerSticky:               "true",
				TraefikBackendLoadBalancerStickiness:           "true",
				TraefikBackendLoadBalancerStickinessCookieName: "foo",
			},
			expected: &types.LoadBalancer{
				Method: "drr",
				Sticky: true,
				Stickiness: &types.Stickiness{
					CookieName: "foo",
				},
			},
		},
		{
			desc: "should return a nil Stickiness when Stickiness is not set",
			labels: map[string]string{
				TraefikBackendLoadBalancerMethod:               "drr",
				TraefikBackendLoadBalancerSticky:               "true",
				TraefikBackendLoadBalancerStickinessCookieName: "foo",
			},
			expected: &types.LoadBalancer{
				Method:     "drr",
				Sticky:     true,
				Stickiness: nil,
			},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			actual := GetLoadBalancer(test.labels)

			assert.Equal(t, test.expected, actual)
		})
	}
}

func TestGetMaxConn(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected *types.MaxConn
	}{
		{
			desc:     "should return nil when no max conn labels",
			labels:   map[string]string{},
			expected: nil,
		},
		{
			desc: "should return nil when no amount label",
			labels: map[string]string{
				TraefikBackendMaxConnExtractorFunc: "client.ip",
			},
			expected: nil,
		},
		{
			desc: "should return default when no empty extractorFunc label",
			labels: map[string]string{
				TraefikBackendMaxConnExtractorFunc: "",
				TraefikBackendMaxConnAmount:        "666",
			},
			expected: &types.MaxConn{
				ExtractorFunc: "request.host",
				Amount:        666,
			},
		},
		{
			desc: "should return a struct when max conn labels are set",
			labels: map[string]string{
				TraefikBackendMaxConnExtractorFunc: "client.ip",
				TraefikBackendMaxConnAmount:        "666",
			},
			expected: &types.MaxConn{
				ExtractorFunc: "client.ip",
				Amount:        666,
			},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			actual := GetMaxConn(test.labels)
			assert.Equal(t, test.expected, actual)
		})
	}
}

func TestGetHealthCheck(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected *types.HealthCheck
	}{
		{
			desc:     "should return nil when no health check labels",
			labels:   map[string]string{},
			expected: nil,
		},
		{
			desc: "should return nil when no health check Path label",
			labels: map[string]string{
				TraefikBackendHealthCheckPort:     "80",
				TraefikBackendHealthCheckInterval: "6",
			},
			expected: nil,
		},
		{
			desc: "should return a struct when health check labels are set",
			labels: map[string]string{
				TraefikBackendHealthCheckPath:     "/health",
				TraefikBackendHealthCheckPort:     "80",
				TraefikBackendHealthCheckInterval: "6",
			},
			expected: &types.HealthCheck{
				Path:     "/health",
				Port:     80,
				Interval: "6",
			},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			actual := GetHealthCheck(test.labels)

			assert.Equal(t, test.expected, actual)
		})
	}
}

func TestGetBuffering(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected *types.Buffering
	}{
		{
			desc:     "should return nil when no buffering labels",
			labels:   map[string]string{},
			expected: nil,
		},
		{
			desc: "should return a struct when buffering labels are set",
			labels: map[string]string{
				TraefikBackendBufferingMaxResponseBodyBytes: "10485760",
				TraefikBackendBufferingMemResponseBodyBytes: "2097152",
				TraefikBackendBufferingMaxRequestBodyBytes:  "10485760",
				TraefikBackendBufferingMemRequestBodyBytes:  "2097152",
				TraefikBackendBufferingRetryExpression:      "IsNetworkError() && Attempts() <= 2",
			},
			expected: &types.Buffering{
				MaxResponseBodyBytes: 10485760,
				MemResponseBodyBytes: 2097152,
				MaxRequestBodyBytes:  10485760,
				MemRequestBodyBytes:  2097152,
				RetryExpression:      "IsNetworkError() && Attempts() <= 2",
			},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			actual := GetBuffering(test.labels)

			assert.Equal(t, test.expected, actual)
		})
	}
}

func TestGetRedirect(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected *types.Redirect
	}{

		{
			desc:     "should return nil when no redirect labels",
			labels:   map[string]string{},
			expected: nil,
		},
		{
			desc: "should use only entry point tag when mix regex redirect and entry point redirect",
			labels: map[string]string{
				TraefikFrontendRedirectEntryPoint:  "https",
				TraefikFrontendRedirectRegex:       "(.*)",
				TraefikFrontendRedirectReplacement: "$1",
			},
			expected: &types.Redirect{
				EntryPoint: "https",
			},
		},
		{
			desc: "should return a struct when entry point redirect label",
			labels: map[string]string{
				TraefikFrontendRedirectEntryPoint: "https",
			},
			expected: &types.Redirect{
				EntryPoint: "https",
			},
		},
		{
			desc: "should return a struct when entry point redirect label (permanent)",
			labels: map[string]string{
				TraefikFrontendRedirectEntryPoint: "https",
				TraefikFrontendRedirectPermanent:  "true",
			},
			expected: &types.Redirect{
				EntryPoint: "https",
				Permanent:  true,
			},
		},
		{
			desc: "should return a struct when regex redirect labels",
			labels: map[string]string{
				TraefikFrontendRedirectRegex:       "(.*)",
				TraefikFrontendRedirectReplacement: "$1",
			},
			expected: &types.Redirect{
				Regex:       "(.*)",
				Replacement: "$1",
			},
		},
		{
			desc: "should return a struct when regex redirect labels (permanent)",
			labels: map[string]string{
				TraefikFrontendRedirectRegex:       "(.*)",
				TraefikFrontendRedirectReplacement: "$1",
				TraefikFrontendRedirectPermanent:   "true",
			},
			expected: &types.Redirect{
				Regex:       "(.*)",
				Replacement: "$1",
				Permanent:   true,
			},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			actual := GetRedirect(test.labels)

			assert.Equal(t, test.expected, actual)
		})
	}
}

func TestGetRateLimit(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected *types.RateLimit
	}{
		{
			desc:     "should return nil when no rate limit labels",
			labels:   map[string]string{},
			expected: nil,
		},
		{
			desc: "should return a struct when rate limit labels are defined",
			labels: map[string]string{
				TraefikFrontendRateLimitExtractorFunc:                            "client.ip",
				Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod:  "6",
				Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
				Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst:   "18",
				Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod:  "3",
				Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
				Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst:   "9",
			},
			expected: &types.RateLimit{
				ExtractorFunc: "client.ip",
				RateSet: map[string]*types.Rate{
					"foo": {
						Period:  flaeg.Duration(6 * time.Second),
						Average: 12,
						Burst:   18,
					},
					"bar": {
						Period:  flaeg.Duration(3 * time.Second),
						Average: 6,
						Burst:   9,
					},
				},
			},
		},
		{
			desc: "should return nil when ExtractorFunc is missing",
			labels: map[string]string{
				Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod:  "6",
				Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
				Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst:   "18",
				Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod:  "3",
				Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
				Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst:   "9",
			},
			expected: nil,
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			actual := GetRateLimit(test.labels)

			assert.Equal(t, test.expected, actual)
		})
	}
}

func TestGetHeaders(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected *types.Headers
	}{
		{
			desc:     "should return nil when no custom headers options are set",
			labels:   map[string]string{},
			expected: nil,
		},
		{
			desc: "should return a struct when all custom headers options are set",
			labels: map[string]string{
				TraefikFrontendRequestHeaders:          "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
				TraefikFrontendResponseHeaders:         "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
				TraefikFrontendSSLProxyHeaders:         "Access-Control-Allow-Methods:POST,GET,OPTIONS || Content-type: application/json; charset=utf-8",
				TraefikFrontendAllowedHosts:            "foo,bar,bor",
				TraefikFrontendHostsProxyHeaders:       "foo,bar,bor",
				TraefikFrontendSSLHost:                 "foo",
				TraefikFrontendCustomFrameOptionsValue: "foo",
				TraefikFrontendContentSecurityPolicy:   "foo",
				TraefikFrontendPublicKey:               "foo",
				TraefikFrontendReferrerPolicy:          "foo",
				TraefikFrontendCustomBrowserXSSValue:   "foo",
				TraefikFrontendSTSSeconds:              "666",
				TraefikFrontendSSLRedirect:             "true",
				TraefikFrontendSSLTemporaryRedirect:    "true",
				TraefikFrontendSTSIncludeSubdomains:    "true",
				TraefikFrontendSTSPreload:              "true",
				TraefikFrontendForceSTSHeader:          "true",
				TraefikFrontendFrameDeny:               "true",
				TraefikFrontendContentTypeNosniff:      "true",
				TraefikFrontendBrowserXSSFilter:        "true",
				TraefikFrontendIsDevelopment:           "true",
			},
			expected: &types.Headers{
				CustomRequestHeaders: map[string]string{
					"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
					"Content-Type":                 "application/json; charset=utf-8",
				},
				CustomResponseHeaders: map[string]string{
					"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
					"Content-Type":                 "application/json; charset=utf-8",
				},
				SSLProxyHeaders: map[string]string{
					"Access-Control-Allow-Methods": "POST,GET,OPTIONS",
					"Content-Type":                 "application/json; charset=utf-8",
				},
				AllowedHosts:            []string{"foo", "bar", "bor"},
				HostsProxyHeaders:       []string{"foo", "bar", "bor"},
				SSLHost:                 "foo",
				CustomFrameOptionsValue: "foo",
				ContentSecurityPolicy:   "foo",
				PublicKey:               "foo",
				ReferrerPolicy:          "foo",
				CustomBrowserXSSValue:   "foo",
				STSSeconds:              666,
				SSLRedirect:             true,
				SSLTemporaryRedirect:    true,
				STSIncludeSubdomains:    true,
				STSPreload:              true,
				ForceSTSHeader:          true,
				FrameDeny:               true,
				ContentTypeNosniff:      true,
				BrowserXSSFilter:        true,
				IsDevelopment:           true,
			},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			actual := GetHeaders(test.labels)

			assert.Equal(t, test.expected, actual)
		})
	}
}

func TestProviderGetErrorPages(t *testing.T) {
	testCases := []struct {
		desc     string
		labels   map[string]string
		expected map[string]*types.ErrorPage
	}{
		{
			desc:     "should return nil when no tags",
			labels:   map[string]string{},
			expected: nil,
		},
		{
			desc: "should return a map when tags are present",
			labels: map[string]string{
				Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageStatus:  "404",
				Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageBackend: "foo_backend",
				Prefix + BaseFrontendErrorPage + "foo." + SuffixErrorPageQuery:   "foo_query",
				Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageStatus:  "500,600",
				Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageBackend: "bar_backend",
				Prefix + BaseFrontendErrorPage + "bar." + SuffixErrorPageQuery:   "bar_query",
			},
			expected: map[string]*types.ErrorPage{
				"foo": {
					Status:  []string{"404"},
					Query:   "foo_query",
					Backend: "foo_backend",
				},
				"bar": {
					Status:  []string{"500", "600"},
					Query:   "bar_query",
					Backend: "bar_backend",
				},
			},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			result := GetErrorPages(test.labels)

			assert.Equal(t, test.expected, result)
		})
	}
}
