package g8

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
)

var handler http.Handler = &testHandler{}

func BenchmarkTestHandler(b *testing.B) {
	request, _ := http.NewRequest("GET", "/handle", nil)

	router := http.NewServeMux()
	router.Handle("/handle", handler)

	for n := 0; n < b.N; n++ {
		responseRecorder := httptest.NewRecorder()
		router.ServeHTTP(responseRecorder, request)
		if responseRecorder.Code != http.StatusOK {
			b.Fatalf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusOK, responseRecorder.Code)
		}
	}
	b.ReportAllocs()
}

func BenchmarkGate_ProtectWhenNoAuthorizationHeader(b *testing.B) {
	gate := New().WithAuthorizationService(NewAuthorizationService().WithToken("good-token"))
	request, _ := http.NewRequest("GET", "/handle", nil)

	router := http.NewServeMux()
	router.Handle("/handle", gate.Protect(handler))

	for n := 0; n < b.N; n++ {
		responseRecorder := httptest.NewRecorder()
		router.ServeHTTP(responseRecorder, request)
		if responseRecorder.Code != http.StatusUnauthorized {
			b.Fatalf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusUnauthorized, responseRecorder.Code)
		}
	}
	b.ReportAllocs()
}

func BenchmarkGate_ProtectWithInvalidToken(b *testing.B) {
	gate := New().WithAuthorizationService(NewAuthorizationService().WithToken("good-token"))
	request, _ := http.NewRequest("GET", "/handle", nil)
	request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", "bad-token"))

	router := http.NewServeMux()
	router.Handle("/handle", gate.Protect(handler))

	for n := 0; n < b.N; n++ {
		responseRecorder := httptest.NewRecorder()
		router.ServeHTTP(responseRecorder, request)
		if responseRecorder.Code != http.StatusUnauthorized {
			b.Fatalf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusUnauthorized, responseRecorder.Code)
		}
	}
	b.ReportAllocs()
}

func BenchmarkGate_ProtectWithValidToken(b *testing.B) {
	gate := New().WithAuthorizationService(NewAuthorizationService().WithToken("good-token"))
	request, _ := http.NewRequest("GET", "/handle", http.NoBody)
	request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", "good-token"))

	router := http.NewServeMux()
	router.Handle("/handle", gate.Protect(handler))

	for n := 0; n < b.N; n++ {
		responseRecorder := httptest.NewRecorder()
		router.ServeHTTP(responseRecorder, request)
		if responseRecorder.Code != http.StatusOK {
			b.Fatalf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusOK, responseRecorder.Code)
		}
	}
	b.ReportAllocs()
}

func BenchmarkGate_ProtectWithPermissionsAndValidToken(b *testing.B) {
	gate := New().WithAuthorizationService(NewAuthorizationService().WithClient(NewClient("token").WithPermission("admin")))
	request, _ := http.NewRequest("GET", "/handle", http.NoBody)
	request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", "token"))

	router := http.NewServeMux()
	router.Handle("/handle", gate.ProtectWithPermissions(handler, []string{"admin"}))

	for n := 0; n < b.N; n++ {
		responseRecorder := httptest.NewRecorder()
		router.ServeHTTP(responseRecorder, request)
		if responseRecorder.Code != http.StatusOK {
			b.Fatalf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusOK, responseRecorder.Code)
		}
	}
	b.ReportAllocs()
}

func BenchmarkGate_ProtectWithPermissionsAndValidTokenButInsufficientPermissions(b *testing.B) {
	gate := New().WithAuthorizationService(NewAuthorizationService().WithClient(NewClient("token").WithPermission("mod")))
	request, _ := http.NewRequest("GET", "/handle", http.NoBody)
	request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", "token"))

	router := http.NewServeMux()
	router.Handle("/handle", gate.ProtectWithPermissions(handler, []string{"admin"}))

	for n := 0; n < b.N; n++ {
		responseRecorder := httptest.NewRecorder()
		router.ServeHTTP(responseRecorder, request)
		if responseRecorder.Code != http.StatusUnauthorized {
			b.Fatalf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusUnauthorized, responseRecorder.Code)
		}
	}
	b.ReportAllocs()
}

func BenchmarkGate_ProtectConcurrently(b *testing.B) {
	gate := New().WithAuthorizationService(NewAuthorizationService().WithToken("good-token"))

	request, _ := http.NewRequest("GET", "/handle", http.NoBody)
	request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", "good-token"))

	badRequest, _ := http.NewRequest("GET", "/handle", http.NoBody)
	badRequest.Header.Add("Authorization", fmt.Sprintf("Bearer %s", "bad-token"))

	router := http.NewServeMux()
	router.Handle("/handle", gate.Protect(handler))

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			responseRecorder := httptest.NewRecorder()
			router.ServeHTTP(responseRecorder, request)
			if responseRecorder.Code != http.StatusOK {
				b.Fatalf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusOK, responseRecorder.Code)
			}
			responseRecorder = httptest.NewRecorder()
			router.ServeHTTP(responseRecorder, badRequest)
			if responseRecorder.Code != http.StatusUnauthorized {
				b.Fatalf("%s %s should have returned %d, but returned %d instead", badRequest.Method, badRequest.URL, http.StatusUnauthorized, responseRecorder.Code)
			}
		}
	})
	b.ReportAllocs()
}

func BenchmarkGate_ProtectWithClientProviderConcurrently(b *testing.B) {
	gate := New().WithAuthorizationService(NewAuthorizationService().WithClientProvider(mockClientProvider))

	request, _ := http.NewRequest("GET", "/handle", http.NoBody)
	request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", TestProviderClientToken))

	firstBadRequest, _ := http.NewRequest("GET", "/handle", http.NoBody)
	firstBadRequest.Header.Set("Authorization", fmt.Sprintf("Bearer %s", "bad-token-1"))

	secondBadRequest, _ := http.NewRequest("GET", "/handle", http.NoBody)
	secondBadRequest.Header.Set("Authorization", fmt.Sprintf("Bearer %s", "bad-token-2"))

	router := http.NewServeMux()
	router.Handle("/handle", gate.Protect(handler))

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			responseRecorder := httptest.NewRecorder()
			router.ServeHTTP(responseRecorder, request)
			if responseRecorder.Code != http.StatusOK {
				b.Fatalf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusOK, responseRecorder.Code)
			}
			responseRecorder = httptest.NewRecorder()
			router.ServeHTTP(responseRecorder, firstBadRequest)
			if responseRecorder.Code != http.StatusUnauthorized {
				b.Fatalf("%s %s should have returned %d, but returned %d instead", firstBadRequest.Method, firstBadRequest.URL, http.StatusUnauthorized, responseRecorder.Code)
			}
			responseRecorder = httptest.NewRecorder()
			router.ServeHTTP(responseRecorder, secondBadRequest)
			if responseRecorder.Code != http.StatusUnauthorized {
				b.Fatalf("%s %s should have returned %d, but returned %d instead", secondBadRequest.Method, secondBadRequest.URL, http.StatusUnauthorized, responseRecorder.Code)
			}
		}
	})
	b.ReportAllocs()
}

func BenchmarkGate_ProtectWithValidTokenAndCustomTokenExtractorFuncConcurrently(b *testing.B) {
	customTokenExtractorFunc := func(request *http.Request) string {
		sessionCookie, err := request.Cookie("session")
		if err != nil {
			return ""
		}
		return sessionCookie.Value
	}
	gate := New().WithAuthorizationService(NewAuthorizationService().WithToken("good-token")).WithCustomTokenExtractor(customTokenExtractorFunc)
	request, _ := http.NewRequest("GET", "/handle", http.NoBody)
	request.AddCookie(&http.Cookie{Name: "session", Value: "good-token"})

	router := http.NewServeMux()
	router.Handle("/handle", gate.Protect(handler))

	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			responseRecorder := httptest.NewRecorder()
			router.ServeHTTP(responseRecorder, request)
			if responseRecorder.Code != http.StatusOK {
				b.Fatalf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, http.StatusOK, responseRecorder.Code)
			}
		}
	})
	b.ReportAllocs()
}
