package com.atlassian.stash.internal.user;

import com.atlassian.bitbucket.auth.AuthenticationException;
import com.atlassian.bitbucket.auth.CaptchaRequiredAuthenticationException;
import com.atlassian.bitbucket.auth.HttpAuthenticationHandler;
import com.atlassian.bitbucket.auth.IncorrectCaptchaAuthenticationException;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.util.UncheckedOperation;
import com.atlassian.stash.internal.auth.CaptchaResponse;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Provides CAPTCHA services around authentication and signup operations.
 * <p>
 * CAPTCHA processing is divided into two steps: CAPTCHA validation and authentication when the CAPTCHA details have
 * been validated. This split is made because authentication is pluggable and there may be multiple
 * {@link HttpAuthenticationHandler authentication handlers} enabled. If multiple
 * authentication handlers require CAPTCHA processing, the CAPTCHA should only be checked once.
 *
 * @since 2.3
 */
public interface CaptchaService {

    /**
     * Second step in the CAPTCHA authentication process. Authentication is divided into two steps because
     * authentication is pluggable and there can be multiple
     * {@link HttpAuthenticationHandler authentication handlers} registered in the application.
     * If multiple authentication handlers require CAPTCHA processing, the CAPTCHA should only be checked once.
     * <p>
     * When {@code authenticationOperation} is called and authentication fails, the failed login
     * count is increased by one. If the failed attempt count exceeds the maximum allowed number of
     * failed attempts, a {@link CaptchaRequiredAuthenticationException}
     * is thrown.
     * <p>
     * If authentication succeeds, the failed attempt count is reset.
     *
     * @param captchaTicket a {@link #checkCaptcha(String, com.atlassian.stash.internal.auth.CaptchaResponse) verified}
     *                     Captcha token
     * @param authenticationOperation the operation that performs authentication
     * @throws CaptchaRequiredAuthenticationException if CAPTCHA is required
     * @throws IncorrectCaptchaAuthenticationException if incorrect CAPTCHA
     *         details were provided
     * @throws AuthenticationException if {@code authenticationOperation}
     *         throws it
     * @return the authenticated user, or {@code null} if the provided {@code authenticationOperation}
     *         opted out of authentication.
     */
    ApplicationUser authenticateWithCaptcha(@Nonnull CaptchaTicket captchaTicket,
                                            @Nonnull UncheckedOperation<ApplicationUser> authenticationOperation);

    /**
     * Tests whether CAPTCHA should be applied for the provided {@code username} and if so whether
     * a valid {@code captchaResponse} has been provided. If no CAPTCHA is required, a CapchaToken
     * is returned without inspecting {@code captchaResponse}.
     * <p>
     * If CAPTCHA is required, there are three possible outcomes:
     * <ul>
     *     <li>
     *         no {@code captchaResponse} is provided - a
     *         {@link CaptchaRequiredAuthenticationException} is thrown.
     *     </li>
     *     <li>
     *         an invalid {@code captchaResponse} is provided - a
     *         {@link IncorrectCaptchaAuthenticationException} is thrown.
     *     </li>
     *     <li>
     *         a valid {@code captchaResponse} is provided - a CaptchaTicket is returned.
     *     </li>
     * </ul>
     *
     * @param username the user that will be authenticated by {@code authenticationOperation}
     * @param captchaResponse the CAPTCHA details provided by the user
     * @throws CaptchaRequiredAuthenticationException if CAPTCHA is required
     * @throws IncorrectCaptchaAuthenticationException if incorrect CAPTCHA
     *         details were provided
     * @throws AuthenticationException if {@code authenticationOperation}
     *         throws it
     * @return a token that can be passed to
     *         {@link #authenticateWithCaptcha(CaptchaTicket, UncheckedOperation)}
     */
    @Nonnull
    CaptchaTicket checkCaptcha(@Nonnull String username, @Nullable CaptchaResponse captchaResponse);

    /**
     * Clears any CAPTCHA challenge that may constrain the user with the supplied username when they authenticate.
     * Additionally any counter or metric that contributed towards the user being issued the CAPTCHA challenge
     * (for instance too many consecutive failed logins) will also be reset.
     *
     * @param username the user whose captcha challenge should be cleared
     */
    void clearCaptchaChallenge(@Nonnull String username);

    /**
     * Determines if the user with the supplied username will be challenged with a CAPTCHA the next time they
     * authenticate. Failure to correctly answer a CAPTCHA challenge when required denies the user login to Stash.
     *
     * @param username the name of the user to check
     * @return true if that user must answer a CAPTCHA challenge the next time they authenticate, false otherwise
     */
    boolean isCaptchaChallenged(@Nonnull String username);

    /**
     * Determines if the response to a CAPTCHA challenge is correct.
     * @param captchaResponse the CAPTCHA details provided by the user
     * @return true if the response is correct, false otheriwse
     */
    boolean validateCaptchaResponse(@Nonnull CaptchaResponse captchaResponse);

}
