// Copyright 2026 The Gitea Authors.
// SPDX-License-Identifier: MIT

package admin

import (
	"errors"
	"net/http"
	"net/url"
	"strings"

	"gitea.dev/models/db"
	user_model "gitea.dev/models/user"
	"gitea.dev/modules/log"
	"gitea.dev/modules/setting"
	"gitea.dev/modules/templates"
	"gitea.dev/modules/util"
	"gitea.dev/modules/web"
	"gitea.dev/services/context"
	"gitea.dev/services/forms"
)

const (
	tplBadges     templates.TplName = "admin/badge/list"
	tplBadgeNew   templates.TplName = "admin/badge/new"
	tplBadgeView  templates.TplName = "admin/badge/view"
	tplBadgeEdit  templates.TplName = "admin/badge/edit"
	tplBadgeUsers templates.TplName = "admin/badge/users"
)

// BadgeSearchDefaultAdminSort is the default sort type for admin view
const BadgeSearchDefaultAdminSort = "oldest"

// Badges show all the badges
func Badges(ctx *context.Context) {
	ctx.Data["Title"] = ctx.Tr("admin.badges")
	ctx.Data["PageIsAdminBadges"] = true

	RenderBadgeSearch(ctx, &user_model.SearchBadgeOptions{
		ListOptions: db.ListOptions{
			Page:     max(ctx.FormInt("page"), 1),
			PageSize: setting.UI.Admin.UserPagingNum,
		},
	}, tplBadges)
}

// NewBadge render adding a new badge
func NewBadge(ctx *context.Context) {
	ctx.Data["Title"] = ctx.Tr("admin.badges.new_badge")
	ctx.Data["PageIsAdminBadges"] = true

	ctx.HTML(http.StatusOK, tplBadgeNew)
}

// NewBadgePost response for adding a new badge
func NewBadgePost(ctx *context.Context) {
	form := web.GetForm(ctx).(*forms.AdminCreateBadgeForm)

	if ctx.HasError() {
		ctx.JSONError(ctx.GetErrMsg())
		return
	}

	b := &user_model.Badge{
		Slug:        form.Slug,
		Description: form.Description,
		ImageURL:    form.ImageURL,
	}

	if err := user_model.CreateBadge(ctx, b); err != nil {
		if errors.Is(err, util.ErrAlreadyExist) {
			ctx.JSONError(ctx.Tr("admin.badges.slug_been_taken"))
		} else {
			ctx.ServerError("CreateBadge", err)
		}
		return
	}

	log.Trace("Badge created by admin (%s): %s", ctx.Doer.Name, b.Slug)

	ctx.Flash.Success(ctx.Tr("admin.badges.new_success", b.Slug))
	ctx.JSONRedirect(setting.AppSubURL + "/-/admin/badges/slug/" + url.PathEscape(b.Slug))
}

func prepareBadgeInfo(ctx *context.Context) *user_model.Badge {
	b, err := user_model.GetBadge(ctx, ctx.PathParam("badge_slug"))
	if err != nil {
		if errors.Is(err, util.ErrNotExist) {
			ctx.Redirect(setting.AppSubURL + "/-/admin/badges")
		} else {
			ctx.ServerError("GetBadge", err)
		}
		return nil
	}
	ctx.Data["Badge"] = b
	return b
}

func ViewBadge(ctx *context.Context) {
	ctx.Data["Title"] = ctx.Tr("admin.badges.details")
	ctx.Data["PageIsAdminBadges"] = true

	prepareBadgeInfo(ctx)
	if ctx.Written() {
		return
	}

	badge := ctx.Data["Badge"].(*user_model.Badge)
	opts := &user_model.GetBadgeUsersOptions{
		ListOptions: db.ListOptions{
			Page:     1,
			PageSize: setting.UI.Admin.UserPagingNum,
		},
		BadgeSlug: badge.Slug,
	}
	users, count, err := user_model.GetBadgeUsers(ctx, opts)
	if err != nil {
		ctx.ServerError("GetBadgeUsers", err)
		return
	}
	ctx.Data["Users"] = users
	ctx.Data["UsersTotal"] = int(count)

	ctx.HTML(http.StatusOK, tplBadgeView)
}

// EditBadge show editing badge page
func EditBadge(ctx *context.Context) {
	ctx.Data["Title"] = ctx.Tr("admin.badges.edit_badge")
	ctx.Data["PageIsAdminBadges"] = true
	prepareBadgeInfo(ctx)
	if ctx.Written() {
		return
	}

	ctx.HTML(http.StatusOK, tplBadgeEdit)
}

// EditBadgePost response for editing badge
func EditBadgePost(ctx *context.Context) {
	b := prepareBadgeInfo(ctx)
	if ctx.Written() {
		return
	}

	form := web.GetForm(ctx).(*forms.AdminEditBadgeForm)
	if ctx.HasError() {
		ctx.JSONError(ctx.GetErrMsg())
		return
	}

	b.ImageURL = form.ImageURL
	b.Description = form.Description

	if err := user_model.UpdateBadge(ctx, b); err != nil {
		ctx.ServerError("UpdateBadge", err)
		return
	}

	log.Trace("Badge updated by admin (%s): %s", ctx.Doer.Name, b.Slug)

	ctx.Flash.Success(ctx.Tr("admin.badges.update_success"))
	ctx.JSONRedirect(setting.AppSubURL + "/-/admin/badges/slug/" + url.PathEscape(ctx.PathParam("badge_slug")))
}

// DeleteBadge response for deleting a badge
func DeleteBadge(ctx *context.Context) {
	b, err := user_model.GetBadge(ctx, ctx.PathParam("badge_slug"))
	if err != nil {
		ctx.ServerError("GetBadge", err)
		return
	}

	if err = user_model.DeleteBadge(ctx, b); err != nil {
		ctx.ServerError("DeleteBadge", err)
		return
	}

	log.Trace("Badge deleted by admin (%s): %s", ctx.Doer.Name, b.Slug)

	ctx.Flash.Success(ctx.Tr("admin.badges.deletion_success"))
	ctx.Redirect(setting.AppSubURL + "/-/admin/badges")
}

func BadgeUsers(ctx *context.Context) {
	ctx.Data["Title"] = ctx.Tr("admin.badges.users_with_badge", ctx.PathParam("badge_slug"))
	ctx.Data["PageIsAdminBadges"] = true

	page := max(ctx.FormInt("page"), 1)

	badge := &user_model.Badge{Slug: ctx.PathParam("badge_slug")}
	opts := &user_model.GetBadgeUsersOptions{
		ListOptions: db.ListOptions{
			Page:     page,
			PageSize: setting.UI.Admin.UserPagingNum,
		},
		BadgeSlug: badge.Slug,
	}
	users, count, err := user_model.GetBadgeUsers(ctx, opts)
	if err != nil {
		ctx.ServerError("GetBadgeUsers", err)
		return
	}

	ctx.Data["Users"] = users
	ctx.Data["Total"] = count
	ctx.Data["Page"] = context.NewPagination(count, setting.UI.Admin.UserPagingNum, page, 5)

	ctx.HTML(http.StatusOK, tplBadgeUsers)
}

// BadgeUsersPost response for actions for user badges
func BadgeUsersPost(ctx *context.Context) {
	name := strings.ToLower(ctx.FormString("user"))

	u, err := user_model.GetUserByName(ctx, name)
	if err != nil {
		if user_model.IsErrUserNotExist(err) {
			ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
			ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
		} else {
			ctx.ServerError("GetUserByName", err)
		}
		return
	}

	if err = user_model.AddUserBadge(ctx, u, &user_model.Badge{Slug: ctx.PathParam("badge_slug")}); err != nil {
		if errors.Is(err, util.ErrNotExist) {
			ctx.Flash.Error(ctx.Tr("admin.badges.not_found"))
			ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
		} else if errors.Is(err, util.ErrAlreadyExist) {
			ctx.Flash.Error(ctx.Tr("admin.badges.user_already_has"))
			ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
		} else {
			ctx.ServerError("AddUserBadge", err)
		}
		return
	}

	ctx.Flash.Success(ctx.Tr("admin.badges.user_add_success"))
	ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath())
}

// DeleteBadgeUser delete a badge from a user
func DeleteBadgeUser(ctx *context.Context) {
	badgeUsersURL := setting.AppSubURL + "/-/admin/badges/slug/" + url.PathEscape(ctx.PathParam("badge_slug")) + "/users"

	user, err := user_model.GetUserByID(ctx, ctx.FormInt64("id"))
	if err != nil {
		if user_model.IsErrUserNotExist(err) {
			ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
			ctx.JSONRedirect(badgeUsersURL)
			return
		} else {
			ctx.ServerError("GetUserByID", err)
			return
		}
	}
	if err := user_model.RemoveUserBadge(ctx, user, &user_model.Badge{Slug: ctx.PathParam("badge_slug")}); err == nil {
		ctx.Flash.Success(ctx.Tr("admin.badges.user_remove_success"))
	} else {
		ctx.ServerError("RemoveUserBadge", err)
		return
	}

	ctx.JSONRedirect(badgeUsersURL)
}

func RenderBadgeSearch(ctx *context.Context, opts *user_model.SearchBadgeOptions, tplName templates.TplName) {
	var (
		badges  []*user_model.Badge
		count   int64
		err     error
		orderBy db.SearchOrderBy
	)

	sortOrder := ctx.FormString("sort")
	if sortOrder == "" {
		sortOrder = BadgeSearchDefaultAdminSort
	}
	ctx.Data["SortType"] = sortOrder

	switch sortOrder {
	case "newest":
		orderBy = "`badge`.id DESC"
	case "oldest":
		orderBy = "`badge`.id ASC"
	case "reversealphabetically":
		orderBy = "`badge`.slug DESC"
	case "alphabetically":
		orderBy = "`badge`.slug ASC"
	default:
		// In case the sort type is invalid, keep admin default sorting.
		ctx.Data["SortType"] = "oldest"
		orderBy = "`badge`.id ASC"
	}

	opts.Keyword = ctx.FormTrim("q")
	opts.OrderBy = orderBy
	if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
		badges, count, err = user_model.SearchBadges(ctx, opts)
		if err != nil {
			ctx.ServerError("SearchBadges", err)
			return
		}
	}

	ctx.Data["Keyword"] = opts.Keyword
	ctx.Data["Total"] = count
	ctx.Data["Badges"] = badges

	pager := context.NewPagination(count, opts.PageSize, opts.Page, 5)
	pager.AddParamFromRequest(ctx.Req)
	ctx.Data["Page"] = pager

	ctx.HTML(http.StatusOK, tplName)
}
