package bypass

import (
	"fmt"
	"net/http"
	"path"
	"sync"

	log "github.com/sirupsen/logrus"
)

var byPassMutex sync.RWMutex

// bypassPaths is a set of paths that should bypass middleware.
var bypassPaths = make(map[string]struct{})

// AddBypassPath adds an exact path to the list of paths that bypass middleware.
// Paths can include wildcards, such as /api/*. Paths are matched using path.Match.
// Returns an error if the path has invalid pattern.
func AddBypassPath(path string) error {
	byPassMutex.Lock()
	defer byPassMutex.Unlock()
	if err := validatePath(path); err != nil {
		return fmt.Errorf("validate: %w", err)
	}
	bypassPaths[path] = struct{}{}
	return nil
}

// RemovePath removes a path from the list of paths that bypass middleware.
func RemovePath(path string) {
	byPassMutex.Lock()
	defer byPassMutex.Unlock()
	delete(bypassPaths, path)
}

// GetList returns a list of all bypass paths.
func GetList() []string {
	byPassMutex.RLock()
	defer byPassMutex.RUnlock()

	list := make([]string, 0, len(bypassPaths))
	for k := range bypassPaths {
		list = append(list, k)
	}

	return list
}

// ShouldBypass checks if the request path is one of the auth bypass paths and returns true if the middleware should be bypassed.
// This can be used to bypass authz/authn middlewares for certain paths, such as webhooks that implement their own authentication.
func ShouldBypass(requestPath string, h http.Handler, w http.ResponseWriter, r *http.Request) bool {
	byPassMutex.RLock()
	defer byPassMutex.RUnlock()

	for bypassPath := range bypassPaths {
		matched, err := path.Match(bypassPath, requestPath)
		if err != nil {
			log.WithContext(r.Context()).Errorf("Error matching path %s with %s from %s: %v", bypassPath, requestPath, GetList(), err)
			continue
		}
		if matched {
			h.ServeHTTP(w, r)
			return true
		}
	}

	return false
}

func validatePath(p string) error {
	_, err := path.Match(p, "")
	return err
}
