package api

import (
	"errors"
	"fmt"
	"net/http"
	"testing"

	"github.com/stretchr/testify/assert"
)

var testToken = new(AuthToken)

func testAuthenticator(r *http.Request, s *http.Server) (*AuthToken, error) {
	switch {
	case testToken.Read == -127 || testToken.Write == -127:
		return nil, errors.New("test error")
	case testToken.Read == -128 || testToken.Write == -128:
		return nil, fmt.Errorf("%wdenied", ErrAPIAccessDeniedMessage)
	default:
		return testToken, nil
	}
}

type testAuthHandler struct {
	Read  Permission
	Write Permission
}

func (ah *testAuthHandler) ReadPermission(r *http.Request) Permission {
	return ah.Read
}

func (ah *testAuthHandler) WritePermission(r *http.Request) Permission {
	return ah.Write
}

func (ah *testAuthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Check if request is as expected.
	ar := GetAPIRequest(r)
	switch {
	case ar == nil:
		http.Error(w, "ar == nil", http.StatusInternalServerError)
	case ar.AuthToken == nil:
		http.Error(w, "ar.AuthToken == nil", http.StatusInternalServerError)
	default:
		http.Error(w, "auth success", http.StatusOK)
	}
}

func makeAuthTestPath(reading bool, p Permission) string {
	if reading {
		return fmt.Sprintf("/test/auth/read/%s", p)
	}
	return fmt.Sprintf("/test/auth/write/%s", p)
}

func init() {
	// Set test authenticator.
	err := SetAuthenticator(testAuthenticator)
	if err != nil {
		panic(err)
	}
}

func TestPermissions(t *testing.T) {
	t.Parallel()

	testHandler := &mainHandler{
		mux: mainMux,
	}

	// Define permissions that need testing.
	permissionsToTest := []Permission{
		NotSupported,
		PermitAnyone,
		PermitUser,
		PermitAdmin,
		PermitSelf,
		Dynamic,
		NotFound,
		100,  // Test a too high value.
		-100, // Test a too low value.
		-127, // Simulate authenticator failure.
		-128, // Simulate authentication denied message.
	}

	// Register test handlers.
	for _, p := range permissionsToTest {
		RegisterHandler(makeAuthTestPath(true, p), &testAuthHandler{Read: p})
		RegisterHandler(makeAuthTestPath(false, p), &testAuthHandler{Write: p})
	}

	// Test all the combinations.
	for _, requestPerm := range permissionsToTest {
		for _, handlerPerm := range permissionsToTest {
			for _, method := range []string{
				http.MethodGet,
				http.MethodHead,
				http.MethodPost,
				http.MethodPut,
				http.MethodDelete,
			} {

				// Set request permission for test requests.
				_, reading, _ := getEffectiveMethod(&http.Request{Method: method})
				if reading {
					testToken.Read = requestPerm
					testToken.Write = NotSupported
				} else {
					testToken.Read = NotSupported
					testToken.Write = requestPerm
				}

				// Evaluate expected result.
				var expectSuccess bool
				switch {
				case handlerPerm == PermitAnyone:
					// This is fast-tracked. There are not additional checks.
					expectSuccess = true
				case handlerPerm == Dynamic:
					// This is turned into PermitAnyone in the authenticator.
					// But authentication is still processed and the result still gets
					// sanity checked!
					if requestPerm >= PermitAnyone &&
						requestPerm <= PermitSelf {
						expectSuccess = true
					}
					// Another special case is when the handler requires permission to be
					// processed but the authenticator fails to authenticate the request.
					// In this case, a fallback token with PermitAnyone is used.
					if requestPerm == -128 {
						// -128 is used to simulate a permission denied message.
						expectSuccess = true
					}
				case handlerPerm <= NotSupported:
					// Invalid handler permission.
				case handlerPerm > PermitSelf:
					// Invalid handler permission.
				case requestPerm <= NotSupported:
					// Invalid request permission.
				case requestPerm > PermitSelf:
					// Invalid request permission.
				case requestPerm < handlerPerm:
					// Valid, but insufficient request permission.
				default:
					expectSuccess = true
				}

				if expectSuccess {
					// Test for success.
					if !assert.HTTPBodyContains(
						t,
						testHandler.ServeHTTP,
						method,
						makeAuthTestPath(reading, handlerPerm),
						nil,
						"auth success",
					) {
						t.Errorf(
							"%s with %s (%d) to handler %s (%d)",
							method,
							requestPerm, requestPerm,
							handlerPerm, handlerPerm,
						)
					}
				} else {
					// Test for error.
					if !assert.HTTPError(t,
						testHandler.ServeHTTP,
						method,
						makeAuthTestPath(reading, handlerPerm),
						nil,
					) {
						t.Errorf(
							"%s with %s (%d) to handler %s (%d)",
							method,
							requestPerm, requestPerm,
							handlerPerm, handlerPerm,
						)
					}
				}
			}
		}
	}
}

func TestPermissionDefinitions(t *testing.T) {
	t.Parallel()

	if NotSupported != 0 {
		t.Fatalf("NotSupported must be zero, was %v", NotSupported)
	}
}
