package com.atlassian.bitbucket.internal.mirroring.mirror.web;

import com.atlassian.bitbucket.internal.mirroring.mirror.InternalUpstreamServer;
import com.atlassian.bitbucket.internal.mirroring.mirror.InternalUpstreamService;
import com.atlassian.bitbucket.internal.mirroring.mirror.MirrorAuthenticationContext;
import com.atlassian.bitbucket.mirroring.mirror.MirroringMode;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamAccount;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamSettings;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamSettingsService;
import com.atlassian.bitbucket.server.ApplicationMode;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.soy.renderer.SoyTemplateRenderer;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.MediaType;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
import java.util.Set;

import static com.atlassian.bitbucket.internal.mirroring.mirror.web.MirrorWebConstants.TEMPLATE_MODULE_KEY;
import static java.util.Objects.requireNonNull;

/**
 * Servlet to configure the mirror. Rendered on the mirror but displayed upstream.
 */
public class MirrorConfigServlet extends HttpServlet {

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

    private static final String TEMPLATE_CONFIG = "bitbucket.internal.mirror.page.config";

    private final ApplicationPropertiesService propertiesService;
    private final InternalUpstreamService upstreamService;
    private final MirrorAuthenticationContext mirrorAuthenticationContext;
    private final UpstreamSettingsService upstreamSettingsService;
    private final SoyTemplateRenderer soyTemplateRenderer;

    public MirrorConfigServlet(ApplicationPropertiesService propertiesService,
                               InternalUpstreamService upstreamService,
                               @Qualifier("defaultMirrorAuthenticationContext") MirrorAuthenticationContext mirrorAuthenticationContext,
                               UpstreamSettingsService upstreamSettingsService,
                               SoyTemplateRenderer soyTemplateRenderer) {
        this.propertiesService = propertiesService;
        this.mirrorAuthenticationContext = mirrorAuthenticationContext;
        this.soyTemplateRenderer = soyTemplateRenderer;
        this.upstreamSettingsService = upstreamSettingsService;
        this.upstreamService = upstreamService;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (propertiesService.getMode() != ApplicationMode.MIRROR) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        InternalUpstreamServer upstream = upstreamService.get();
        if (upstream == null) {
            log.info("Unable to configure mirror: No upstream configured.");
            resp.sendRedirect(req.getContextPath());
            return;
        }

        //This checks that the request was JWT signed and that the JWT token contained the user that is
        //requesting the configuration page (aka impersonated user). We can't check that impersonated user has admin
        //permission because we don't have access to the upstream's permission graph. In this case we are assuming
        //that the upstream only generates valid JWT signed requests for this resource if the user is an admin on
        //the upstream.
        if (!mirrorAuthenticationContext.getImpersonatedUser().isPresent()) {

            boolean present = mirrorAuthenticationContext.getCurrentUpstreamUser().isPresent();
            if (present) {
                log.info("Unable to configure mirror: Request does not contain the impersonated user.");
            } else {
                log.info("Unable to configure mirror: Request incorrectly signed.");
            }

            resp.sendRedirect(req.getContextPath());
            return;
        }

        Optional<UpstreamSettings> settingsOptional = upstreamSettingsService.getSettings(upstream.getId());
        MirroringMode mode = MirroringMode.SELECTED_PROJECTS;
        Set<String> projectIds = ImmutableSet.of();
        if (settingsOptional.isPresent()) {
            UpstreamSettings settings = settingsOptional.get();
            mode = settings.getMode();

            if (mode == MirroringMode.SELECTED_PROJECTS) {
                projectIds = settings.getMirroredProjectIds();
            }
        }

        resp.setContentType(MediaType.HTML_UTF_8.toString());
        soyTemplateRenderer.render(resp.getWriter(), TEMPLATE_MODULE_KEY, TEMPLATE_CONFIG, ImmutableMap.of(
                "upstreamServer", ImmutableMap.builder()
                    .put("apiBaseUrl", upstream.getApiBaseUrl())
                    .put("baseUrl", upstream.getBaseUrl())
                    .put("id", upstream.getId())
                    .put("teamId", StringUtils.defaultString(upstream.getAccount().map(UpstreamAccount::getId).orElse(null)))
                    .put("teamName", StringUtils.defaultString(upstream.getAccount().map(UpstreamAccount::getName).orElse(null)))
                    .put("type", upstream.getType().getId()).build(),
                "mirrorMode", mode.getId(),
                "selectedProjects", projectIds,
                "mirrorName", requireNonNull(propertiesService.getDisplayName()),
                "mirrorBaseUrl", requireNonNull(propertiesService.getBaseUrl()).toString()
        ));
    }
}
