package com.atlassian.stash.internal.web.users;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.mail.MailException;
import com.atlassian.bitbucket.mail.MailService;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.UserAdminService;
import com.atlassian.bitbucket.user.UserService;
import com.atlassian.soy.springmvc.errors.ErrorUtils;
import com.atlassian.stash.internal.crowd.CrowdControl;
import com.atlassian.stash.internal.web.auth.IgnoresXsrf;
import com.atlassian.stash.internal.web.soy.StashSoyResponseBuilder;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import javax.validation.Valid;

/**
 * Controller for the reset password dances.
 */
@Controller
public class PasswordResetController {

    private static final Logger log = LoggerFactory.getLogger(PasswordResetController.class);

    @VisibleForTesting
    static final String RESET_PASSWORD_FORM = "bitbucket.internal.users.passwordReset";
    @VisibleForTesting
    static final String RESET_PASSWORD_ACKNOWLEDGMENT = "bitbucket.internal.users.passwordResetReceived";
    private static final String CONFIRM_RESET_PASSWORD = "bitbucket.internal.users.passwordResetConfirm";
    private static final String CONFIRM_PASSWORD_SET = "bitbucket.internal.users.passwordSet";

    private final CrowdControl crowdControl;
    private final I18nService i18nService;
    private final MailService mailService;
    private final UserAdminService userAdminService;
    private final UserService userService;

    @Autowired
    public PasswordResetController(CrowdControl crowdControl, I18nService i18nService, MailService mailService,
                                   UserAdminService userAdminService, UserService userService) {
        this.crowdControl = crowdControl;
        this.i18nService = i18nService;
        this.mailService = mailService;
        this.userAdminService = userAdminService;
        this.userService = userService;
    }

    @RequestMapping(value = "/passwordreset", method = RequestMethod.GET, params = "token")
    public ModelAndView displayPasswordForm(@RequestParam String token) {
        ApplicationUser user = userAdminService.findUserByPasswordResetToken(token);

        return new ModelAndView(CONFIRM_RESET_PASSWORD, "form", new ResetPasswordForm(token, user));
    }

    @RequestMapping(value = "/passwordreset", method = RequestMethod.GET)
    public ModelAndView displayRequestForm() {
        return buildRequestForm(null, false, false);
    }

    @RequestMapping(value = "/passwordreset", method = RequestMethod.POST, params = "token")
    @IgnoresXsrf // This is already protected by the password reset token
    public ModelAndView resetPassword(@Valid ResetPasswordForm form, Errors errors) {
        if (errors.hasErrors()) {
            return new StashSoyResponseBuilder(CONFIRM_RESET_PASSWORD).put("form", form).putValidationErrors(errors).build();
        }

        try {
            userAdminService.resetPassword(form.getToken(), form.getPassword());
        } catch (Exception e) {
            return new StashSoyResponseBuilder(CONFIRM_RESET_PASSWORD).put("form", form).putFormErrors(e.getLocalizedMessage()).build();
        }
        return new ModelAndView(CONFIRM_PASSWORD_SET);
    }

    @RequestMapping(value = "/passwordreset", method = RequestMethod.POST)
    @IgnoresXsrf // Password reset is allowed anonymously so protecting this form from XSRF does nothing you couldn't already do
    public ModelAndView submitRequest(@RequestParam String identifier) {
        ApplicationUser user = userService.findUserByNameOrEmail(identifier);
        if (user == null) {
            return buildRequestForm(identifier, true, false);
        }
        if (!crowdControl.canResetPassword(user.getName())) {
            return buildRequestForm(identifier, false, true);
        }

        try {
            userAdminService.requestPasswordReset(user.getName());
        } catch (MailException e) {
            log.error("A password reset e-mail could not be sent to " + identifier, e);

            return buildRequestForm(identifier, false, false, i18nService.getMessage("bitbucket.web.user.password.reset.mail.error"));
        } catch (Exception e) {
            return buildRequestForm(identifier, false, false, ErrorUtils.getLocalizedMessageOfRootCause(e));
        }

        return new ModelAndView(RESET_PASSWORD_ACKNOWLEDGMENT);
    }

    private ModelAndView buildRequestForm(String identifier, boolean userNotFound, boolean userNotEditable,
                                          String... systemErrors) {
        return new StashSoyResponseBuilder(RESET_PASSWORD_FORM)
                .put("canSendEmail", mailService.isHostConfigured())
                .put("identifier", identifier)
                .put("userNotFound", userNotFound)
                .put("userNotEditable", userNotEditable)
                .putFormErrors(systemErrors)
                .build();
    }
}
