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

package admin

import (
	"strconv"
	"testing"

	"code.gitea.io/gitea/models/unittest"
	user_model "code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/modules/setting"
	api "code.gitea.io/gitea/modules/structs"
	"code.gitea.io/gitea/modules/test"
	"code.gitea.io/gitea/modules/web"
	"code.gitea.io/gitea/services/contexttest"
	"code.gitea.io/gitea/services/forms"

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

func TestNewUserPost_MustChangePassword(t *testing.T) {
	unittest.PrepareTestEnv(t)
	ctx, _ := contexttest.MockContext(t, "admin/users/new")

	u := unittest.AssertExistsAndLoadBean(t, &user_model.User{
		IsAdmin: true,
		ID:      2,
	})

	ctx.Doer = u

	username := "gitea"
	email := "gitea@gitea.io"

	form := forms.AdminCreateUserForm{
		LoginType:          "local",
		LoginName:          "local",
		UserName:           username,
		Email:              email,
		Password:           "abc123ABC!=$",
		SendNotify:         false,
		MustChangePassword: true,
	}

	web.SetForm(ctx, &form)
	NewUserPost(ctx)

	assert.NotEmpty(t, ctx.Flash.SuccessMsg)

	u, err := user_model.GetUserByName(ctx, username)

	assert.NoError(t, err)
	assert.Equal(t, username, u.Name)
	assert.Equal(t, email, u.Email)
	assert.True(t, u.MustChangePassword)
}

func TestNewUserPost_MustChangePasswordFalse(t *testing.T) {
	unittest.PrepareTestEnv(t)
	ctx, _ := contexttest.MockContext(t, "admin/users/new")

	u := unittest.AssertExistsAndLoadBean(t, &user_model.User{
		IsAdmin: true,
		ID:      2,
	})

	ctx.Doer = u

	username := "gitea"
	email := "gitea@gitea.io"

	form := forms.AdminCreateUserForm{
		LoginType:          "local",
		LoginName:          "local",
		UserName:           username,
		Email:              email,
		Password:           "abc123ABC!=$",
		SendNotify:         false,
		MustChangePassword: false,
	}

	web.SetForm(ctx, &form)
	NewUserPost(ctx)

	assert.NotEmpty(t, ctx.Flash.SuccessMsg)

	u, err := user_model.GetUserByName(ctx, username)

	assert.NoError(t, err)
	assert.Equal(t, username, u.Name)
	assert.Equal(t, email, u.Email)
	assert.False(t, u.MustChangePassword)
}

func TestNewUserPost_InvalidEmail(t *testing.T) {
	unittest.PrepareTestEnv(t)
	ctx, _ := contexttest.MockContext(t, "admin/users/new")

	u := unittest.AssertExistsAndLoadBean(t, &user_model.User{
		IsAdmin: true,
		ID:      2,
	})

	ctx.Doer = u

	username := "gitea"
	email := "gitea@gitea.io\r\n"

	form := forms.AdminCreateUserForm{
		LoginType:          "local",
		LoginName:          "local",
		UserName:           username,
		Email:              email,
		Password:           "abc123ABC!=$",
		SendNotify:         false,
		MustChangePassword: false,
	}

	web.SetForm(ctx, &form)
	NewUserPost(ctx)

	assert.NotEmpty(t, ctx.Flash.ErrorMsg)
}

func TestNewUserPost_VisibilityDefaultPublic(t *testing.T) {
	unittest.PrepareTestEnv(t)
	ctx, _ := contexttest.MockContext(t, "admin/users/new")

	u := unittest.AssertExistsAndLoadBean(t, &user_model.User{
		IsAdmin: true,
		ID:      2,
	})

	ctx.Doer = u

	username := "gitea"
	email := "gitea@gitea.io"

	form := forms.AdminCreateUserForm{
		LoginType:          "local",
		LoginName:          "local",
		UserName:           username,
		Email:              email,
		Password:           "abc123ABC!=$",
		SendNotify:         false,
		MustChangePassword: false,
	}

	web.SetForm(ctx, &form)
	NewUserPost(ctx)

	assert.NotEmpty(t, ctx.Flash.SuccessMsg)

	u, err := user_model.GetUserByName(ctx, username)

	assert.NoError(t, err)
	assert.Equal(t, username, u.Name)
	assert.Equal(t, email, u.Email)
	// As default user visibility
	assert.Equal(t, setting.Service.DefaultUserVisibilityMode, u.Visibility)
}

func TestNewUserPost_VisibilityPrivate(t *testing.T) {
	unittest.PrepareTestEnv(t)
	ctx, _ := contexttest.MockContext(t, "admin/users/new")

	u := unittest.AssertExistsAndLoadBean(t, &user_model.User{
		IsAdmin: true,
		ID:      2,
	})

	ctx.Doer = u

	username := "gitea"
	email := "gitea@gitea.io"

	form := forms.AdminCreateUserForm{
		LoginType:          "local",
		LoginName:          "local",
		UserName:           username,
		Email:              email,
		Password:           "abc123ABC!=$",
		SendNotify:         false,
		MustChangePassword: false,
		Visibility:         api.VisibleTypePrivate,
	}

	web.SetForm(ctx, &form)
	NewUserPost(ctx)

	assert.NotEmpty(t, ctx.Flash.SuccessMsg)

	u, err := user_model.GetUserByName(ctx, username)

	assert.NoError(t, err)
	assert.Equal(t, username, u.Name)
	assert.Equal(t, email, u.Email)
	// As default user visibility
	assert.True(t, u.Visibility.IsPrivate())
}

func TestNewUserDefaultsSendInviteWhenMailIsAvailable(t *testing.T) {
	unittest.PrepareTestEnv(t)
	defer test.MockVariableValue(&setting.MailService, &setting.Mailer{})()

	ctx, _ := contexttest.MockContext(t, "admin/users/new")

	NewUser(ctx)

	assert.Equal(t, false, ctx.Data["send_notify"])
	assert.Equal(t, true, ctx.Data["send_invite"])
}

func TestNewUserDefaultsDoNotSendInviteWithoutMail(t *testing.T) {
	unittest.PrepareTestEnv(t)

	ctx, _ := contexttest.MockContext(t, "admin/users/new")

	NewUser(ctx)

	assert.Equal(t, false, ctx.Data["send_notify"])
	assert.Equal(t, false, ctx.Data["send_invite"])
}

func TestNewUserPost_SendInviteCreatesInactivePendingInviteAccount(t *testing.T) {
	unittest.PrepareTestEnv(t)
	defer test.MockVariableValue(&setting.Service.DisableRegistration, false)()
	defer test.MockVariableValue(&setting.Service.AdminCreatedAccountMode, setting.AdminCreatedAccountModeLocal)()
	defer test.MockVariableValue(&setting.MailService, &setting.Mailer{})()

	ctx, _ := contexttest.MockContext(t, "admin/users/new")

	adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{
		IsAdmin: true,
		ID:      2,
	})

	ctx.Doer = adminUser

	username := "gitea"
	email := "gitea@gitea.io"

	form := forms.AdminCreateUserForm{
		LoginType:          "local",
		LoginName:          "local",
		UserName:           username,
		Email:              email,
		Password:           "abc123ABC!=$",
		SendNotify:         false,
		SendInvite:         true,
		MustChangePassword: true,
	}

	web.SetForm(ctx, &form)
	NewUserPost(ctx)

	assert.NotEmpty(t, ctx.Flash.SuccessMsg)

	u, err := user_model.GetUserByName(ctx, username)

	assert.NoError(t, err)
	assert.False(t, u.IsActive)
	assert.False(t, u.ProhibitLogin)
	isPendingAdminInvite, err := user_model.IsPendingAdminInvite(t.Context(), u)
	assert.NoError(t, err)
	assert.True(t, isPendingAdminInvite)
	createdBy, err := user_model.GetUserSetting(ctx, u.ID, user_model.SettingsKeyAdminInviteCreatedBy)
	assert.NoError(t, err)
	assert.Equal(t, "2", createdBy)
	createdByName, err := user_model.GetUserSetting(ctx, u.ID, user_model.SettingsKeyAdminInviteCreatedByName)
	assert.NoError(t, err)
	assert.Equal(t, adminUser.Name, createdByName)
	createdByEmail, err := user_model.GetUserSetting(ctx, u.ID, user_model.SettingsKeyAdminInviteCreatedByEmail)
	assert.NoError(t, err)
	assert.Equal(t, adminUser.Email, createdByEmail)
}

func TestNewUserPost_SendNotifyCreatesActiveLocalAccount(t *testing.T) {
	unittest.PrepareTestEnv(t)
	defer test.MockVariableValue(&setting.Service.DisableRegistration, true)()
	defer test.MockVariableValue(&setting.Service.AdminCreatedAccountMode, setting.AdminCreatedAccountModeInvite)()
	defer test.MockVariableValue(&setting.MailService, &setting.Mailer{})()

	ctx, _ := contexttest.MockContext(t, "admin/users/new")

	adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{
		IsAdmin: true,
		ID:      2,
	})

	ctx.Doer = adminUser

	username := "gitea"
	email := "gitea@gitea.io"

	form := forms.AdminCreateUserForm{
		LoginType:          "local",
		LoginName:          "local",
		UserName:           username,
		Email:              email,
		Password:           "abc123ABC!=$",
		SendNotify:         true,
		SendInvite:         false,
		MustChangePassword: true,
	}

	web.SetForm(ctx, &form)
	NewUserPost(ctx)

	assert.NotEmpty(t, ctx.Flash.SuccessMsg)

	u, err := user_model.GetUserByName(ctx, username)

	assert.NoError(t, err)
	assert.True(t, u.IsActive)
	assert.False(t, u.ProhibitLogin)
	createdBy, err := user_model.GetUserSetting(ctx, u.ID, user_model.SettingsKeyAdminInviteCreatedBy)
	assert.NoError(t, err)
	assert.Equal(t, "2", createdBy)
}

func TestLoadAdminUserListCreatedBy(t *testing.T) {
	unittest.PrepareTestEnv(t)

	firstAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
	createdUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
	require.NoError(t, user_model.SetUserSetting(t.Context(), createdUser.ID, user_model.SettingsKeyAdminInviteCreatedBy, "1"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), createdUser.ID, user_model.SettingsKeyAdminInviteCreatedByName, "deleted-admin"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), createdUser.ID, user_model.SettingsKeyAdminInviteCreatedByEmail, "deleted-admin@example.com"))

	ctx, _ := contexttest.MockContext(t, "admin/users")
	loadAdminUserListCreatedBy(ctx, user_model.UserList{firstAdmin, createdUser})

	usersCreatedByAdmin := ctx.Data["UsersCreatedByAdmin"].(map[int64]*adminInviteCreatorInfo)
	assert.Equal(t, "GOD", usersCreatedByAdmin[firstAdmin.ID].Name)
	assert.Empty(t, usersCreatedByAdmin[firstAdmin.ID].Email)
	assert.Equal(t, "deleted-admin", usersCreatedByAdmin[createdUser.ID].Name)
	assert.Equal(t, "deleted-admin@example.com", usersCreatedByAdmin[createdUser.ID].Email)
}

func TestLoadAdminUserListAdminBy(t *testing.T) {
	unittest.PrepareTestEnv(t)

	adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
	adminUser.IsAdmin = true
	require.NoError(t, user_model.SetUserSetting(t.Context(), adminUser.ID, user_model.SettingsKeyAdminGrantedBy, "1"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), adminUser.ID, user_model.SettingsKeyAdminGrantedByName, "deleted-admin"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), adminUser.ID, user_model.SettingsKeyAdminGrantedByEmail, "deleted-admin@example.com"))

	ctx, _ := contexttest.MockContext(t, "admin/users")
	loadAdminUserListAdminBy(ctx, user_model.UserList{adminUser})

	usersAdminByAdmin := ctx.Data["UsersAdminByAdmin"].(map[int64]*adminActionActorInfo)
	assert.Equal(t, "deleted-admin", usersAdminByAdmin[adminUser.ID].Name)
	assert.Equal(t, "deleted-admin@example.com", usersAdminByAdmin[adminUser.ID].Email)
}

func TestLoadAdminUserListActivatedBy(t *testing.T) {
	unittest.PrepareTestEnv(t)

	activatedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
	require.True(t, activatedUser.IsActive)
	require.NoError(t, user_model.SetUserSetting(t.Context(), activatedUser.ID, user_model.SettingsKeyAdminActivatedBy, "1"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), activatedUser.ID, user_model.SettingsKeyAdminActivatedByName, "deleted-admin"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), activatedUser.ID, user_model.SettingsKeyAdminActivatedByEmail, "deleted-admin@example.com"))

	ctx, _ := contexttest.MockContext(t, "admin/users")
	loadAdminUserListActivatedBy(ctx, user_model.UserList{activatedUser})

	usersActivatedByAdmin := ctx.Data["UsersActivatedByAdmin"].(map[int64]*adminActionActorInfo)
	assert.Equal(t, "deleted-admin", usersActivatedByAdmin[activatedUser.ID].Name)
	assert.Equal(t, "deleted-admin@example.com", usersActivatedByAdmin[activatedUser.ID].Email)
}

func TestLoadAdminUserListProhibitLoginBy(t *testing.T) {
	unittest.PrepareTestEnv(t)

	prohibitedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
	prohibitedUser.ProhibitLogin = true
	require.NoError(t, user_model.SetUserSetting(t.Context(), prohibitedUser.ID, user_model.SettingsKeyAdminProhibitLoginUnix, "1714320000"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), prohibitedUser.ID, user_model.SettingsKeyAdminProhibitLoginBy, "1"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), prohibitedUser.ID, user_model.SettingsKeyAdminProhibitLoginByName, "deleted-admin"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), prohibitedUser.ID, user_model.SettingsKeyAdminProhibitLoginByEmail, "deleted-admin@example.com"))

	ctx, _ := contexttest.MockContext(t, "admin/users")
	loadAdminUserListProhibitLoginBy(ctx, user_model.UserList{prohibitedUser})

	usersProhibitLoginByAdmin := ctx.Data["UsersProhibitLoginByAdmin"].(map[int64]*adminProhibitLoginInfo)
	assert.EqualValues(t, 1714320000, usersProhibitLoginByAdmin[prohibitedUser.ID].At)
	assert.Equal(t, "deleted-admin", usersProhibitLoginByAdmin[prohibitedUser.ID].Name)
	assert.Equal(t, "deleted-admin@example.com", usersProhibitLoginByAdmin[prohibitedUser.ID].Email)
}

func TestLoadAdminUserListRestrictedBy(t *testing.T) {
	unittest.PrepareTestEnv(t)

	restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
	restrictedUser.IsRestricted = true
	require.NoError(t, user_model.SetUserSetting(t.Context(), restrictedUser.ID, user_model.SettingsKeyAdminRestrictedUnix, "1714320000"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), restrictedUser.ID, user_model.SettingsKeyAdminRestrictedBy, "1"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), restrictedUser.ID, user_model.SettingsKeyAdminRestrictedByName, "deleted-admin"))
	require.NoError(t, user_model.SetUserSetting(t.Context(), restrictedUser.ID, user_model.SettingsKeyAdminRestrictedByEmail, "deleted-admin@example.com"))

	ctx, _ := contexttest.MockContext(t, "admin/users")
	loadAdminUserListRestrictedBy(ctx, user_model.UserList{restrictedUser})

	usersRestrictedByAdmin := ctx.Data["UsersRestrictedByAdmin"].(map[int64]*adminRestrictedInfo)
	assert.EqualValues(t, 1714320000, usersRestrictedByAdmin[restrictedUser.ID].At)
	assert.Equal(t, "deleted-admin", usersRestrictedByAdmin[restrictedUser.ID].Name)
	assert.Equal(t, "deleted-admin@example.com", usersRestrictedByAdmin[restrictedUser.ID].Email)
}

func TestStoreAdminActivationInfo(t *testing.T) {
	unittest.PrepareTestEnv(t)

	adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
	activatedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
	ctx, _ := contexttest.MockContext(t, "admin/users")

	require.True(t, storeAdminActivationInfo(ctx, activatedUser.ID, adminUser))

	activatedBy, err := user_model.GetUserSetting(t.Context(), activatedUser.ID, user_model.SettingsKeyAdminActivatedBy)
	require.NoError(t, err)
	assert.Equal(t, "1", activatedBy)
	activatedByName, err := user_model.GetUserSetting(t.Context(), activatedUser.ID, user_model.SettingsKeyAdminActivatedByName)
	require.NoError(t, err)
	assert.Equal(t, adminUser.Name, activatedByName)
	activatedByEmail, err := user_model.GetUserSetting(t.Context(), activatedUser.ID, user_model.SettingsKeyAdminActivatedByEmail)
	require.NoError(t, err)
	assert.Equal(t, adminUser.Email, activatedByEmail)
}

func TestStoreAdminGrantedInfo(t *testing.T) {
	unittest.PrepareTestEnv(t)

	adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
	grantedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
	ctx, _ := contexttest.MockContext(t, "admin/users")

	require.True(t, storeAdminGrantedInfo(ctx, grantedUser.ID, adminUser))

	grantedUnix, err := user_model.GetUserSetting(t.Context(), grantedUser.ID, user_model.SettingsKeyAdminGrantedUnix)
	require.NoError(t, err)
	grantedUnixValue, err := strconv.ParseInt(grantedUnix, 10, 64)
	require.NoError(t, err)
	assert.Positive(t, grantedUnixValue)
	grantedBy, err := user_model.GetUserSetting(t.Context(), grantedUser.ID, user_model.SettingsKeyAdminGrantedBy)
	require.NoError(t, err)
	assert.Equal(t, "1", grantedBy)
	grantedByName, err := user_model.GetUserSetting(t.Context(), grantedUser.ID, user_model.SettingsKeyAdminGrantedByName)
	require.NoError(t, err)
	assert.Equal(t, adminUser.Name, grantedByName)
	grantedByEmail, err := user_model.GetUserSetting(t.Context(), grantedUser.ID, user_model.SettingsKeyAdminGrantedByEmail)
	require.NoError(t, err)
	assert.Equal(t, adminUser.Email, grantedByEmail)
}

func TestStoreAdminProhibitLoginInfo(t *testing.T) {
	unittest.PrepareTestEnv(t)

	adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
	prohibitedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
	ctx, _ := contexttest.MockContext(t, "admin/users")

	require.True(t, storeAdminProhibitLoginInfo(ctx, prohibitedUser.ID, adminUser))

	prohibitedUnix, err := user_model.GetUserSetting(t.Context(), prohibitedUser.ID, user_model.SettingsKeyAdminProhibitLoginUnix)
	require.NoError(t, err)
	prohibitedUnixValue, err := strconv.ParseInt(prohibitedUnix, 10, 64)
	require.NoError(t, err)
	assert.Positive(t, prohibitedUnixValue)
	prohibitedBy, err := user_model.GetUserSetting(t.Context(), prohibitedUser.ID, user_model.SettingsKeyAdminProhibitLoginBy)
	require.NoError(t, err)
	assert.Equal(t, "1", prohibitedBy)
	prohibitedByName, err := user_model.GetUserSetting(t.Context(), prohibitedUser.ID, user_model.SettingsKeyAdminProhibitLoginByName)
	require.NoError(t, err)
	assert.Equal(t, adminUser.Name, prohibitedByName)
	prohibitedByEmail, err := user_model.GetUserSetting(t.Context(), prohibitedUser.ID, user_model.SettingsKeyAdminProhibitLoginByEmail)
	require.NoError(t, err)
	assert.Equal(t, adminUser.Email, prohibitedByEmail)
}

func TestStoreAdminRestrictedInfo(t *testing.T) {
	unittest.PrepareTestEnv(t)

	adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
	restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
	ctx, _ := contexttest.MockContext(t, "admin/users")

	require.True(t, storeAdminRestrictedInfo(ctx, restrictedUser.ID, adminUser))

	restrictedUnix, err := user_model.GetUserSetting(t.Context(), restrictedUser.ID, user_model.SettingsKeyAdminRestrictedUnix)
	require.NoError(t, err)
	restrictedUnixValue, err := strconv.ParseInt(restrictedUnix, 10, 64)
	require.NoError(t, err)
	assert.Positive(t, restrictedUnixValue)
	restrictedBy, err := user_model.GetUserSetting(t.Context(), restrictedUser.ID, user_model.SettingsKeyAdminRestrictedBy)
	require.NoError(t, err)
	assert.Equal(t, "1", restrictedBy)
	restrictedByName, err := user_model.GetUserSetting(t.Context(), restrictedUser.ID, user_model.SettingsKeyAdminRestrictedByName)
	require.NoError(t, err)
	assert.Equal(t, adminUser.Name, restrictedByName)
	restrictedByEmail, err := user_model.GetUserSetting(t.Context(), restrictedUser.ID, user_model.SettingsKeyAdminRestrictedByEmail)
	require.NoError(t, err)
	assert.Equal(t, adminUser.Email, restrictedByEmail)
}
