package sso

/*
	handlers.go

	This file contains the handlers for the SSO module.
	If you are looking for handlers for SSO user management,
	please refer to userHandlers.go.
*/

import (
	"encoding/json"
	"net/http"
	"strings"

	"github.com/gofrs/uuid"
	"imuslab.com/zoraxy/mod/utils"
)

// HandleSSOStatus handle the request to get the status of the SSO portal server
func (s *SSOHandler) HandleSSOStatus(w http.ResponseWriter, r *http.Request) {
	type SSOStatus struct {
		Enabled             bool
		SSOInterceptEnabled bool
		ListeningPort       int
		AuthURL             string
	}

	status := SSOStatus{
		Enabled: s.ssoPortalServer != nil,
		//SSOInterceptEnabled: s.ssoInterceptEnabled,
		ListeningPort: s.Config.PortalServerPort,
		AuthURL:       s.Config.AuthURL,
	}

	js, _ := json.Marshal(status)
	utils.SendJSONResponse(w, string(js))
}

// Wrapper for starting and stopping the SSO portal server
// require POST request with key "enable" and value "true" or "false"
func (s *SSOHandler) HandleSSOEnable(w http.ResponseWriter, r *http.Request) {
	enable, err := utils.PostBool(r, "enable")
	if err != nil {
		utils.SendErrorResponse(w, "invalid enable value")
		return
	}

	if enable {
		s.HandleStartSSOPortal(w, r)
	} else {
		s.HandleStopSSOPortal(w, r)
	}
}

// HandleStartSSOPortal handle the request to start the SSO portal server
func (s *SSOHandler) HandleStartSSOPortal(w http.ResponseWriter, r *http.Request) {
	if s.ssoPortalServer != nil {
		//Already enabled. Do restart instead.
		err := s.RestartSSOServer()
		if err != nil {
			utils.SendErrorResponse(w, "failed to start SSO server")
			return
		}
		utils.SendOK(w)
		return
	}

	//Check if the authURL is set correctly. If not, return error
	if s.Config.AuthURL == "" {
		utils.SendErrorResponse(w, "auth URL not set")
		return
	}

	//Start the SSO portal server in go routine
	go s.StartSSOPortal()

	//Write current state to database
	err := s.Config.Database.Write("sso_conf", "enabled", true)
	if err != nil {
		utils.SendErrorResponse(w, "failed to update SSO state")
		return
	}
	utils.SendOK(w)
}

// HandleStopSSOPortal handle the request to stop the SSO portal server
func (s *SSOHandler) HandleStopSSOPortal(w http.ResponseWriter, r *http.Request) {
	if s.ssoPortalServer == nil {
		//Already disabled
		utils.SendOK(w)
		return
	}

	err := s.ssoPortalServer.Close()
	if err != nil {
		s.Log("Failed to stop SSO portal server", err)
		utils.SendErrorResponse(w, "failed to stop SSO portal server")
		return
	}
	s.ssoPortalServer = nil

	//Write current state to database
	err = s.Config.Database.Write("sso_conf", "enabled", false)
	if err != nil {
		utils.SendErrorResponse(w, "failed to update SSO state")
		return
	}
	utils.SendOK(w)
}

// HandlePortChange handle the request to change the SSO portal server port
func (s *SSOHandler) HandlePortChange(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		//Return the current port
		js, _ := json.Marshal(s.Config.PortalServerPort)
		utils.SendJSONResponse(w, string(js))
		return
	}

	port, err := utils.PostInt(r, "port")
	if err != nil {
		utils.SendErrorResponse(w, "invalid port given")
		return
	}

	s.Config.PortalServerPort = port

	//Write to the database
	err = s.Config.Database.Write("sso_conf", "port", port)
	if err != nil {
		utils.SendErrorResponse(w, "failed to update port")
		return
	}

	if s.IsRunning() {
		//Restart the server if it is running
		err = s.RestartSSOServer()
		if err != nil {
			utils.SendErrorResponse(w, "failed to restart SSO server")
			return
		}
	}
	utils.SendOK(w)
}

// HandleSetAuthURL handle the request to change the SSO auth URL
// This is the URL that the SSO portal server will redirect to for authentication
// e.g. auth.yourdomain.com
func (s *SSOHandler) HandleSetAuthURL(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		//Return the current auth URL
		js, _ := json.Marshal(s.Config.AuthURL)
		utils.SendJSONResponse(w, string(js))
		return
	}

	//Get the auth URL
	authURL, err := utils.PostPara(r, "auth_url")
	if err != nil {
		utils.SendErrorResponse(w, "invalid auth URL given")
		return
	}

	s.Config.AuthURL = authURL

	//Write to the database
	err = s.Config.Database.Write("sso_conf", "authurl", authURL)
	if err != nil {
		utils.SendErrorResponse(w, "failed to update auth URL")
		return
	}

	//Clear the cookie store and restart the server
	err = s.RestartSSOServer()
	if err != nil {
		utils.SendErrorResponse(w, "failed to restart SSO server")
		return
	}
	utils.SendOK(w)
}

// HandleRegisterApp handle the request to register a new app to the SSO portal
func (s *SSOHandler) HandleRegisterApp(w http.ResponseWriter, r *http.Request) {
	appName, err := utils.PostPara(r, "app_name")
	if err != nil {
		utils.SendErrorResponse(w, "invalid app name given")
		return
	}

	id, err := utils.PostPara(r, "app_id")
	if err != nil {
		//If id is not given, use the app name with a random UUID
		newID, err := uuid.NewV4()
		if err != nil {
			utils.SendErrorResponse(w, "failed to generate new app ID")
			return
		}
		id = strings.ReplaceAll(appName, " ", "") + "-" + newID.String()
	}

	//Check if the given appid is already in use
	if _, ok := s.Apps[id]; ok {
		utils.SendErrorResponse(w, "app ID already in use")
		return
	}

	/*
		Process the app domain
		An app can have multiple domains, separated by commas
		Usually the app domain is the proxy rule that points to the app
		For example, if the app is hosted at app.yourdomain.com, the app domain is app.yourdomain.com
	*/
	appDomain, err := utils.PostPara(r, "app_domain")
	if err != nil {
		utils.SendErrorResponse(w, "invalid app URL given")
		return
	}

	appURLs := strings.Split(appDomain, ",")
	//Remove padding and trailing spaces in each URL
	for i := range appURLs {
		appURLs[i] = strings.TrimSpace(appURLs[i])
	}

	//Create a new app entry
	thisAppEntry := RegisteredUpstreamApp{
		ID:              id,
		Secret:          "",
		Domain:          appURLs,
		Scopes:          []string{},
		SessionDuration: 3600,
	}

	js, _ := json.Marshal(thisAppEntry)

	//Create a new app in the database
	err = s.Config.Database.Write("sso_apps", appName, string(js))
	if err != nil {
		utils.SendErrorResponse(w, "failed to create new app")
		return
	}

	//Also add the app to runtime config
	s.Apps[appName] = thisAppEntry

	utils.SendOK(w)
}

// HandleAppRemove handle the request to remove an app from the SSO portal
func (s *SSOHandler) HandleAppRemove(w http.ResponseWriter, r *http.Request) {
	appID, err := utils.PostPara(r, "app_id")
	if err != nil {
		utils.SendErrorResponse(w, "invalid app ID given")
		return
	}

	//Check if the app actually exists
	if _, ok := s.Apps[appID]; !ok {
		utils.SendErrorResponse(w, "app not found")
		return
	}
	delete(s.Apps, appID)

	//Also remove it from the database
	err = s.Config.Database.Delete("sso_apps", appID)
	if err != nil {
		s.Log("Failed to remove app from database", err)
	}

}
