package v1

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

	"github.com/labstack/echo/v4"
	"github.com/usememos/memos/store"
	"golang.org/x/exp/slices"
)

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"
	// UserSettingTelegramUserID 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{
		"de",
		"en",
		"es",
		"fr",
		"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 int            `json:"userId"`
	Key    UserSettingKey `json:"key"`
	Value  string         `json:"value"`
}

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

func (upsert UpsertUserSettingRequest) Validate() error {
	if upsert.Key == UserSettingLocaleKey {
		localeValue := "en"
		err := json.Unmarshal([]byte(upsert.Value), &localeValue)
		if err != nil {
			return fmt.Errorf("failed to unmarshal user setting locale value")
		}
		if !slices.Contains(UserSettingLocaleValue, localeValue) {
			return fmt.Errorf("invalid user setting locale value")
		}
	} else if upsert.Key == UserSettingAppearanceKey {
		appearanceValue := "system"
		err := json.Unmarshal([]byte(upsert.Value), &appearanceValue)
		if err != nil {
			return fmt.Errorf("failed to unmarshal user setting appearance value")
		}
		if !slices.Contains(UserSettingAppearanceValue, appearanceValue) {
			return fmt.Errorf("invalid user setting appearance value")
		}
	} else if upsert.Key == UserSettingMemoVisibilityKey {
		memoVisibilityValue := Private
		err := json.Unmarshal([]byte(upsert.Value), &memoVisibilityValue)
		if err != nil {
			return fmt.Errorf("failed to unmarshal user setting memo visibility value")
		}
		if !slices.Contains(UserSettingMemoVisibilityValue, memoVisibilityValue) {
			return fmt.Errorf("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 fmt.Errorf("invalid user setting telegram user id value")
		}
	} else {
		return fmt.Errorf("invalid user setting key")
	}

	return nil
}

func (s *APIV1Service) registerUserSettingRoutes(g *echo.Group) {
	g.POST("/user/setting", func(c echo.Context) error {
		ctx := c.Request().Context()
		userID, ok := c.Get(getUserIDContextKey()).(int)
		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 convertUserSettingFromStore(userSetting *store.UserSetting) *UserSetting {
	return &UserSetting{
		UserID: userSetting.UserID,
		Key:    UserSettingKey(userSetting.Key),
		Value:  userSetting.Value,
	}
}