package v1

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

	"github.com/labstack/echo/v4"
	"github.com/pkg/errors"
	"golang.org/x/exp/slices"

	"github.com/usememos/memos/store"
)

type UserSettingKey string

const (
	// UserSettingLocaleKey is the key type for user locale.
	UserSettingLocaleKey UserSettingKey = "locale"
	// UserSettingAppearanceKey is the key type for user appearance.
	UserSettingAppearanceKey UserSettingKey = "appearance"
	// UserSettingMemoVisibilityKey is the key type for user preference memo default visibility.
	UserSettingMemoVisibilityKey UserSettingKey = "memo-visibility"
	// UserSettingTelegramUserIDKey is the key type for telegram UserID of memos user.
	UserSettingTelegramUserIDKey UserSettingKey = "telegram-user-id"
)

// String returns the string format of UserSettingKey type.
func (key UserSettingKey) String() string {
	switch key {
	case UserSettingLocaleKey:
		return "locale"
	case UserSettingAppearanceKey:
		return "appearance"
	case UserSettingMemoVisibilityKey:
		return "memo-visibility"
	case UserSettingTelegramUserIDKey:
		return "telegram-user-id"
	}
	return ""
}

var (
	UserSettingLocaleValue = []string{
		"ar",
		"de",
		"en",
		"es",
		"fr",
		"hi",
		"hr",
		"it",
		"ja",
		"ko",
		"nl",
		"pl",
		"pt-BR",
		"ru",
		"sl",
		"sv",
		"tr",
		"uk",
		"vi",
		"zh-Hans",
		"zh-Hant",
	}
	UserSettingAppearanceValue     = []string{"system", "light", "dark"}
	UserSettingMemoVisibilityValue = []Visibility{Private, Protected, Public}
)

type UserSetting struct {
	UserID int32          `json:"userId"`
	Key    UserSettingKey `json:"key"`
	Value  string         `json:"value"`
}

type UpsertUserSettingRequest struct {
	UserID int32          `json:"-"`
	Key    UserSettingKey `json:"key"`
	Value  string         `json:"value"`
}

func (s *APIV1Service) registerUserSettingRoutes(g *echo.Group) {
	g.POST("/user/setting", s.UpsertUserSetting)
}

// UpsertUserSetting godoc
//
//	@Summary	Upsert user setting
//	@Tags		user-setting
//	@Accept		json
//	@Produce	json
//	@Param		body	body		UpsertUserSettingRequest	true	"Request object."
//	@Success	200		{object}	store.UserSetting			"Created user setting"
//	@Failure	400		{object}	nil							"Malformatted post user setting upsert request | Invalid user setting format"
//	@Failure	401		{object}	nil							"Missing auth session"
//	@Failure	500		{object}	nil							"Failed to upsert user setting"
//	@Router		/api/v1/user/setting [POST]
func (s *APIV1Service) UpsertUserSetting(c echo.Context) error {
	ctx := c.Request().Context()
	userID, ok := c.Get(userIDContextKey).(int32)
	if !ok {
		return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
	}

	userSettingUpsert := &UpsertUserSettingRequest{}
	if err := json.NewDecoder(c.Request().Body).Decode(userSettingUpsert); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user setting upsert request").SetInternal(err)
	}
	if err := userSettingUpsert.Validate(); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "Invalid user setting format").SetInternal(err)
	}

	userSettingUpsert.UserID = userID
	userSetting, err := s.Store.UpsertUserSetting(ctx, &store.UserSetting{
		UserID: userID,
		Key:    userSettingUpsert.Key.String(),
		Value:  userSettingUpsert.Value,
	})
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert user setting").SetInternal(err)
	}

	userSettingMessage := convertUserSettingFromStore(userSetting)
	return c.JSON(http.StatusOK, userSettingMessage)
}

func (upsert UpsertUserSettingRequest) Validate() error {
	if upsert.Key == UserSettingLocaleKey {
		localeValue := "en"
		err := json.Unmarshal([]byte(upsert.Value), &localeValue)
		if err != nil {
			return errors.New("failed to unmarshal user setting locale value")
		}
		if !slices.Contains(UserSettingLocaleValue, localeValue) {
			return errors.New("invalid user setting locale value")
		}
	} else if upsert.Key == UserSettingAppearanceKey {
		appearanceValue := "system"
		err := json.Unmarshal([]byte(upsert.Value), &appearanceValue)
		if err != nil {
			return errors.New("failed to unmarshal user setting appearance value")
		}
		if !slices.Contains(UserSettingAppearanceValue, appearanceValue) {
			return errors.New("invalid user setting appearance value")
		}
	} else if upsert.Key == UserSettingMemoVisibilityKey {
		memoVisibilityValue := Private
		err := json.Unmarshal([]byte(upsert.Value), &memoVisibilityValue)
		if err != nil {
			return errors.New("failed to unmarshal user setting memo visibility value")
		}
		if !slices.Contains(UserSettingMemoVisibilityValue, memoVisibilityValue) {
			return errors.New("invalid user setting memo visibility value")
		}
	} else if upsert.Key == UserSettingTelegramUserIDKey {
		var key string
		err := json.Unmarshal([]byte(upsert.Value), &key)
		if err != nil {
			return errors.New("invalid user setting telegram user id value")
		}
	} else {
		return errors.New("invalid user setting key")
	}

	return nil
}

func convertUserSettingFromStore(userSetting *store.UserSetting) *UserSetting {
	return &UserSetting{
		UserID: userSetting.UserID,
		Key:    UserSettingKey(userSetting.Key),
		Value:  userSetting.Value,
	}
}
