package setup_keys

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

	"github.com/gorilla/mux"

	"github.com/netbirdio/netbird/management/server/account"
	nbcontext "github.com/netbirdio/netbird/management/server/context"
	"github.com/netbirdio/netbird/management/server/http/api"
	"github.com/netbirdio/netbird/management/server/http/util"
	"github.com/netbirdio/netbird/management/server/status"
	"github.com/netbirdio/netbird/management/server/types"
)

// handler is a handler that returns a list of setup keys of the account
type handler struct {
	accountManager account.Manager
}

func AddEndpoints(accountManager account.Manager, router *mux.Router) {
	keysHandler := newHandler(accountManager)
	router.HandleFunc("/setup-keys", keysHandler.getAllSetupKeys).Methods("GET", "OPTIONS")
	router.HandleFunc("/setup-keys", keysHandler.createSetupKey).Methods("POST", "OPTIONS")
	router.HandleFunc("/setup-keys/{keyId}", keysHandler.getSetupKey).Methods("GET", "OPTIONS")
	router.HandleFunc("/setup-keys/{keyId}", keysHandler.updateSetupKey).Methods("PUT", "OPTIONS")
	router.HandleFunc("/setup-keys/{keyId}", keysHandler.deleteSetupKey).Methods("DELETE", "OPTIONS")
}

// newHandler creates a new setup key handler
func newHandler(accountManager account.Manager) *handler {
	return &handler{
		accountManager: accountManager,
	}
}

// createSetupKey is a POST requests that creates a new SetupKey
func (h *handler) createSetupKey(w http.ResponseWriter, r *http.Request) {
	userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
	if err != nil {
		util.WriteError(r.Context(), err, w)
		return
	}

	accountID, userID := userAuth.AccountId, userAuth.UserId
	req := &api.PostApiSetupKeysJSONRequestBody{}
	err = json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
		return
	}

	if req.Name == "" {
		util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "setup key name shouldn't be empty"), w)
		return
	}

	if !(types.SetupKeyType(req.Type) == types.SetupKeyReusable ||
		types.SetupKeyType(req.Type) == types.SetupKeyOneOff) {
		util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "unknown setup key type %s", req.Type), w)
		return
	}

	expiresIn := time.Duration(req.ExpiresIn) * time.Second

	if expiresIn < 0 {
		util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "expiresIn can not be in the past"), w)
		return
	}

	if req.AutoGroups == nil {
		req.AutoGroups = []string{}
	}

	var ephemeral bool
	if req.Ephemeral != nil {
		ephemeral = *req.Ephemeral
	}

	var allowExtraDNSLabels bool
	if req.AllowExtraDnsLabels != nil {
		allowExtraDNSLabels = *req.AllowExtraDnsLabels
	}

	setupKey, err := h.accountManager.CreateSetupKey(r.Context(), accountID, req.Name, types.SetupKeyType(req.Type), expiresIn,
		req.AutoGroups, req.UsageLimit, userID, ephemeral, allowExtraDNSLabels)
	if err != nil {
		util.WriteError(r.Context(), err, w)
		return
	}

	apiSetupKeys := ToResponseBody(setupKey)
	// for the creation we need to send the plain key
	apiSetupKeys.Key = setupKey.Key

	util.WriteJSONObject(r.Context(), w, apiSetupKeys)
}

// getSetupKey is a GET request to get a SetupKey by ID
func (h *handler) getSetupKey(w http.ResponseWriter, r *http.Request) {
	userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
	if err != nil {
		util.WriteError(r.Context(), err, w)
		return
	}
	accountID, userID := userAuth.AccountId, userAuth.UserId

	vars := mux.Vars(r)
	keyID := vars["keyId"]
	if len(keyID) == 0 {
		util.WriteError(r.Context(), status.NewInvalidKeyIDError(), w)
		return
	}

	key, err := h.accountManager.GetSetupKey(r.Context(), accountID, userID, keyID)
	if err != nil {
		util.WriteError(r.Context(), err, w)
		return
	}

	writeSuccess(r.Context(), w, key)
}

// updateSetupKey is a PUT request to update server.SetupKey
func (h *handler) updateSetupKey(w http.ResponseWriter, r *http.Request) {
	userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
	if err != nil {
		util.WriteError(r.Context(), err, w)
		return
	}

	accountID, userID := userAuth.AccountId, userAuth.UserId
	vars := mux.Vars(r)
	keyID := vars["keyId"]
	if len(keyID) == 0 {
		util.WriteError(r.Context(), status.NewInvalidKeyIDError(), w)
		return
	}

	req := &api.PutApiSetupKeysKeyIdJSONRequestBody{}
	err = json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		util.WriteErrorResponse("couldn't parse JSON request", http.StatusBadRequest, w)
		return
	}

	if req.AutoGroups == nil {
		util.WriteError(r.Context(), status.Errorf(status.InvalidArgument, "setup key AutoGroups field is invalid"), w)
		return
	}

	newKey := &types.SetupKey{}
	newKey.AutoGroups = req.AutoGroups
	newKey.Revoked = req.Revoked
	newKey.Id = keyID

	newKey, err = h.accountManager.SaveSetupKey(r.Context(), accountID, newKey, userID)
	if err != nil {
		util.WriteError(r.Context(), err, w)
		return
	}
	writeSuccess(r.Context(), w, newKey)
}

// getAllSetupKeys is a GET request that returns a list of SetupKey
func (h *handler) getAllSetupKeys(w http.ResponseWriter, r *http.Request) {
	userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
	if err != nil {
		util.WriteError(r.Context(), err, w)
		return
	}

	accountID, userID := userAuth.AccountId, userAuth.UserId
	setupKeys, err := h.accountManager.ListSetupKeys(r.Context(), accountID, userID)
	if err != nil {
		util.WriteError(r.Context(), err, w)
		return
	}

	apiSetupKeys := make([]*api.SetupKey, 0)
	for _, key := range setupKeys {
		apiSetupKeys = append(apiSetupKeys, ToResponseBody(key))
	}

	util.WriteJSONObject(r.Context(), w, apiSetupKeys)
}

func (h *handler) deleteSetupKey(w http.ResponseWriter, r *http.Request) {
	userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
	if err != nil {
		util.WriteError(r.Context(), err, w)
		return
	}

	accountID, userID := userAuth.AccountId, userAuth.UserId
	vars := mux.Vars(r)
	keyID := vars["keyId"]
	if len(keyID) == 0 {
		util.WriteError(r.Context(), status.NewInvalidKeyIDError(), w)
		return
	}

	err = h.accountManager.DeleteSetupKey(r.Context(), accountID, userID, keyID)
	if err != nil {
		util.WriteError(r.Context(), err, w)
		return
	}

	util.WriteJSONObject(r.Context(), w, util.EmptyObject{})
}

func writeSuccess(ctx context.Context, w http.ResponseWriter, key *types.SetupKey) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	err := json.NewEncoder(w).Encode(ToResponseBody(key))
	if err != nil {
		util.WriteError(ctx, err, w)
		return
	}
}

func ToResponseBody(key *types.SetupKey) *api.SetupKey {
	var state string
	switch {
	case key.IsExpired():
		state = "expired"
	case key.IsRevoked():
		state = "revoked"
	case key.IsOverUsed():
		state = "overused"
	default:
		state = "valid"
	}

	return &api.SetupKey{
		Id:                  key.Id,
		Key:                 key.KeySecret,
		Name:                key.Name,
		Expires:             key.GetExpiresAt(),
		Type:                string(key.Type),
		Valid:               key.IsValid(),
		Revoked:             key.Revoked,
		UsedTimes:           key.UsedTimes,
		LastUsed:            key.GetLastUsed(),
		State:               state,
		AutoGroups:          key.AutoGroups,
		UpdatedAt:           key.UpdatedAt,
		UsageLimit:          key.UsageLimit,
		Ephemeral:           key.Ephemeral,
		AllowExtraDnsLabels: key.AllowExtraDNSLabels,
	}
}
