package handler

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

	c "npm/internal/api/context"
	h "npm/internal/api/http"
	"npm/internal/entity/auth"
	"npm/internal/entity/user"
	"npm/internal/errors"
	"npm/internal/logger"
)

type setAuthModel struct {
	Type          string `json:"type" db:"type"`
	Secret        string `json:"secret,omitempty" db:"secret"`
	CurrentSecret string `json:"current_secret,omitempty"`
}

// SetAuth sets a auth method. This can be used for "me" and `2` for example
// Route: POST /users/:userID/auth
func SetAuth() func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)

		var newAuth setAuthModel
		err := json.Unmarshal(bodyBytes, &newAuth)
		if err != nil {
			h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
			return
		}

		userID, isSelf, userIDErr := getUserIDFromRequest(r)
		if userIDErr != nil {
			h.ResultErrorJSON(w, r, http.StatusBadRequest, userIDErr.Error(), nil)
			return
		}

		// Load user
		thisUser, thisUserErr := user.GetByID(userID)
		if thisUserErr != nil {
			h.ResultErrorJSON(w, r, http.StatusBadRequest, thisUserErr.Error(), nil)
			return
		}

		if thisUser.IsSystem {
			h.ResultErrorJSON(w, r, http.StatusBadRequest, "Cannot set password for system user", nil)
			return
		}

		// Load existing auth for user
		userAuth, userAuthErr := auth.GetByUserIDType(userID, newAuth.Type)
		if userAuthErr != nil {
			h.ResultErrorJSON(w, r, http.StatusBadRequest, userAuthErr.Error(), nil)
			return
		}

		if isSelf {
			// confirm that the current_secret given is valid for the one stored in the database
			validateErr := userAuth.ValidateSecret(newAuth.CurrentSecret)
			if validateErr != nil {
				logger.Debug("%s: %s", "Password change: current password was incorrect", validateErr.Error())
				// Sleep for 1 second to prevent brute force password guessing
				time.Sleep(time.Second)

				h.ResultErrorJSON(w, r, http.StatusBadRequest, errors.ErrCurrentPasswordInvalid.Error(), nil)
				return
			}
		}

		if newAuth.Type == auth.TypePassword {
			err := userAuth.SetPassword(newAuth.Secret)
			if err != nil {
				logger.Error("SetPasswordError", err)
				h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
			}
		}

		if err = userAuth.Save(); err != nil {
			logger.Error("AuthSaveError", err)
			h.ResultErrorJSON(w, r, http.StatusInternalServerError, "Unable to save Authentication for User", nil)
			return
		}

		userAuth.Secret = ""

		// todo: add to audit-log

		h.ResultResponseJSON(w, r, http.StatusOK, userAuth)
	}
}