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

package user_test

import (
	"testing"

	"gitea.dev/models/db"
	"gitea.dev/models/unittest"
	user_model "gitea.dev/models/user"
	"gitea.dev/modules/util"

	"github.com/stretchr/testify/assert"
)

func TestBadge(t *testing.T) {
	assert.NoError(t, unittest.PrepareTestDatabase())

	t.Run("GetBadgeNotExist", testGetBadgeNotExist)
	t.Run("CreateBadgeAlreadyExists", testCreateBadgeAlreadyExists)
	t.Run("GetBadgeUsers", testGetBadgeUsers)
	t.Run("AddAndRemoveUserBadges", testAddAndRemoveUserBadges)
	t.Run("SearchBadgesOrderingAndKeyword", testSearchBadgesOrderingAndKeyword)
}

func testGetBadgeNotExist(t *testing.T) {
	badge, err := user_model.GetBadge(t.Context(), "does-not-exist")
	assert.Nil(t, badge)
	assert.Error(t, err)
	assert.ErrorIs(t, err, util.ErrNotExist)
}

func testCreateBadgeAlreadyExists(t *testing.T) {
	badge := &user_model.Badge{
		Slug:        "duplicate-badge-slug",
		Description: "First",
	}
	assert.NoError(t, user_model.CreateBadge(t.Context(), badge))

	err := user_model.CreateBadge(t.Context(), &user_model.Badge{
		Slug:        "duplicate-badge-slug",
		Description: "Second",
	})
	assert.Error(t, err)
	assert.ErrorIs(t, err, util.ErrAlreadyExist)
}

func testGetBadgeUsers(t *testing.T) {
	// Create a test badge
	badge := &user_model.Badge{
		Slug:        "test-badge-users",
		Description: "Test Badge",
		ImageURL:    "test.png",
	}
	assert.NoError(t, user_model.CreateBadge(t.Context(), badge))

	// Create test users and assign badges
	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})

	assert.NoError(t, user_model.AddUserBadge(t.Context(), user1, badge))
	assert.NoError(t, user_model.AddUserBadge(t.Context(), user2, badge))
	defer func() {
		assert.NoError(t, user_model.RemoveUserBadge(t.Context(), user1, badge))
		assert.NoError(t, user_model.RemoveUserBadge(t.Context(), user2, badge))
	}()

	// Test getting users with pagination
	opts := &user_model.GetBadgeUsersOptions{
		BadgeSlug: badge.Slug,
		ListOptions: db.ListOptions{
			Page:     1,
			PageSize: 1,
		},
	}

	users, count, err := user_model.GetBadgeUsers(t.Context(), opts)
	assert.NoError(t, err)
	assert.EqualValues(t, 2, count)
	assert.Len(t, users, 1)

	// Test second page
	opts.Page = 2
	users, count, err = user_model.GetBadgeUsers(t.Context(), opts)
	assert.NoError(t, err)
	assert.EqualValues(t, 2, count)
	assert.Len(t, users, 1)

	// Test with non-existent badge
	opts.BadgeSlug = "non-existent"
	users, count, err = user_model.GetBadgeUsers(t.Context(), opts)
	assert.NoError(t, err)
	assert.EqualValues(t, 0, count)
	assert.Empty(t, users)
}

func testAddAndRemoveUserBadges(t *testing.T) {
	badge1 := unittest.AssertExistsAndLoadBean(t, &user_model.Badge{ID: 1})
	user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})

	// Add a badge to user and verify that it is returned in the list
	assert.NoError(t, user_model.AddUserBadge(t.Context(), user1, badge1))
	badges, count, err := user_model.GetUserBadges(t.Context(), user1)
	assert.Equal(t, int64(1), count)
	assert.Equal(t, badge1.Slug, badges[0].Slug)
	assert.NoError(t, err)

	// Confirm that it is impossible to duplicate the same badge
	err = user_model.AddUserBadge(t.Context(), user1, badge1)
	assert.Error(t, err)
	assert.ErrorIs(t, err, util.ErrAlreadyExist)

	// Nothing happened to the existing badge
	badges, count, err = user_model.GetUserBadges(t.Context(), user1)
	assert.Equal(t, int64(1), count)
	assert.Equal(t, badge1.Slug, badges[0].Slug)
	assert.NoError(t, err)

	// Remove a badge from user and verify that it is no longer in the list
	assert.NoError(t, user_model.RemoveUserBadge(t.Context(), user1, badge1))
	_, count, err = user_model.GetUserBadges(t.Context(), user1)
	assert.Equal(t, int64(0), count)
	assert.NoError(t, err)

	// Removing empty or missing badge selections should be a no-op.
	assert.NoError(t, user_model.RemoveUserBadges(t.Context(), user1, nil))
	assert.NoError(t, user_model.RemoveUserBadges(t.Context(), user1, []*user_model.Badge{{Slug: "does-not-exist"}}))
}

func testSearchBadgesOrderingAndKeyword(t *testing.T) {
	createdBadges := []*user_model.Badge{
		{Slug: "badge-sort-b", Description: "Badge Sort B"},
		{Slug: "badge-sort-c", Description: "Badge Sort C"},
		{Slug: "badge-sort-a", Description: "Badge Sort A"},
		{Slug: "badge-sort-case", Description: "MiXeDCaSeKeyword"},
	}
	for _, badge := range createdBadges {
		assert.NoError(t, user_model.CreateBadge(t.Context(), badge))
	}

	opts := &user_model.SearchBadgeOptions{
		ListOptions: db.ListOptions{ListAll: true},
		Keyword:     "badge-sort-",
		OrderBy:     db.SearchOrderBy("`badge`.id ASC"),
	}

	oldestFirst, count, err := user_model.SearchBadges(t.Context(), opts)
	assert.NoError(t, err)
	assert.EqualValues(t, 4, count)
	assert.Equal(t, []string{"badge-sort-b", "badge-sort-c", "badge-sort-a", "badge-sort-case"}, collectBadgeSlugs(oldestFirst))

	opts.OrderBy = db.SearchOrderBy("`badge`.id DESC")
	newestFirst, count, err := user_model.SearchBadges(t.Context(), opts)
	assert.NoError(t, err)
	assert.EqualValues(t, 4, count)
	assert.Equal(t, []string{"badge-sort-case", "badge-sort-a", "badge-sort-c", "badge-sort-b"}, collectBadgeSlugs(newestFirst))

	opts.OrderBy = db.SearchOrderBy("`badge`.slug ASC")
	alpha, count, err := user_model.SearchBadges(t.Context(), opts)
	assert.NoError(t, err)
	assert.EqualValues(t, 4, count)
	assert.Equal(t, []string{"badge-sort-a", "badge-sort-b", "badge-sort-c", "badge-sort-case"}, collectBadgeSlugs(alpha))

	opts.OrderBy = db.SearchOrderBy("`badge`.slug DESC")
	reverseAlpha, count, err := user_model.SearchBadges(t.Context(), opts)
	assert.NoError(t, err)
	assert.EqualValues(t, 4, count)
	assert.Equal(t, []string{"badge-sort-case", "badge-sort-c", "badge-sort-b", "badge-sort-a"}, collectBadgeSlugs(reverseAlpha))

	opts.Keyword = "mixedcasekeyword"
	opts.OrderBy = db.SearchOrderBy("`badge`.slug ASC")
	caseInsensitive, count, err := user_model.SearchBadges(t.Context(), opts)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, count)
	assert.Equal(t, []string{"badge-sort-case"}, collectBadgeSlugs(caseInsensitive))
}

func collectBadgeSlugs(badges []*user_model.Badge) []string {
	slugs := make([]string, 0, len(badges))
	for _, badge := range badges {
		slugs = append(slugs, badge.Slug)
	}
	return slugs
}
