package server

import (
	"github.com/1f349/lavender/logger"
	"github.com/1f349/lavender/pages"
	"github.com/1f349/lavender/scope"
	"github.com/julienschmidt/httprouter"
	"net/http"
	"net/url"
	"strings"
)

func (h *HttpServer) authorizeEndpoint(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) {
	// function is only called with GET or POST method
	isPost := req.Method == http.MethodPost

	var form url.Values
	if isPost {
		err := req.ParseForm()
		if err != nil {
			http.Error(rw, "Failed to parse form", http.StatusInternalServerError)
			return
		}
		form = req.PostForm
	} else {
		form = req.URL.Query()
	}

	clientID := form.Get("client_id")
	client, err := h.oauthMgr.GetClient(req.Context(), clientID)
	if err != nil {
		http.Error(rw, "Invalid client", http.StatusBadRequest)
		return
	}

	redirectUri := form.Get("redirect_uri")
	clientDomains := strings.Fields(client.GetDomain())
	allowedDomains := make(map[string]bool)
	for _, i := range clientDomains {
		allowedDomains[i] = true
	}

	if !allowedDomains[redirectUri] {
		http.Error(rw, "Incorrect redirect URI", http.StatusBadRequest)
		return
	}

	if form.Has("cancel") {
		uCancel, err := url.Parse(redirectUri)
		if err != nil {
			http.Error(rw, "Invalid redirect URI", http.StatusBadRequest)
			return
		}
		q := uCancel.Query()
		q.Set("error", "access_denied")
		uCancel.RawQuery = q.Encode()

		http.Redirect(rw, req, uCancel.String(), http.StatusFound)
		return
	}

	var isSSO bool
	if clientIsSSO, ok := client.(interface{ IsSSO() bool }); ok {
		isSSO = clientIsSSO.IsSSO()
	}

	switch {
	case isSSO && isPost:
		http.Error(rw, "400 Bad Request: Not sure how you even managed to send a POST request for an SSO application", http.StatusBadRequest)
		return
	case !isSSO && !isPost:
		// find application redirect domain and name
		appUrlFull, err := url.Parse(redirectUri)
		if err != nil {
			http.Error(rw, "500 Internal Server Error: Failed to parse application redirect URL", http.StatusInternalServerError)
			return
		}
		appDomain := appUrlFull.Scheme + "://" + appUrlFull.Host
		appName := appUrlFull.Host
		if clientGetName, ok := client.(interface{ GetName() string }); ok {
			n := clientGetName.GetName()
			if n != "" {
				appName = n
			}
		}

		scopeList := form.Get("scope")
		if !scope.ScopesExist(scopeList) {
			http.Error(rw, "Invalid scopes", http.StatusBadRequest)
			return
		}

		rw.WriteHeader(http.StatusOK)
		pages.RenderPageTemplate(rw, "oauth-authorize", map[string]any{
			"ServiceName":  h.conf.ServiceName,
			"AppName":      appName,
			"AppDomain":    appDomain,
			"DisplayName":  auth.DisplayName,
			"WantsList":    scope.FancyScopeList(scopeList),
			"ResponseType": form.Get("response_type"),
			"ResponseMode": form.Get("response_mode"),
			"ClientID":     form.Get("client_id"),
			"RedirectUri":  form.Get("redirect_uri"),
			"State":        form.Get("state"),
			"Scope":        scopeList,
			"Nonce":        form.Get("nonce"),
		})
		return
	}

	// redirect with an error if the action is not authorize
	if form.Get("oauth_action") == "authorize" || isSSO {
		if err := h.oauthSrv.HandleAuthorizeRequest(rw, req); err != nil {
			logger.Logger.Error(err)
			http.Error(rw, err.Error(), http.StatusBadRequest)
		}
		return
	}

	parsedRedirect, err := url.Parse(redirectUri)
	if err != nil {
		http.Error(rw, "400 Bad Request: Invalid redirect URI", http.StatusBadRequest)
		return
	}
	q := parsedRedirect.Query()
	q.Set("error", "user_cancelled")
	parsedRedirect.RawQuery = q.Encode()
	http.Redirect(rw, req, parsedRedirect.String(), http.StatusFound)
}

func (h *HttpServer) oauthUserAuthorization(rw http.ResponseWriter, req *http.Request) (string, error) {
	err := req.ParseForm()
	if err != nil {
		return "", err
	}

	auth, err := h.internalAuthenticationHandler(rw, req)
	if err != nil {
		return "", err
	}

	if auth.IsGuest() {
		// handle redirecting to oauth
		var q url.Values
		switch req.Method {
		case http.MethodPost:
			q = req.PostForm
		case http.MethodGet:
			q = req.URL.Query()
		default:
			http.Error(rw, "405 Method Not Allowed", http.StatusMethodNotAllowed)
			return "", err
		}

		redirectUrl := PrepareRedirectUrl("/login", &url.URL{Path: "/authorize", RawQuery: q.Encode()})
		http.Redirect(rw, req, redirectUrl.String(), http.StatusFound)
		return "", nil
	}
	return auth.Subject, nil
}
