package admin

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

	"code.gitea.io/gitea/models/auth"
	"code.gitea.io/gitea/models/db"
	repo_model "code.gitea.io/gitea/models/repo"
	user_model "code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/modules/context"
	"code.gitea.io/gitea/modules/convert"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/password"
	"code.gitea.io/gitea/modules/setting"
	api "code.gitea.io/gitea/modules/structs"
	"code.gitea.io/gitea/modules/web"
	"code.gitea.io/gitea/services/agit"
	container_service "code.gitea.io/gitea/services/packages/container"
	hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs"
)

func parseAuthSource(ctx *context.APIContext, u *user_model.User, sourceID int64, loginName string) {
	if sourceID == 0 {
		return
	}

	source, err := auth.GetSourceByID(sourceID)
	if err != nil {
		if auth.IsErrSourceNotExist(err) {
			ctx.Error(http.StatusUnprocessableEntity, "", err)
		} else {
			ctx.Error(http.StatusInternalServerError, "auth.GetSourceByID", err)
		}
		return
	}

	u.LoginType = source.Type
	u.LoginSource = source.ID
	u.LoginName = loginName
}

func EditUser(ctx *context.APIContext) {
	form := web.GetForm(ctx).(*hat_api.HatEditUserOption)
	parseAuthSource(ctx, ctx.ContextUser, form.SourceID, form.LoginName)
	if ctx.Written() {
		return
	}

	if len(form.Password) != 0 {
		if len(form.Password) < setting.MinPasswordLength {
			ctx.Error(http.StatusBadRequest, "PasswordTooShort", fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength))
			return
		}
		if !password.IsComplexEnough(form.Password) {
			err := errors.New("PasswordComplexity")
			ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
			return
		}
		pwned, err := password.IsPwned(ctx, form.Password)
		if pwned {
			if err != nil {
				log.Error(err.Error())
			}
			ctx.Data["Err_Password"] = true
			ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
			return
		}
		if ctx.ContextUser.Salt, err = user_model.GetUserSalt(); err != nil {
			ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
			return
		}
		if err = ctx.ContextUser.SetPassword(form.Password); err != nil {
			ctx.InternalServerError(err)
			return
		}
	}

	if form.MustChangePassword != nil {
		ctx.ContextUser.MustChangePassword = *form.MustChangePassword
	}

	if len(form.NewName) != 0 && ctx.ContextUser.Name != form.NewName {
		if err := handleUsernameChange(ctx, ctx.ContextUser, form.NewName); err != nil {
			ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
			return
		}
		ctx.ContextUser.Name = form.NewName
		ctx.ContextUser.LowerName = strings.ToLower(form.NewName)
	}

	ctx.ContextUser.LoginName = form.LoginName

	if form.FullName != nil {
		ctx.ContextUser.FullName = *form.FullName
	}
	var emailChanged bool
	if form.Email != nil {
		email := strings.TrimSpace(*form.Email)
		if len(email) == 0 {
			ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("email is not allowed to be empty string"))
			return
		}

		if err := user_model.ValidateEmail(email); err != nil {
			ctx.InternalServerError(err)
			return
		}

		emailChanged = !strings.EqualFold(ctx.ContextUser.Email, email)
		ctx.ContextUser.Email = email
	}
	if form.Website != nil {
		ctx.ContextUser.Website = *form.Website
	}
	if form.Location != nil {
		ctx.ContextUser.Location = *form.Location
	}
	if form.Description != nil {
		ctx.ContextUser.Description = *form.Description
	}
	if form.Active != nil {
		ctx.ContextUser.IsActive = *form.Active
	}
	if len(form.Visibility) != 0 {
		ctx.ContextUser.Visibility = api.VisibilityModes[form.Visibility]
	}
	if form.Admin != nil {
		ctx.ContextUser.IsAdmin = *form.Admin
	}
	if form.AllowGitHook != nil {
		ctx.ContextUser.AllowGitHook = *form.AllowGitHook
	}
	if form.AllowImportLocal != nil {
		ctx.ContextUser.AllowImportLocal = *form.AllowImportLocal
	}
	if form.MaxRepoCreation != nil {
		ctx.ContextUser.MaxRepoCreation = *form.MaxRepoCreation
	}
	if form.AllowCreateOrganization != nil {
		ctx.ContextUser.AllowCreateOrganization = *form.AllowCreateOrganization
	}
	if form.ProhibitLogin != nil {
		ctx.ContextUser.ProhibitLogin = *form.ProhibitLogin
	}
	if form.Restricted != nil {
		ctx.ContextUser.IsRestricted = *form.Restricted
	}

	if err := user_model.UpdateUser(ctx, ctx.ContextUser, emailChanged); err != nil {
		if user_model.IsErrEmailAlreadyUsed(err) ||
			user_model.IsErrEmailCharIsNotSupported(err) ||
			user_model.IsErrEmailInvalid(err) {
			ctx.Error(http.StatusUnprocessableEntity, "", err)
		} else {
			ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
		}
		return
	}
	log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, ctx.ContextUser.Name)

	ctx.JSON(http.StatusOK, convert.ToUser(ctx.ContextUser, ctx.Doer))
}

func handleUsernameChange(ctx *context.APIContext, user *user_model.User, newName string) error {
	// Non-local users are not allowed to change their username.
	if !user.IsLocal() {
		ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user"))
		return fmt.Errorf(ctx.Tr("form.username_change_not_local_user"))
	}

	// Check if user name has been changed
	if user.LowerName != strings.ToLower(newName) {
		if err := user_model.ChangeUserName(user, newName); err != nil {
			switch {
			case user_model.IsErrUserAlreadyExist(err):
				ctx.Error(http.StatusInternalServerError, "ChangeUserName", ctx.Tr("form.username_been_taken"))
			case user_model.IsErrEmailAlreadyUsed(err):
				ctx.Error(http.StatusInternalServerError, "ChangeUserName", ctx.Tr("form.email_been_used"))
			case db.IsErrNameReserved(err):
				ctx.Error(http.StatusInternalServerError, "ChangeUserName", ctx.Tr("user.form.name_reserved", newName))
			case db.IsErrNamePatternNotAllowed(err):
				ctx.Error(http.StatusInternalServerError, "ChangeUserName", ctx.Tr("user.form.name_pattern_not_allowed", newName))
			case db.IsErrNameCharsNotAllowed(err):
				ctx.Error(http.StatusInternalServerError, "ChangeUserName", ctx.Tr("user.form.name_chars_not_allowed", newName))
			default:
				ctx.Error(http.StatusInternalServerError, "ChangeUserName", err)
			}
			return err
		}
	} else {
		if err := repo_model.UpdateRepositoryOwnerNames(user.ID, newName); err != nil {
			ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
			return err
		}
	}

	// update all agit flow pull request header
	err := agit.UserNameChanged(user, newName)
	if err != nil {
		ctx.Error(http.StatusInternalServerError, "agit.UserNameChanged", err)
		return err
	}

	if err := container_service.UpdateRepositoryNames(ctx, user, newName); err != nil {
		ctx.Error(http.StatusInternalServerError, "UpdateRepositoryNames", err)
		return err
	}

	log.Trace("User name changed: %s -> %s", user.Name, newName)
	return nil
}
