package com.atlassian.stash.internal.auth;

import com.atlassian.bitbucket.auth.HttpAuthenticationContext;
import com.atlassian.bitbucket.auth.HttpAuthenticationSuccessContext;
import com.atlassian.bitbucket.auth.HttpAuthenticationSuccessHandler;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.server.StandardFeature;
import com.atlassian.bitbucket.server.FeatureManager;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.util.UrlUtils;
import com.atlassian.sal.api.usersettings.UserSettings;
import com.atlassian.stash.internal.user.InternalUserSettingsService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.HttpHeaders;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;

import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static com.atlassian.stash.internal.ApplicationConstants.SEEN_BITBUCKET_UPGRADE_PAGE_KEY;
import static com.atlassian.stash.internal.ApplicationConstants.SHOW_GETTING_STARTED_PAGE_KEY;

/**
 * Authentication success handler that redirects the user to the specified 'next' URL. If no next URL is specified or
 * an invalid URL is specified (not local to Stash), the user is redirected to the application root.
 *
 * @since 2.3
 */
public class RedirectingAuthenticationSuccessHandler implements HttpAuthenticationSuccessHandler {

    private static final String KEY_NEXT_URL = "next";

    private final NavBuilder navBuilder;
    private final InternalUserSettingsService userSettingsService;
    private final FeatureManager featureManager;
    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    private final RequestCache requestCache = new HttpSessionRequestCache();

    public RedirectingAuthenticationSuccessHandler(@Nonnull NavBuilder navBuilder,
                                                   @Nonnull InternalUserSettingsService userSettingsService,
                                                   @Nonnull FeatureManager featureManager) {
        this.navBuilder = navBuilder;
        this.userSettingsService = userSettingsService;
        this.featureManager = featureManager;
    }

    @Override
    public boolean onAuthenticationSuccess(@Nonnull HttpAuthenticationSuccessContext context) throws ServletException, IOException {
        if (!HttpAuthenticationContext.METHOD_FORM.equals(context.getMethod())) {
            return false;
        }

        HttpServletRequest request = context.getRequest();
        HttpServletResponse response = context.getResponse();

        SavedRequest savedRequest = requestCache.getRequest(request, response);
        String nextUrl = getRedirectUrl(request);
        if (StringUtils.isNotBlank(nextUrl)) {
            // a 'next' parameter was provided, in which case we can discard the saved request
            if (savedRequest != null) {
                requestCache.removeRequest(request, response);
            }
        } else {
            if (savedRequest != null) {
                // no 'next' specified on the request, try saved request
                // Unfortunately the absolute URL saved by Spring will contain the MVC servlet path - remove it
                nextUrl = stripServletPath(savedRequest.getRedirectUrl(), navBuilder.buildAbsolute());
            } else if (nextUrl == null) {
                // still no 'next', default to the root
                nextUrl = navBuilder.buildAbsolute();
            }
        }

        nextUrl = resolvePendingRedirect(context.getUser(), nextUrl);

        redirectSafely(request, response, nextUrl);
        return true;
    }

    protected String getRedirectUrl(HttpServletRequest request) {
        return request.getParameter(KEY_NEXT_URL);
    }

    protected void redirectSafely(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
        // Please see com.atlassian.bitbucket.rest.util.CachePolicies for a description of private, no-cache,
        // and max-age cache control parameters

        response.setHeader(HttpHeaders.CACHE_CONTROL, "private, max-age=0, no-cache");
        response.setDateHeader(HttpHeaders.EXPIRES, 0);

        redirectStrategy.sendRedirect(request, response,
                UrlUtils.safeUrlForRedirect(url, navBuilder.buildAbsolute(), navBuilder.buildAbsolute()));
    }

    private String contextRelative(String nextUrl) {
        String contextPath = navBuilder.buildRelative();
        return nextUrl.startsWith(contextPath) ? nextUrl : contextPath + nextUrl;
    }

    private String resolvePendingRedirect(ApplicationUser user, String nextUrl) {
        UserSettings userSettings = userSettingsService.getUserSettings(user);

        boolean showGettingStarted = featureManager.isEnabled(StandardFeature.GETTING_STARTED) &&
                 userSettings.getBoolean(SHOW_GETTING_STARTED_PAGE_KEY).getOrElse(false);

        boolean showBitbucketUpgrade = featureManager.isEnabled(StandardFeature.UPGRADE_ONBOARDING) &&
                !userSettings.getBoolean(SEEN_BITBUCKET_UPGRADE_PAGE_KEY).getOrElse(false);

        if (showGettingStarted) {
            String redirectUrl = contextRelative(UrlUtils.getPathAndQuery(nextUrl));
            nextUrl = navBuilder.gettingStarted().next(redirectUrl).buildRelNoContext();
        } else if (showBitbucketUpgrade) {
            String redirectUrl = contextRelative(UrlUtils.getPathAndQuery(nextUrl));
            nextUrl = navBuilder.upgradeOnboarding().next(redirectUrl).buildRelNoContext();
        }
        return nextUrl;
    }

    /**
     * This method will strip the /mvc added by spring to the url it the prefix matches the url
     *
     */
    @VisibleForTesting
    static String stripServletPath(String url, String prefix) {
        String mvc = "/mvc/";
        if (url.startsWith(prefix + mvc)) {
            url = prefix + "/" + url.substring(prefix.length() + mvc.length());
        }
        return url;
    }
}
