package callee

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"code.justin.tv/sse/malachai/pkg/capabilities"
	"code.justin.tv/sse/malachai/pkg/events"
	"code.justin.tv/sse/malachai/pkg/jwtvalidation"
	"code.justin.tv/sse/malachai/pkg/log"
)

// newCapabilitiesAuthorizer returns middleware that authorizes the caller's
// capabilities (as a capabilities.Capabilities object)
func newCapabilitiesAuthorizer(cfg *Config, eventsLogger events.EventWriter, lgr log.S2SLogger) (fn func(http.Handler) http.Handler) {
	fn = func(inner http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			var (
				caps   *capabilities.Capabilities
				caller *jwtvalidation.SigningEntity
			)

			if caps = GetCapabilities(r.Context()); caps == nil {
				http.Error(w, "caller capabilities are not set in request context", http.StatusInternalServerError)
				return
			}

			if caller = GetCallerID(r.Context()); caller == nil {
				http.Error(w, "caller identity not set in request context.", http.StatusInternalServerError)
				return
			}

			eventLogger := eventsLogger.WithFields(&events.Event{
				URLPath:   r.URL.EscapedPath(),
				CallerID:  caller.Caller,
				Timestamp: events.Time(time.Now()),
			})

			err := isRequestAuthorized(r, caps, cfg.SupportWildCard)
			if err != nil {
				// if passthrough mode is disabled, reject the request
				if !cfg.PassthroughMode {
					eventLogger.WriteEvent(&events.Event{
						Category:   "RequestUnauthorized",
						StatusCode: "401",
						Message:    err.Error(),
					})

					http.Error(w, err.Error(), http.StatusUnauthorized)
					return
				}

				// if passthrough mode is enabled, accept the request
				err = nil
			}

			eventLogger.WriteEvent(&events.Event{
				Category: "RequestAuthorized",
			})

			inner.ServeHTTP(w, r)
		})
	}
	return
}
func isRequestAuthorized(r *http.Request, caps *capabilities.Capabilities, supportWildcard bool) (err error) {
	requestedCap := buildRequestedCapabilitiesFromHTTPRequest(r)
	var authorized bool

	if supportWildcard {
		authorized = caps.ContainsWildCard(requestedCap)
	} else {
		authorized = caps.Contains(requestedCap)
	}

	if !authorized {
		var callerName string
		if caller := GetCallerID(r.Context()); caller != nil {
			callerName = caller.Caller
		}
		callee := GetCalleeName(r.Context())
		err = fmt.Errorf("caller %s does not have authorization to perform capability %s on callee %s", callerName, requestedCap, callee)
	}
	return
}

func buildRequestedCapabilitiesFromHTTPRequest(r *http.Request) (cap string) {
	//Remove leading/trailing whitespaces and slashes from URL.Path
	urlPath := strings.Trim(r.URL.Path, " ")
	if !strings.HasPrefix(urlPath, "/") {
		urlPath = fmt.Sprintf("/%s", urlPath)
	}

	cap = fmt.Sprintf("%s %s", r.Method, urlPath)
	return
}
