package auth

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

	"github.com/micro/go-micro/v2/auth"
)

var (
	// DefaultExcludes is the paths which are allowed by default
	DefaultExcludes = []string{"/favicon.ico"}
)

// CombinedAuthHandler wraps a server and authenticates requests
func CombinedAuthHandler(h http.Handler) http.Handler {
	return authHandler{
		handler: h,
		auth:    auth.DefaultAuth,
	}
}

type authHandler struct {
	handler http.Handler
	auth    auth.Auth
}

const (
	// BearerScheme is the prefix in the auth header
	BearerScheme = "Bearer "
)

func (h authHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Extract the token from the request
	var token string
	if header := req.Header.Get("Authorization"); len(header) > 0 {
		// Extract the auth token from the request
		if strings.HasPrefix(header, BearerScheme) {
			token = header[len(BearerScheme):]
		}
	} else {
		// Get the token out the cookies if not provided in headers
		if c, err := req.Cookie("micro-token"); err == nil && c != nil {
			token = strings.TrimPrefix(c.Value, auth.CookieName+"=")
			req.Header.Set("Authorization", BearerScheme+token)
		}
	}

	// Return if the user disabled auth on this endpoint
	excludes := h.auth.Options().Exclude
	excludes = append(excludes, DefaultExcludes...)

	loginURL := h.auth.Options().LoginURL
	if len(loginURL) > 0 {
		excludes = append(excludes, loginURL)
	}

	for _, e := range excludes {
		// is a standard exclude, e.g. /rpc
		if e == req.URL.Path {
			h.handler.ServeHTTP(w, req)
			return
		}

		// is a wildcard exclude, e.g. /services/*
		wildcard := strings.Replace(e, "*", "", 1)
		if strings.HasSuffix(e, "*") && strings.HasPrefix(req.URL.Path, wildcard) {
			h.handler.ServeHTTP(w, req)
			return
		}
	}

	// If the token is valid, allow the request
	if _, err := h.auth.Verify(token); err == nil {
		h.handler.ServeHTTP(w, req)
		return
	}

	// If there is no auth login url set, 401
	if loginURL == "" {
		w.WriteHeader(401)
		return
	}

	// Redirect to the login path
	params := url.Values{"redirect_to": {req.URL.Path}}
	loginWithRedirect := fmt.Sprintf("%v?%v", loginURL, params.Encode())
	http.Redirect(w, req, loginWithRedirect, http.StatusTemporaryRedirect)
}