package com.atlassian.stash.internal.discovery;

import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.request.RequestContext;
import com.atlassian.bitbucket.request.RequestManager;
import com.atlassian.bitbucket.util.Version;
import com.atlassian.fugue.Option;
import com.atlassian.sal.api.usersettings.UserSettings;
import com.atlassian.stash.internal.AbstractService;
import com.atlassian.stash.internal.user.InternalUserSettingsService;
import com.google.common.collect.Sets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

import javax.annotation.Nonnull;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.transaction.Transactional;
import java.util.EnumSet;
import java.util.Set;

import static com.atlassian.bitbucket.util.MoreCollectors.toImmutableSet;
import static com.atlassian.stash.internal.ApplicationConstants.USER_CREATED_VERSION;
import static java.util.Objects.requireNonNull;

@Service("featureDiscoveryService")
public class DefaultFeatureDiscoveryService extends AbstractService implements FeatureDiscoveryService {

    private static final String DISCOVERED_ALL_SESSION_KEY = "feature.discovery.all-discovered";
    private final AuthenticationContext authenticationContext;
    private final RequestManager requestManager;
    private final InternalUserSettingsService userSettingsService;

    @Autowired
    public DefaultFeatureDiscoveryService(AuthenticationContext authenticationContext,
                                          RequestManager requestManager,
                                          InternalUserSettingsService userSettingsService) {
        this.authenticationContext = authenticationContext;
        this.requestManager = requestManager;
        this.userSettingsService = userSettingsService;
    }

    @Nonnull
    @Override
    @PreAuthorize("isAuthenticated()")
    public Set<DiscoverableFeature> getUndiscovered() {
        return getIfUndiscovered(EnumSet.allOf(DiscoverableFeature.class));
    }

    @Nonnull
    @Override
    @PreAuthorize("isAuthenticated()")
    public Set<DiscoverableFeature> getIfUndiscovered(@Nonnull Set<DiscoverableFeature> features) {
        requireNonNull(features, "features");

        HttpSession session = getHttpSession();
        if (session != null && session.getAttribute(DISCOVERED_ALL_SESSION_KEY) == Boolean.TRUE) {
            return EnumSet.noneOf(DiscoverableFeature.class);
        }

        UserSettings userSettings = userSettingsService.getUserSettings(authenticationContext.getCurrentUser());

        //Check all discoverable features to see if we can set the session flag and short-circuit on the next
        //request from this user.
        Set<DiscoverableFeature> undiscovered = EnumSet.allOf(DiscoverableFeature.class).stream()
                .filter(feature -> !hasDiscovered(userSettings, feature))
                .collect(toImmutableSet());

        if (undiscovered.isEmpty() && session != null) {
            session.setAttribute(DISCOVERED_ALL_SESSION_KEY, Boolean.TRUE);
        }

        //Only return the undiscovered features queried by the client
        return Sets.intersection(features, undiscovered);
    }

    @PreAuthorize("isAuthenticated()")
    @Override
    @Transactional
    @SuppressWarnings("ConstantConditions")
    public void setDiscovered(@Nonnull Set<DiscoverableFeature> features) {
        requireNonNull(features, "features");

        if (!features.isEmpty()) {
            userSettingsService.updateUserSettings(authenticationContext.getCurrentUser(), builder -> {
                features.forEach(feature -> builder.put(feature.getKey(), true));
                return builder.build();
            });
        }
    }

    private HttpSession getHttpSession() {
        RequestContext requestContext = requestManager.getRequestContext();
        if (requestContext == null) {
            return null;
        }

        Object rawRequest = requestContext.getRawRequest();
        return rawRequest instanceof HttpServletRequest ? ((HttpServletRequest) rawRequest).getSession(true) : null;
    }

    private boolean hasDiscovered(@Nonnull UserSettings userSettings, @Nonnull DiscoverableFeature feature) {
        return userSettings.getBoolean(feature.getKey())
                .orElse(() -> feature.getLegacyKey().map(userSettings::getBoolean).orElse(Option.none()))
                .orElse(() -> userSettings.getString(USER_CREATED_VERSION)
                        .map(Version::new)
                        //If the user was created after the feature was introduced
                        //they should not see the feature discovery dialog
                        .map(version -> version.compareTo(feature.getVersion()) >= 0))
                //The user had no version information. It's safe to assume that they are a pre-existing user
                //who has not discovered the feature
                .getOrElse(Boolean.FALSE);
    }
}
