package main

import (
	"encoding/json"
	"errors"
	"net/http"
	"runtime/debug"
	"strings"

	"github.com/gorilla/mux"
	"github.com/larkox/mattermost-plugin-badges/badgesmodel"
	"github.com/mattermost/mattermost-server/v5/model"
)

// HTTPHandlerFuncWithUser is http.HandleFunc but userID is already exported
type HTTPHandlerFuncWithUser func(w http.ResponseWriter, r *http.Request, userID string)

// ResponseType indicates type of response returned by api
type ResponseType string

const (
	// ResponseTypeJSON indicates that response type is json
	ResponseTypeJSON ResponseType = "JSON_RESPONSE"
	// ResponseTypePlain indicates that response type is text plain
	ResponseTypePlain ResponseType = "TEXT_RESPONSE"
	// ResponseTypeDialog indicates that response type is a dialog response
	ResponseTypeDialog ResponseType = "DIALOG"
)

type APIErrorResponse struct {
	ID         string `json:"id"`
	Message    string `json:"message"`
	StatusCode int    `json:"status_code"`
}

type CreateBadgeRequest struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Image       string `json:"image"`
	Type        string `json:"type"`
	Multiple    bool   `json:"multiple"`
	ChannelID   string `json:"channel_id"`
}

type UpdateBadgeRequest struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
	Image       string `json:"image"`
	Type        string `json:"type"`
	Multiple    bool   `json:"multiple"`
}

type CreateTypeRequest struct {
	Name               string `json:"name"`
	EveryoneCanCreate  bool   `json:"everyone_can_create"`
	EveryoneCanGrant   bool   `json:"everyone_can_grant"`
	AllowlistCanCreate string `json:"allowlist_can_create"`
	AllowlistCanGrant  string `json:"allowlist_can_grant"`
	ChannelID          string `json:"channel_id"`
}

type UpdateTypeRequest struct {
	ID                 string `json:"id"`
	Name               string `json:"name"`
	EveryoneCanCreate  bool   `json:"everyone_can_create"`
	EveryoneCanGrant   bool   `json:"everyone_can_grant"`
	AllowlistCanCreate string `json:"allowlist_can_create"`
	AllowlistCanGrant  string `json:"allowlist_can_grant"`
}

type GrantBadgeAPIRequest struct {
	BadgeID    string `json:"badge_id"`
	UserID     string `json:"user_id"`
	Reason     string `json:"reason"`
	NotifyHere bool   `json:"notify_here"`
	ChannelID  string `json:"channel_id"`
}

type SubscriptionAPIRequest struct {
	TypeID    string `json:"type_id"`
	ChannelID string `json:"channel_id"`
}

type RevokeOwnershipRequest struct {
	BadgeID string `json:"badge_id"`
	UserID  string `json:"user_id"`
	Time    string `json:"time"`
}

type TypeWithBadgeCount struct {
	*badgesmodel.BadgeTypeDefinition
	BadgeCount         int    `json:"badge_count"`
	CreatedByUsername   string `json:"created_by_username"`
	AllowlistCanCreate string `json:"allowlist_can_create"`
	AllowlistCanGrant  string `json:"allowlist_can_grant"`
}

type GetTypesResponse struct {
	Types         []TypeWithBadgeCount `json:"types"`
	CanCreateType bool                 `json:"can_create_type"`
	CanEditType   bool                 `json:"can_edit_type"`
}

func (p *Plugin) initializeAPI() {
	p.router = mux.NewRouter()
	p.router.Use(p.withRecovery)

	apiRouter := p.router.PathPrefix("/api/v1").Subrouter()
	pluginAPIRouter := p.router.PathPrefix(badgesmodel.PluginAPIPath).Subrouter()
	autocompleteRouter := p.router.PathPrefix(AutocompletePath).Subrouter()
	dialogRouter := p.router.PathPrefix(DialogPath).Subrouter()

	apiRouter.HandleFunc("/getUserBadges/{userID}", p.extractUserMiddleWare(p.getUserBadges, ResponseTypeJSON)).Methods(http.MethodGet)
	apiRouter.HandleFunc("/getBadgeDetails/{badgeID}", p.extractUserMiddleWare(p.getBadgeDetails, ResponseTypeJSON)).Methods(http.MethodGet)
	apiRouter.HandleFunc("/getAllBadges", p.extractUserMiddleWare(p.getAllBadges, ResponseTypeJSON)).Methods(http.MethodGet)
	apiRouter.HandleFunc("/getTypes", p.extractUserMiddleWare(p.getTypes, ResponseTypeJSON)).Methods(http.MethodGet)
	apiRouter.HandleFunc("/createBadge", p.extractUserMiddleWare(p.apiCreateBadge, ResponseTypeJSON)).Methods(http.MethodPost)
	apiRouter.HandleFunc("/createType", p.extractUserMiddleWare(p.apiCreateType, ResponseTypeJSON)).Methods(http.MethodPost)
	apiRouter.HandleFunc("/updateBadge", p.extractUserMiddleWare(p.apiUpdateBadge, ResponseTypeJSON)).Methods(http.MethodPut)
	apiRouter.HandleFunc("/updateType", p.extractUserMiddleWare(p.apiUpdateType, ResponseTypeJSON)).Methods(http.MethodPut)
	apiRouter.HandleFunc("/deleteBadge/{badgeID}", p.extractUserMiddleWare(p.apiDeleteBadge, ResponseTypeJSON)).Methods(http.MethodDelete)
	apiRouter.HandleFunc("/deleteType/{typeID}", p.extractUserMiddleWare(p.apiDeleteType, ResponseTypeJSON)).Methods(http.MethodDelete)
	apiRouter.HandleFunc("/grantBadge", p.extractUserMiddleWare(p.apiGrantBadge, ResponseTypeJSON)).Methods(http.MethodPost)
	apiRouter.HandleFunc("/revokeOwnership", p.extractUserMiddleWare(p.apiRevokeOwnership, ResponseTypeJSON)).Methods(http.MethodPost)
	apiRouter.HandleFunc("/getChannelSubscriptions/{channelID}", p.extractUserMiddleWare(p.apiGetChannelSubscriptions, ResponseTypeJSON)).Methods(http.MethodGet)
	apiRouter.HandleFunc("/createSubscription", p.extractUserMiddleWare(p.apiCreateSubscription, ResponseTypeJSON)).Methods(http.MethodPost)
	apiRouter.HandleFunc("/deleteSubscription", p.extractUserMiddleWare(p.apiDeleteSubscription, ResponseTypeJSON)).Methods(http.MethodPost)

	pluginAPIRouter.HandleFunc(badgesmodel.PluginAPIPathEnsure, checkPluginRequest(p.ensureBadges)).Methods(http.MethodPost)
	pluginAPIRouter.HandleFunc(badgesmodel.PluginAPIPathGrant, checkPluginRequest(p.grantBadge)).Methods(http.MethodPost)

	autocompleteRouter.HandleFunc(AutocompletePathBadgeSuggestions, p.extractUserMiddleWare(p.getBadgeSuggestions, ResponseTypeJSON)).Methods(http.MethodGet)
	autocompleteRouter.HandleFunc(AutocompletePathEditBadgeSuggestions, p.extractUserMiddleWare(p.getEditBadgeSuggestions, ResponseTypeJSON)).Methods(http.MethodGet)
	autocompleteRouter.HandleFunc(AutocompletePathTypeSuggestions, p.extractUserMiddleWare(p.getBadgeTypeSuggestions, ResponseTypeJSON)).Methods(http.MethodGet)
	autocompleteRouter.HandleFunc(AutocompletePathEditTypeSuggestions, p.extractUserMiddleWare(p.getEditBadgeTypeSuggestions, ResponseTypeJSON)).Methods(http.MethodGet)

	dialogRouter.HandleFunc(DialogPathCreateBadge, p.extractUserMiddleWare(p.dialogCreateBadge, ResponseTypeDialog)).Methods(http.MethodPost)
	dialogRouter.HandleFunc(DialogPathCreateType, p.extractUserMiddleWare(p.dialogCreateType, ResponseTypeDialog)).Methods(http.MethodPost)
	dialogRouter.HandleFunc(DialogPathGrant, p.extractUserMiddleWare(p.dialogGrant, ResponseTypeDialog)).Methods(http.MethodPost)
	dialogRouter.HandleFunc(DialogPathSelectBadge, p.extractUserMiddleWare(p.dialogSelectBadge, ResponseTypeDialog)).Methods(http.MethodPost)
	dialogRouter.HandleFunc(DialogPathSelectType, p.extractUserMiddleWare(p.dialogSelectType, ResponseTypeDialog)).Methods(http.MethodPost)
	dialogRouter.HandleFunc(DialogPathEditBadge, p.extractUserMiddleWare(p.dialogEditBadge, ResponseTypeDialog)).Methods(http.MethodPost)
	dialogRouter.HandleFunc(DialogPathEditType, p.extractUserMiddleWare(p.dialogEditType, ResponseTypeDialog)).Methods(http.MethodPost)
	dialogRouter.HandleFunc(DialogPathCreateSubscription, p.extractUserMiddleWare(p.dialogCreateSubscription, ResponseTypeDialog)).Methods(http.MethodPost)
	dialogRouter.HandleFunc(DialogPathDeleteSubscription, p.extractUserMiddleWare(p.dialogDeleteSubscription, ResponseTypeDialog)).Methods(http.MethodPost)

	p.router.PathPrefix("/").HandlerFunc(p.defaultHandler)
}

func (p *Plugin) defaultHandler(w http.ResponseWriter, r *http.Request) {
	p.mm.Log.Debug("Unexpected call", "url", r.URL)
	w.WriteHeader(http.StatusNotFound)
}

func dialogError(w http.ResponseWriter, text string, errors map[string]string) {
	resp := &model.SubmitDialogResponse{
		Error:  "Error: " + text,
		Errors: errors,
	}
	_, _ = w.Write(resp.ToJson())
}

func dialogOK(w http.ResponseWriter) {
	resp := &model.SubmitDialogResponse{}
	_, _ = w.Write(resp.ToJson())
}

func dialogKeepOpen(w http.ResponseWriter) {
	resp := &model.SubmitDialogResponse{
		Error: "_",
	}
	_, _ = w.Write(resp.ToJson())
}

func (p *Plugin) getTypes(w http.ResponseWriter, r *http.Request, userID string) {
	u, err := p.mm.User.Get(userID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	types, err := p.filterCreateBadgeTypes(u)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_types", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	badges, err := p.store.GetRawBadges()
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_badges", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	badgeCountByType := map[badgesmodel.BadgeType]int{}
	for _, badge := range badges {
		badgeCountByType[badge.Type]++
	}

	result := make([]TypeWithBadgeCount, len(types))
	for i, t := range types {
		createdByUsername := t.CreatedBy
		if creator, appErr := p.mm.User.Get(t.CreatedBy); appErr == nil {
			createdByUsername = creator.Username
		}
		result[i] = TypeWithBadgeCount{
			BadgeTypeDefinition: t,
			BadgeCount:          badgeCountByType[t.ID],
			CreatedByUsername:    createdByUsername,
			AllowlistCanCreate:  p.resolveUserIDList(t.CanCreate.AllowList),
			AllowlistCanGrant:   p.resolveUserIDList(t.CanGrant.AllowList),
		}
	}

	resp := GetTypesResponse{
		Types:         result,
		CanCreateType: canCreateType(u, p.badgeAdminUserIDs, false),
		CanEditType:   p.badgeAdminUserIDs[u.Id] || u.IsSystemAdmin(),
	}

	b, _ := json.Marshal(resp)
	_, _ = w.Write(b)
}

func (p *Plugin) apiCreateBadge(w http.ResponseWriter, r *http.Request, userID string) {
	var req CreateBadgeRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
		})
		return
	}

	user, err := p.mm.User.Get(userID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	req.Name = strings.TrimSpace(req.Name)
	req.Description = strings.TrimSpace(req.Description)
	req.Image = strings.TrimSpace(req.Image)

	if req.Name == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_name", Message: "Name is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	if length := len(req.Image); length > 1 && req.Image[0] == ':' && req.Image[length-1] == ':' {
		req.Image = req.Image[1 : length-1]
	}
	if req.Image == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_image", Message: "Emoji is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	t, err := p.store.GetType(badgesmodel.BadgeType(req.Type))
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "type_not_found", Message: "Badge type not found", StatusCode: http.StatusBadRequest,
		})
		return
	}

	if !canCreateBadge(user, p.badgeAdminUserIDs, t) {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "no_permission", Message: "No permission to create badge of this type", StatusCode: http.StatusForbidden,
		})
		return
	}

	toCreate := &badgesmodel.Badge{
		Name:        req.Name,
		Description: req.Description,
		Image:       req.Image,
		ImageType:   badgesmodel.ImageTypeEmoji,
		Multiple:    req.Multiple,
		Type:        badgesmodel.BadgeType(req.Type),
		CreatedBy:   userID,
	}

	created, err := p.store.AddBadge(toCreate)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_create_badge", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if req.ChannelID != "" {
		T := p.getT(user.Locale)
		p.mm.Post.SendEphemeralPost(userID, &model.Post{
			UserId:    p.BotUserID,
			ChannelId: req.ChannelID,
			Message:   T("badges.api.badge_created", "Значок `%s` создан.", toCreate.Name),
		})
	}

	b, _ := json.Marshal(created)
	w.WriteHeader(http.StatusCreated)
	_, _ = w.Write(b)
}

func (p *Plugin) apiCreateType(w http.ResponseWriter, r *http.Request, userID string) {
	var req CreateTypeRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
		})
		return
	}

	user, err := p.mm.User.Get(userID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if !canCreateType(user, p.badgeAdminUserIDs, false) {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "no_permission", Message: "No permission to create type", StatusCode: http.StatusForbidden,
		})
		return
	}

	req.Name = strings.TrimSpace(req.Name)
	if req.Name == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_name", Message: "Type name is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	toCreate := &badgesmodel.BadgeTypeDefinition{
		Name:      req.Name,
		CreatedBy: userID,
	}
	toCreate.CanCreate.Everyone = req.EveryoneCanCreate
	toCreate.CanGrant.Everyone = req.EveryoneCanGrant

	if req.AllowlistCanCreate != "" {
		allowList, aErr := p.resolveUsernameList(req.AllowlistCanCreate)
		if aErr != nil {
			p.writeAPIError(w, &APIErrorResponse{
				ID: "invalid_request", Message: aErr.Error(), StatusCode: http.StatusBadRequest,
			})
			return
		}
		toCreate.CanCreate.AllowList = allowList
	}

	if req.AllowlistCanGrant != "" {
		allowList, aErr := p.resolveUsernameList(req.AllowlistCanGrant)
		if aErr != nil {
			p.writeAPIError(w, &APIErrorResponse{
				ID: "invalid_request", Message: aErr.Error(), StatusCode: http.StatusBadRequest,
			})
			return
		}
		toCreate.CanGrant.AllowList = allowList
	}

	created, err := p.store.AddType(toCreate)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_create_type", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if req.ChannelID != "" {
		T := p.getT(user.Locale)
		p.mm.Post.SendEphemeralPost(userID, &model.Post{
			UserId:    p.BotUserID,
			ChannelId: req.ChannelID,
			Message:   T("badges.api.type_created", "Тип `%s` создан.", toCreate.Name),
		})
	}

	b, _ := json.Marshal(created)
	w.WriteHeader(http.StatusCreated)
	_, _ = w.Write(b)
}

func (p *Plugin) apiUpdateBadge(w http.ResponseWriter, r *http.Request, userID string) {
	var req UpdateBadgeRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
		})
		return
	}

	user, err := p.mm.User.Get(userID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	badge, err := p.store.GetBadge(badgesmodel.BadgeID(req.ID))
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "badge_not_found", Message: "Badge not found", StatusCode: http.StatusNotFound,
		})
		return
	}

	badgeType, err := p.store.GetType(badge.Type)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "type_not_found", Message: "Badge type not found", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if !canEditBadge(user, p.badgeAdminUserIDs, badge, badgeType) {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "no_permission", Message: "No permission to edit this badge", StatusCode: http.StatusForbidden,
		})
		return
	}

	req.Name = strings.TrimSpace(req.Name)
	req.Description = strings.TrimSpace(req.Description)
	req.Image = strings.TrimSpace(req.Image)

	if req.Name == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_name", Message: "Name is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	if length := len(req.Image); length > 1 && req.Image[0] == ':' && req.Image[length-1] == ':' {
		req.Image = req.Image[1 : length-1]
	}
	if req.Image == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_image", Message: "Emoji is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	badge.Name = req.Name
	badge.Description = req.Description
	badge.Image = req.Image
	badge.Type = badgesmodel.BadgeType(req.Type)
	badge.Multiple = req.Multiple

	if err := p.store.UpdateBadge(badge); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_update_badge", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	b, _ := json.Marshal(badge)
	_, _ = w.Write(b)
}

func (p *Plugin) apiUpdateType(w http.ResponseWriter, r *http.Request, userID string) {
	var req UpdateTypeRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
		})
		return
	}

	user, err := p.mm.User.Get(userID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	originalType, err := p.store.GetType(badgesmodel.BadgeType(req.ID))
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "type_not_found", Message: "Badge type not found", StatusCode: http.StatusNotFound,
		})
		return
	}

	if !canEditType(user, p.badgeAdminUserIDs, originalType) {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "no_permission", Message: "No permission to edit this type", StatusCode: http.StatusForbidden,
		})
		return
	}

	req.Name = strings.TrimSpace(req.Name)
	if req.Name == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_name", Message: "Name is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	originalType.Name = req.Name
	originalType.CanCreate.Everyone = req.EveryoneCanCreate
	originalType.CanGrant.Everyone = req.EveryoneCanGrant

	createAllowList, aErr := p.resolveUsernameList(req.AllowlistCanCreate)
	if aErr != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: aErr.Error(), StatusCode: http.StatusBadRequest,
		})
		return
	}
	originalType.CanCreate.AllowList = createAllowList

	grantAllowList, aErr := p.resolveUsernameList(req.AllowlistCanGrant)
	if aErr != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: aErr.Error(), StatusCode: http.StatusBadRequest,
		})
		return
	}
	originalType.CanGrant.AllowList = grantAllowList

	if err := p.store.UpdateType(originalType); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_update_type", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	b, _ := json.Marshal(originalType)
	_, _ = w.Write(b)
}

func (p *Plugin) apiDeleteBadge(w http.ResponseWriter, r *http.Request, userID string) {
	badgeID, ok := mux.Vars(r)["badgeID"]
	if !ok {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "missing_badge_id", Message: "Missing badge ID", StatusCode: http.StatusBadRequest,
		})
		return
	}

	user, err := p.mm.User.Get(userID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	badge, err := p.store.GetBadge(badgesmodel.BadgeID(badgeID))
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "badge_not_found", Message: "Badge not found", StatusCode: http.StatusNotFound,
		})
		return
	}

	badgeType, err := p.store.GetType(badge.Type)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "type_not_found", Message: "Badge type not found", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if !canEditBadge(user, p.badgeAdminUserIDs, badge, badgeType) {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "no_permission", Message: "No permission to delete this badge", StatusCode: http.StatusForbidden,
		})
		return
	}

	if err := p.store.DeleteBadge(badgesmodel.BadgeID(badgeID)); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_delete_badge", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	_, _ = w.Write([]byte(`{"success": true}`))
}

func (p *Plugin) apiDeleteType(w http.ResponseWriter, r *http.Request, userID string) {
	typeID, ok := mux.Vars(r)["typeID"]
	if !ok {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "missing_type_id", Message: "Missing type ID", StatusCode: http.StatusBadRequest,
		})
		return
	}

	user, err := p.mm.User.Get(userID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	t, err := p.store.GetType(badgesmodel.BadgeType(typeID))
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "type_not_found", Message: "Type not found", StatusCode: http.StatusNotFound,
		})
		return
	}

	if t.IsDefault {
		T := p.getT("ru")
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_delete_default_type", Message: T("badges.api.cannot_delete_default_type", "Нельзя удалить тип по умолчанию"), StatusCode: http.StatusForbidden,
		})
		return
	}

	if !canEditType(user, p.badgeAdminUserIDs, t) {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "no_permission", Message: "No permission to delete this type", StatusCode: http.StatusForbidden,
		})
		return
	}

	if err := p.store.DeleteType(badgesmodel.BadgeType(typeID)); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_delete_type", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	_, _ = w.Write([]byte(`{"success": true}`))
}

func (p *Plugin) dialogCreateBadge(w http.ResponseWriter, r *http.Request, userID string) {
	req := model.SubmitDialogRequestFromJson(r.Body)
	if req == nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
		return
	}

	user, err := p.mm.User.Get(userID)
	if err != nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.cannot_get_user", "Не удалось найти пользователя"), nil)
		return
	}
	T := p.getT(user.Locale)

	toCreate := &badgesmodel.Badge{}
	toCreate.CreatedBy = userID
	toCreate.ImageType = badgesmodel.ImageTypeEmoji
	name, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeName)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}
	toCreate.Name = name

	description, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeDescription)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}
	toCreate.Description = description

	image, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeImage)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}

	if length := len(image); length > 1 && image[0] == ':' && image[length-1] == ':' {
		image = image[1 : len(image)-1]
	}
	if image == "" {
		dialogError(w, T("badges.api.invalid_field", "Некорректное поле"), map[string]string{"image": T("badges.api.empty_emoji", "Пустой эмодзи")})
		return
	}
	toCreate.Image = image

	badgeTypeStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeType)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}

	toCreate.Type = badgesmodel.BadgeType(badgeTypeStr)
	toCreate.Multiple = getDialogSubmissionBoolField(req, DialogFieldBadgeMultiple)

	t, err := p.store.GetType(badgesmodel.BadgeType(badgeTypeStr))
	if err != nil {
		dialogError(w, T("badges.api.type_not_exist", "Этот тип не существует"), nil)
		return
	}

	if !canCreateBadge(user, p.badgeAdminUserIDs, t) {
		dialogError(w, T("badges.api.no_permissions_create_badge", "У вас нет прав на создание этого значка"), nil)
		return
	}

	_, err = p.store.AddBadge(toCreate)
	if err != nil {
		dialogError(w, err.Error(), nil)
		return
	}

	p.mm.Post.SendEphemeralPost(userID, &model.Post{
		UserId:    p.BotUserID,
		ChannelId: req.ChannelId,
		Message:   T("badges.api.badge_created", "Значок `%s` создан.", toCreate.Name),
	})

	dialogOK(w)
}

func (p *Plugin) dialogCreateType(w http.ResponseWriter, r *http.Request, userID string) {
	req := model.SubmitDialogRequestFromJson(r.Body)
	if req == nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
		return
	}

	u, err := p.mm.User.Get(userID)
	if err != nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.cannot_get_user", "Не удалось найти пользователя"), nil)
		return
	}
	T := p.getT(u.Locale)

	if !canCreateType(u, p.badgeAdminUserIDs, false) {
		dialogError(w, T("badges.api.no_permissions_create_type", "У вас нет прав на создание типа"), nil)
		return
	}

	toCreate := &badgesmodel.BadgeTypeDefinition{}
	toCreate.CreatedBy = userID
	toCreate.CanCreate.Everyone = getDialogSubmissionBoolField(req, DialogFieldTypeEveryoneCanCreate)
	toCreate.CanGrant.Everyone = getDialogSubmissionBoolField(req, DialogFieldTypeEveryoneCanGrant)
	name, errText, errors := getDialogSubmissionTextField(req, DialogFieldTypeName)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}
	toCreate.Name = name

	createAllowList, _ := req.Submission[DialogFieldTypeAllowlistCanCreate].(string)
	grantAllowList, _ := req.Submission[DialogFieldTypeAllowlistCanGrant].(string)

	if createAllowList != "" {
		toCreate.CanCreate.AllowList = map[string]bool{}
		usernames := strings.Split(createAllowList, ",")
		for _, username := range usernames {
			username = strings.TrimSpace(username)
			if username == "" {
				continue
			}
			foundUser, userErr := p.mm.User.GetByUsername(username)
			if userErr != nil {
				dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), map[string]string{DialogFieldTypeAllowlistCanCreate: T("badges.api.error_getting_user", "Ошибка получения пользователя %s: %v", username, userErr)})
				return
			}
			toCreate.CanCreate.AllowList[foundUser.Id] = true
		}
	}

	if grantAllowList != "" {
		toCreate.CanGrant.AllowList = map[string]bool{}
		usernames := strings.Split(grantAllowList, ",")
		for _, username := range usernames {
			username = strings.TrimSpace(username)
			if username == "" {
				continue
			}
			foundUser, userErr := p.mm.User.GetByUsername(username)
			if userErr != nil {
				dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), map[string]string{DialogFieldTypeAllowlistCanGrant: T("badges.api.error_getting_user", "Ошибка получения пользователя %s: %v", username, userErr)})
				return
			}
			toCreate.CanGrant.AllowList[foundUser.Id] = true
		}
	}

	_, err = p.store.AddType(toCreate)
	if err != nil {
		dialogError(w, err.Error(), nil)
		return
	}

	p.mm.Post.SendEphemeralPost(userID, &model.Post{
		UserId:    p.BotUserID,
		ChannelId: req.ChannelId,
		Message:   T("badges.api.type_created", "Тип `%s` создан.", toCreate.Name),
	})

	dialogOK(w)
}

// This is not working on the current webapp architecture. A similar approach should be handled using Apps.
func (p *Plugin) dialogSelectType(w http.ResponseWriter, r *http.Request, userID string) {
	req := model.SubmitDialogRequestFromJson(r.Body)
	if req == nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
		return
	}

	badgeTypeStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeType)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}

	t, err := p.store.GetType(badgesmodel.BadgeType(badgeTypeStr))
	if err != nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.cannot_get_type", "Не удалось получить тип"), map[string]string{DialogFieldBadgeType: T("badges.api.cannot_get_type", "Не удалось получить тип")})
		return
	}

	u, err := p.mm.User.Get(userID)
	if err != nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), nil)
		return
	}
	T := p.getT(u.Locale)

	if !canEditType(u, p.badgeAdminUserIDs, t) {
		dialogError(w, T("badges.api.cannot_edit_type", "Вы не можете редактировать этот тип"), nil)
		return
	}

	_, _ = p.mm.SlashCommand.Execute(&model.CommandArgs{
		UserId:    userID,
		ChannelId: req.ChannelId,
		TeamId:    req.TeamId,
		Command:   "/badges edit type --type " + badgeTypeStr,
	})

	dialogKeepOpen(w)
}

func (p *Plugin) dialogEditType(w http.ResponseWriter, r *http.Request, userID string) {
	req := model.SubmitDialogRequestFromJson(r.Body)
	if req == nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
		return
	}

	u, err := p.mm.User.Get(userID)
	if err != nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), nil)
		return
	}
	T := p.getT(u.Locale)

	originalTypeID := req.State

	originalType, err := p.store.GetType(badgesmodel.BadgeType(originalTypeID))
	if err != nil {
		dialogError(w, T("badges.api.could_not_get_type", "Не удалось получить тип"), nil)
		return
	}

	if !canEditType(u, p.badgeAdminUserIDs, originalType) {
		dialogError(w, T("badges.api.no_permissions_edit_type", "У вас нет прав на редактирование этого типа"), nil)
		return
	}

	if getDialogSubmissionBoolField(req, DialogFieldTypeDelete) {
		if originalType.IsDefault {
			dialogError(w, T("badges.api.cannot_delete_default_type", "Нельзя удалить тип по умолчанию"), nil)
			return
		}
		err = p.store.DeleteType(badgesmodel.BadgeType(originalTypeID))
		if err != nil {
			dialogError(w, err.Error(), nil)
		}
		return
	}
	originalType.CanCreate.Everyone = getDialogSubmissionBoolField(req, DialogFieldTypeEveryoneCanCreate)
	originalType.CanGrant.Everyone = getDialogSubmissionBoolField(req, DialogFieldTypeEveryoneCanGrant)
	name, errText, errors := getDialogSubmissionTextField(req, DialogFieldTypeName)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}
	originalType.Name = name

	createAllowList, _ := req.Submission[DialogFieldTypeAllowlistCanCreate].(string)
	grantAllowList, _ := req.Submission[DialogFieldTypeAllowlistCanGrant].(string)

	originalType.CanCreate.AllowList = map[string]bool{}
	usernames := strings.Split(createAllowList, ",")
	for _, username := range usernames {
		username = strings.TrimSpace(username)
		if username == "" {
			continue
		}
		var allowedUser *model.User
		allowedUser, err = p.mm.User.GetByUsername(username)
		if err != nil {
			dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), map[string]string{DialogFieldTypeAllowlistCanCreate: T("badges.api.error_getting_user", "Ошибка получения пользователя %s: %v", username, err)})
			return
		}
		originalType.CanCreate.AllowList[allowedUser.Id] = true
	}

	originalType.CanGrant.AllowList = map[string]bool{}
	usernames = strings.Split(grantAllowList, ",")
	for _, username := range usernames {
		username = strings.TrimSpace(username)
		if username == "" {
			continue
		}
		var allowedUser *model.User
		allowedUser, err = p.mm.User.GetByUsername(username)
		if err != nil {
			dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), map[string]string{DialogFieldTypeAllowlistCanGrant: T("badges.api.error_getting_user", "Ошибка получения пользователя %s: %v", username, err)})
			return
		}
		originalType.CanGrant.AllowList[allowedUser.Id] = true
	}

	err = p.store.UpdateType(originalType)
	if err != nil {
		dialogError(w, err.Error(), nil)
		return
	}

	p.mm.Post.SendEphemeralPost(userID, &model.Post{
		UserId:    p.BotUserID,
		ChannelId: req.ChannelId,
		Message:   T("badges.api.type_updated", "Тип `%s` обновлён.", originalType.Name),
	})

	dialogOK(w)
}

// This is not working on the current webapp architecture. A similar approach should be handled using Apps.
func (p *Plugin) dialogSelectBadge(w http.ResponseWriter, r *http.Request, userID string) {
	req := model.SubmitDialogRequestFromJson(r.Body)
	if req == nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
		return
	}

	badgeIDStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadge)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}

	b, err := p.store.GetBadge(badgesmodel.BadgeID(badgeIDStr))
	if err != nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.cannot_get_badge", "Не удалось получить значок"), map[string]string{DialogFieldBadge: T("badges.api.cannot_get_badge", "Не удалось получить значок")})
		return
	}

	u, err := p.mm.User.Get(userID)
	if err != nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), nil)
		return
	}
	T := p.getT(u.Locale)

	bt, err := p.store.GetType(b.Type)
	if err != nil {
		dialogError(w, T("badges.api.cannot_get_type", "Не удалось получить тип значка"), nil)
		return
	}

	if !canEditBadge(u, p.badgeAdminUserIDs, b, bt) {
		dialogError(w, T("badges.api.cannot_edit_badge", "Вы не можете редактировать этот значок"), nil)
		return
	}

	_, _ = p.mm.SlashCommand.Execute(&model.CommandArgs{
		UserId:    userID,
		ChannelId: req.ChannelId,
		TeamId:    req.TeamId,
		Command:   "/badges edit badge --id " + badgeIDStr,
	})

	dialogKeepOpen(w)
}

func (p *Plugin) dialogEditBadge(w http.ResponseWriter, r *http.Request, userID string) {
	req := model.SubmitDialogRequestFromJson(r.Body)
	if req == nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
		return
	}

	u, err := p.mm.User.Get(userID)
	if err != nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.cannot_find_user", "Не удалось найти пользователя"), nil)
		return
	}
	T := p.getT(u.Locale)

	originalBadgeID := req.State

	originalBadge, err := p.store.GetBadge(badgesmodel.BadgeID(originalBadgeID))
	if err != nil {
		dialogError(w, T("badges.api.could_not_get_badge", "Не удалось получить значок"), nil)
		return
	}

	originalBadgeType, err := p.store.GetType(originalBadge.Type)
	if err != nil {
		dialogError(w, T("badges.api.cannot_get_type", "Не удалось получить тип значка"), nil)
		return
	}

	if !canEditBadge(u, p.badgeAdminUserIDs, originalBadge, originalBadgeType) {
		dialogError(w, T("badges.api.no_permissions_edit_badge", "У вас нет прав на редактирование этого значка"), nil)
		return
	}

	if getDialogSubmissionBoolField(req, DialogFieldBadgeDelete) {
		err = p.store.DeleteBadge(badgesmodel.BadgeID(originalBadgeID))
		if err != nil {
			dialogError(w, err.Error(), nil)
			return
		}
		return
	}
	name, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeName)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}
	originalBadge.Name = name

	description, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeDescription)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}
	originalBadge.Description = description

	image, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeImage)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}

	if length := len(image); length > 1 && image[0] == ':' && image[length-1] == ':' {
		image = image[1 : len(image)-1]
	}
	if image == "" {
		dialogError(w, T("badges.api.invalid_field", "Некорректное поле"), map[string]string{"image": T("badges.api.empty_emoji", "Пустой эмодзи")})
		return
	}
	originalBadge.Image = image

	badgeTypeStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeType)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}

	originalBadge.Type = badgesmodel.BadgeType(badgeTypeStr)

	originalBadge.Multiple = getDialogSubmissionBoolField(req, DialogFieldBadgeMultiple)

	err = p.store.UpdateBadge(originalBadge)
	if err != nil {
		dialogError(w, err.Error(), nil)
		return
	}

	p.mm.Post.SendEphemeralPost(userID, &model.Post{
		UserId:    p.BotUserID,
		ChannelId: req.ChannelId,
		Message:   T("badges.api.badge_updated", "Значок `%s` обновлён.", originalBadge.Name),
	})

	dialogOK(w)
}

func (p *Plugin) dialogGrant(w http.ResponseWriter, r *http.Request, userID string) {
	req := model.SubmitDialogRequestFromJson(r.Body)
	if req == nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
		return
	}

	badgeIDStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadge)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}

	notifyHere := getDialogSubmissionBoolField(req, DialogFieldNotifyHere)

	badge, err := p.store.GetBadge(badgesmodel.BadgeID(badgeIDStr))
	if err != nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.badge_not_found", "Значок не найден"), nil)
		return
	}

	granter, err := p.mm.User.Get(userID)
	if err != nil {
		dialogError(w, err.Error(), nil)
		return
	}
	T := p.getT(granter.Locale)

	badgeType, err := p.store.GetType(badge.Type)
	if err != nil {
		dialogError(w, err.Error(), nil)
		return
	}

	if !canGrantBadge(granter, p.badgeAdminUserIDs, badge, badgeType) {
		dialogError(w, T("badges.api.no_permissions_grant", "У вас нет прав на выдачу этого значка"), nil)
		return
	}

	grantToID := req.State
	if grantToID == "" {
		grantToID, errText, errors = getDialogSubmissionTextField(req, DialogFieldUser)
		if errors != nil {
			dialogError(w, errText, errors)
			return
		}
	}

	grantToUser, err := p.mm.User.Get(grantToID)
	if err != nil {
		dialogError(w, T("badges.api.user_not_found", "Пользователь не найден"), nil)
		return
	}

	reason, _ := req.Submission[DialogFieldGrantReason].(string)

	shouldNotify, err := p.store.GrantBadge(badgesmodel.BadgeID(badgeIDStr), grantToID, userID, reason)
	if err == errAlreadyOwned {
		dialogError(w, T("badges.error.already_owned", "Это достижение уже выдано этому пользователю"), nil)
		return
	}
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "cannot grant badge",
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if shouldNotify {
		p.notifyGrant(badgesmodel.BadgeID(badgeIDStr), userID, grantToUser, notifyHere, req.ChannelId, reason)
	}

	p.mm.Post.SendEphemeralPost(userID, &model.Post{
		UserId:    p.BotUserID,
		ChannelId: req.ChannelId,
		Message:   T("badges.api.badge_granted", "Значок `%s` выдан @%s.", badge.Name, grantToUser.Username),
	})

	dialogOK(w)
}

func (p *Plugin) dialogCreateSubscription(w http.ResponseWriter, r *http.Request, userID string) {
	req := model.SubmitDialogRequestFromJson(r.Body)
	if req == nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
		return
	}

	u, err := p.mm.User.Get(userID)
	if err != nil {
		dialogError(w, err.Error(), nil)
		return
	}
	T := p.getT(u.Locale)

	if !canCreateSubscription(u, p.badgeAdminUserIDs, req.ChannelId) {
		dialogError(w, T("badges.api.cannot_create_subscription", "Вы не можете создать подписку"), nil)
		return
	}

	typeIDStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeType)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}

	err = p.store.AddSubscription(badgesmodel.BadgeType(typeIDStr), req.ChannelId)
	if err != nil {
		dialogError(w, err.Error(), nil)
	}

	p.mm.Post.SendEphemeralPost(userID, &model.Post{
		UserId:    p.BotUserID,
		ChannelId: req.ChannelId,
		Message:   T("badges.api.subscription_added", "Подписка добавлена"),
	})

	dialogOK(w)
}

func (p *Plugin) dialogDeleteSubscription(w http.ResponseWriter, r *http.Request, userID string) {
	req := model.SubmitDialogRequestFromJson(r.Body)
	if req == nil {
		T := p.getT("ru")
		dialogError(w, T("badges.api.dialog_parse_error", "Не удалось получить данные диалога"), nil)
		return
	}

	u, err := p.mm.User.Get(userID)
	if err != nil {
		dialogError(w, err.Error(), nil)
		return
	}
	T := p.getT(u.Locale)

	if !canCreateSubscription(u, p.badgeAdminUserIDs, req.ChannelId) {
		dialogError(w, T("badges.api.cannot_delete_subscription", "Вы не можете удалить подписку"), nil)
		return
	}

	typeIDStr, errText, errors := getDialogSubmissionTextField(req, DialogFieldBadgeType)
	if errors != nil {
		dialogError(w, errText, errors)
		return
	}

	err = p.store.RemoveSubscriptions(badgesmodel.BadgeType(typeIDStr), req.ChannelId)
	if err != nil {
		dialogError(w, err.Error(), nil)
	}

	p.mm.Post.SendEphemeralPost(userID, &model.Post{
		UserId:    p.BotUserID,
		ChannelId: req.ChannelId,
		Message:   T("badges.api.subscription_removed", "Подписка удалена"),
	})

	dialogOK(w)
}

func getDialogSubmissionTextField(req *model.SubmitDialogRequest, fieldName string) (value string, errText string, errors map[string]string) {
	value, ok := req.Submission[fieldName].(string)
	value = strings.TrimSpace(value)
	if !ok || value == "" {
		return "", "Invalid argument", map[string]string{fieldName: "Field empty or not recognized."}
	}

	return value, "", nil
}

func getDialogSubmissionBoolField(req *model.SubmitDialogRequest, fieldName string) bool {
	value, _ := req.Submission[fieldName].(bool)
	return value
}

func (p *Plugin) grantBadge(w http.ResponseWriter, r *http.Request, pluginID string) {
	var req *badgesmodel.GrantBadgeRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "cannot unmarshal request",
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
		})
		return
	}
	p.mm.Log.Debug("Granting badge", "req", req)

	if req == nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "missing request",
			Message:    "Missing grant request on request body",
			StatusCode: http.StatusInternalServerError,
		})
		return
	}

	granter, err := p.mm.User.Get(req.BotID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "cannot get user",
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
		})
		return
	}

	badge, err := p.store.GetBadge(badgesmodel.BadgeID(req.BadgeID))
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "cannot get badge",
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
		})
		return
	}

	badgeType, err := p.store.GetType(badge.Type)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "cannot get type",
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if !canGrantBadge(granter, p.badgeAdminUserIDs, badge, badgeType) {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "cannot grant badge",
			Message:    "you have no permissions to grant this badge",
			StatusCode: http.StatusUnauthorized,
		})
		return
	}

	shouldNotify, err := p.store.GrantBadge(req.BadgeID, req.UserID, req.BotID, req.Reason)
	if err == errAlreadyOwned {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "already_owned", Message: "This badge is already owned by this user", StatusCode: http.StatusConflict,
		})
		return
	}
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "cannot grant badge",
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
		})
		return
	}
	if shouldNotify {
		u, err := p.mm.User.Get(req.UserID)
		if err == nil {
			p.notifyGrant(req.BadgeID, req.BotID, u, false, "", req.Reason)
		}
	}

	_, _ = w.Write([]byte(`{"sucess": true}`))
}

func (p *Plugin) ensureBadges(w http.ResponseWriter, r *http.Request, pluginID string) {
	var req *badgesmodel.EnsureBadgesRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "cannot unmarshal request",
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
		})
		return
	}
	if req == nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "missing request",
			Message:    "Missing ensure request on request body",
			StatusCode: http.StatusInternalServerError,
		})
		return
	}

	badges, err := p.store.EnsureBadges(req.Badges, pluginID, req.BotID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "cannot ensure",
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
		})
		return
	}

	b, err := json.Marshal(badges)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID:         "cannot marshal",
			Message:    err.Error(),
			StatusCode: http.StatusInternalServerError,
		})
		return
	}

	_, _ = w.Write(b)
}

func (p *Plugin) getBadgeSuggestions(w http.ResponseWriter, r *http.Request, actingUserID string) {
	out := []model.AutocompleteListItem{}
	u, err := p.mm.User.Get(actingUserID)
	if err != nil {
		p.mm.Log.Debug("Error getting user", "error", err)
		_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
		return
	}

	bb, err := p.filterGrantBadges(u)
	if err != nil {
		p.mm.Log.Debug("Error getting suggestions", "error", err)
		_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
		return
	}

	for _, b := range bb {
		s := model.AutocompleteListItem{
			Item:     string(b.ID),
			Hint:     b.Name,
			HelpText: b.Description,
		}

		out = append(out, s)
	}
	_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
}

func (p *Plugin) getEditBadgeSuggestions(w http.ResponseWriter, r *http.Request, actingUserID string) {
	out := []model.AutocompleteListItem{}
	u, err := p.mm.User.Get(actingUserID)
	if err != nil {
		p.mm.Log.Debug("Error getting user", "error", err)
		_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
		return
	}

	bb, err := p.filterEditBadges(u)
	if err != nil {
		p.mm.Log.Debug("Error getting suggestions", "error", err)
		_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
		return
	}

	for _, b := range bb {
		s := model.AutocompleteListItem{
			Item:     string(b.ID),
			Hint:     b.Name,
			HelpText: b.Description,
		}

		out = append(out, s)
	}
	_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
}

func (p *Plugin) getBadgeTypeSuggestions(w http.ResponseWriter, r *http.Request, actingUserID string) {
	out := []model.AutocompleteListItem{}
	u, err := p.mm.User.Get(actingUserID)
	if err != nil {
		p.mm.Log.Debug("Error getting user", "error", err)
		_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
		return
	}

	types, err := p.filterCreateBadgeTypes(u)
	if err != nil {
		p.mm.Log.Debug("Error getting suggestions", "error", err)
		_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
		return
	}

	for _, t := range types {
		s := model.AutocompleteListItem{
			Item: string(t.ID),
			Hint: t.Name,
		}

		out = append(out, s)
	}
	_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
}

func (p *Plugin) getEditBadgeTypeSuggestions(w http.ResponseWriter, r *http.Request, actingUserID string) {
	out := []model.AutocompleteListItem{}
	u, err := p.mm.User.Get(actingUserID)
	if err != nil {
		p.mm.Log.Debug("Error getting user", "error", err)
		_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
		return
	}

	types, err := p.filterEditTypes(u)
	if err != nil {
		p.mm.Log.Debug("Error getting suggestions", "error", err)
		_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
		return
	}

	for _, t := range types {
		s := model.AutocompleteListItem{
			Item: string(t.ID),
			Hint: t.Name,
		}

		out = append(out, s)
	}
	_, _ = w.Write(model.AutocompleteStaticListItemsToJSON(out))
}

func (p *Plugin) getUserBadges(w http.ResponseWriter, r *http.Request, actingUserID string) {
	userID, ok := mux.Vars(r)["userID"]
	if !ok {
		userID = actingUserID
	}

	badges, err := p.store.GetUserBadges(userID)
	if err != nil {
		p.mm.Log.Debug("Error getting the badges for user", "error", err, "user", userID)
	}

	b, _ := json.Marshal(badges)
	_, _ = w.Write(b)
}

func (p *Plugin) getBadgeDetails(w http.ResponseWriter, r *http.Request, actingUserID string) {
	badgeIDString, ok := mux.Vars(r)["badgeID"]
	if !ok {
		errMessage := "Missing badge id"
		p.mm.Log.Debug(errMessage)
		w.WriteHeader(http.StatusBadRequest)
		_, _ = w.Write([]byte(errMessage))
		return
	}

	badgeID := badgesmodel.BadgeID(badgeIDString)

	badge, err := p.store.GetBadgeDetails(badgeID)
	if err != nil {
		p.mm.Log.Debug("Cannot get badge details", "badgeID", badgeID, "error", err)
	}

	type BadgeDetailsResponse struct {
		*badgesmodel.BadgeDetails
		CanEdit bool `json:"can_edit"`
	}

	resp := BadgeDetailsResponse{BadgeDetails: badge}
	if badge != nil {
		actingUser, userErr := p.mm.User.Get(actingUserID)
		if userErr == nil {
			bt, typeErr := p.store.GetType(badge.Type)
			if typeErr == nil {
				resp.CanEdit = canEditBadge(actingUser, p.badgeAdminUserIDs, &badge.Badge, bt)
			}
		}
	}

	b, _ := json.Marshal(resp)
	_, _ = w.Write(b)
}

func (p *Plugin) getAllBadges(w http.ResponseWriter, r *http.Request, actingUserID string) {
	badges, err := p.store.GetAllBadges()
	if err != nil {
		p.mm.Log.Debug("Cannot get all badges", "error", err)
	}

	b, _ := json.Marshal(badges)
	_, _ = w.Write(b)
}

func (p *Plugin) extractUserMiddleWare(handler HTTPHandlerFuncWithUser, responseType ResponseType) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userID := r.Header.Get("Mattermost-User-ID")
		if userID == "" {
			T := p.getT("ru")
			msg := T("badges.api.not_authorized", "Не авторизован")
			switch responseType {
			case ResponseTypeJSON:
				p.writeAPIError(w, &APIErrorResponse{ID: "", Message: msg, StatusCode: http.StatusUnauthorized})
			case ResponseTypePlain:
				http.Error(w, msg, http.StatusUnauthorized)
			case ResponseTypeDialog:
				dialogError(w, msg, nil)
			default:
				p.mm.Log.Error("Unknown ResponseType detected")
			}
			return
		}

		handler(w, r, userID)
	}
}

func (p *Plugin) withRecovery(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if x := recover(); x != nil {
				p.mm.Log.Error("Recovered from a panic",
					"url", r.URL.String(),
					"error", x,
					"stack", string(debug.Stack()))
			}
		}()

		next.ServeHTTP(w, r)
	})
}

func checkPluginRequest(next HTTPHandlerFuncWithUser) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// All other plugins are allowed
		pluginID := r.Header.Get("Mattermost-Plugin-ID")
		if pluginID == "" {
			http.Error(w, "Not authorized", http.StatusUnauthorized)
			return
		}

		next(w, r, pluginID)
	}
}

func (p *Plugin) writeAPIError(w http.ResponseWriter, apiErr *APIErrorResponse) {
	b, err := json.Marshal(apiErr)
	if err != nil {
		p.mm.Log.Warn("Failed to marshal API error", "error", err.Error())
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.WriteHeader(apiErr.StatusCode)

	_, err = w.Write(b)
	if err != nil {
		p.mm.Log.Warn("Failed to write JSON response", "error", err.Error())
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
}

func (p *Plugin) getPluginURL() string {
	urlP := p.mm.Configuration.GetConfig().ServiceSettings.SiteURL
	url := "/"
	if urlP != nil {
		url = *urlP
	}
	if url[len(url)-1] == '/' {
		url = url[0 : len(url)-1]
	}
	return url + "/plugins/" + manifest.Id
}

func (p *Plugin) getDialogURL() string {
	return p.getPluginURL() + DialogPath
}

func (p *Plugin) apiGrantBadge(w http.ResponseWriter, r *http.Request, userID string) {
	var req GrantBadgeAPIRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
		})
		return
	}

	req.BadgeID = strings.TrimSpace(req.BadgeID)
	req.UserID = strings.TrimSpace(req.UserID)

	if req.BadgeID == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_badge_id", Message: "Badge ID is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	if req.UserID == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_user_id", Message: "User ID is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	badge, err := p.store.GetBadge(badgesmodel.BadgeID(req.BadgeID))
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "badge_not_found", Message: "Badge not found", StatusCode: http.StatusNotFound,
		})
		return
	}

	granter, err := p.mm.User.Get(userID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	badgeType, err := p.store.GetType(badge.Type)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "type_not_found", Message: "Badge type not found", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if !canGrantBadge(granter, p.badgeAdminUserIDs, badge, badgeType) {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "no_permission_grant", Message: "No permission to grant this badge", StatusCode: http.StatusForbidden,
		})
		return
	}

	grantToUser, err := p.mm.User.Get(req.UserID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "user_not_found", Message: "User not found", StatusCode: http.StatusNotFound,
		})
		return
	}

	shouldNotify, err := p.store.GrantBadge(badgesmodel.BadgeID(req.BadgeID), req.UserID, userID, req.Reason)
	if errors.Is(err, errAlreadyOwned) {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "already_owned", Message: "This badge is already owned by this user", StatusCode: http.StatusConflict,
		})
		return
	}
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_grant_badge", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if shouldNotify {
		channelID := req.ChannelID
		p.notifyGrant(badgesmodel.BadgeID(req.BadgeID), userID, grantToUser, req.NotifyHere, channelID, req.Reason)
	}

	resp := map[string]string{"status": "ok"}
	b, _ := json.Marshal(resp)
	_, _ = w.Write(b)
}

func (p *Plugin) apiCreateSubscription(w http.ResponseWriter, r *http.Request, userID string) {
	var req SubscriptionAPIRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
		})
		return
	}

	u, err := p.mm.User.Get(userID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if !canCreateSubscription(u, p.badgeAdminUserIDs, req.ChannelID) {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "no_permission_subscription", Message: "No permission to manage subscriptions", StatusCode: http.StatusForbidden,
		})
		return
	}

	req.TypeID = strings.TrimSpace(req.TypeID)
	if req.TypeID == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_type_id", Message: "Type ID is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	err = p.store.AddSubscription(badgesmodel.BadgeType(req.TypeID), req.ChannelID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_create_subscription", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	T := p.getT(u.Locale)
	p.mm.Post.SendEphemeralPost(userID, &model.Post{
		UserId:    p.BotUserID,
		ChannelId: req.ChannelID,
		Message:   T("badges.api.subscription_added", "Подписка добавлена"),
	})

	resp := map[string]string{"status": "ok"}
	b, _ := json.Marshal(resp)
	_, _ = w.Write(b)
}

func (p *Plugin) apiDeleteSubscription(w http.ResponseWriter, r *http.Request, userID string) {
	var req SubscriptionAPIRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
		})
		return
	}

	u, err := p.mm.User.Get(userID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_user", Message: "Cannot get user", StatusCode: http.StatusInternalServerError,
		})
		return
	}

	if !canCreateSubscription(u, p.badgeAdminUserIDs, req.ChannelID) {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "no_permission_subscription", Message: "No permission to manage subscriptions", StatusCode: http.StatusForbidden,
		})
		return
	}

	req.TypeID = strings.TrimSpace(req.TypeID)
	if req.TypeID == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_type_id", Message: "Type ID is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	err = p.store.RemoveSubscriptions(badgesmodel.BadgeType(req.TypeID), req.ChannelID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_delete_subscription", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	T := p.getT(u.Locale)
	p.mm.Post.SendEphemeralPost(userID, &model.Post{
		UserId:    p.BotUserID,
		ChannelId: req.ChannelID,
		Message:   T("badges.api.subscription_removed", "Подписка удалена"),
	})

	resp := map[string]string{"status": "ok"}
	b, _ := json.Marshal(resp)
	_, _ = w.Write(b)
}

func (p *Plugin) apiGetChannelSubscriptions(w http.ResponseWriter, r *http.Request, userID string) {
	channelID := mux.Vars(r)["channelID"]
	if channelID == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: "Channel ID is required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	types, err := p.store.GetChannelSubscriptions(channelID)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_get_types", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	b, _ := json.Marshal(types)
	_, _ = w.Write(b)
}

func (p *Plugin) apiRevokeOwnership(w http.ResponseWriter, r *http.Request, userID string) {
	var req RevokeOwnershipRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: "Invalid request body", StatusCode: http.StatusBadRequest,
		})
		return
	}

	req.BadgeID = strings.TrimSpace(req.BadgeID)
	req.UserID = strings.TrimSpace(req.UserID)
	req.Time = strings.TrimSpace(req.Time)
	if req.BadgeID == "" || req.UserID == "" || req.Time == "" {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "invalid_request", Message: "badge_id, user_id and time are required", StatusCode: http.StatusBadRequest,
		})
		return
	}

	ownership, err := p.store.FindOwnership(badgesmodel.BadgeID(req.BadgeID), req.UserID, req.Time)
	if err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "ownership_not_found", Message: "Ownership not found", StatusCode: http.StatusNotFound,
		})
		return
	}

	isAdmin := p.badgeAdminUserIDs[userID]
	if ownership.GrantedBy != userID && !isAdmin {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "no_permission_revoke", Message: "No permission to revoke this ownership", StatusCode: http.StatusForbidden,
		})
		return
	}

	if err := p.store.RevokeOwnership(badgesmodel.BadgeID(req.BadgeID), req.UserID, req.Time); err != nil {
		p.writeAPIError(w, &APIErrorResponse{
			ID: "cannot_revoke", Message: err.Error(), StatusCode: http.StatusInternalServerError,
		})
		return
	}

	resp := map[string]string{"status": "ok"}
	b, _ := json.Marshal(resp)
	_, _ = w.Write(b)
}
