package com.atlassian.stash.internal.lifecycle;

import com.atlassian.soy.renderer.SoyTemplateRenderer;
import com.atlassian.bitbucket.Product;
import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.spring.AbstractSmartLifecycle;
import com.atlassian.stash.internal.web.AboutController;
import com.atlassian.stash.internal.web.admin.ProjectAdminController;
import com.atlassian.stash.internal.web.soy.StashSoyViewResolver;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.bitbucket.util.DevModeUtils;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.bitbucket.util.ProgressImpl;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.io.output.NullWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * When the server has started completely, update the progress bar and the message to inform the user
 * Stash has started successfully.
 */
@Component
public class ServerLifecycle extends AbstractSmartLifecycle {

    private final ApplicationEventPublisher eventPublisher;
    private final ApplicationPropertiesService propertiesService;
    private final SoyTemplateRenderer templateRenderer;

    @Autowired
    public ServerLifecycle(ApplicationEventPublisher eventPublisher, ApplicationPropertiesService propertiesService,
                           SoyTemplateRenderer templateRenderer) {
        this.eventPublisher = eventPublisher;
        this.propertiesService = propertiesService;
        this.templateRenderer = templateRenderer;
    }

    @Override
    public int getPhase() {
        return ApplicationConstants.LIFECYCLE_PHASE_SERVER_READY;
    }

    @Override
    public void start() {
        if (!DevModeUtils.isEnabled()) { //Skip warming Soy in dev mode; it doesn't cache then
            warmSoy();
        }

        eventPublisher.publishEvent(new StartupProgressEvent(this,
                new ProgressImpl(Product.NAME + " has started successfully", 100)));

        super.start();
    }

    /**
     * Attempt to warm Soy caches during startup. This step will compile <i>most</i> of the Soy templates in the
     * system, which should make the first page loads after startup faster as less Soy compilation will occur.
     *
     * @since 3.9
     */
    private void warmSoy() {
        Map<String, Object> context;
        String templateName;
        if (propertiesService.isSetup()) {
            //If the application has been setup, render /projects. This gives the best performance for a
            //screen that's very commonly viewed first. But it's not possible if the base URL isn't set
            context = ImmutableMap.<String, Object>of(
                    "projectPage", PageUtils.createEmptyPage(PageUtils.newRequest(0, 25)));
            templateName = ProjectAdminController.PROJECT_LIST;
        } else {
            //Otherwise, render the About page. This page has very minimal dependencies and, most importantly,
            //doesn't use the NavBuilder. Almost every other page does, and that use causes exceptions which
            //are caught by WebPanelFunction and logged there beyond our control
            context = ImmutableMap.<String, Object>of(
                    "showLicenses", Boolean.FALSE,
                    "copyrightStartYear", Product.FIRST_YEAR_OF_RELEASE,
                    "copyrightEndYear", Product.FIRST_YEAR_OF_RELEASE);
            templateName = AboutController.VIEW;
        }

        eventPublisher.publishEvent(new StartupProgressEvent(this, new ProgressImpl("Preparing Soy templates", 95)));
        try {
            templateRenderer.render(new NullWriter(), StashSoyViewResolver.SERVER_SOY_MODULE_KEY, templateName, context);
        } catch (Exception ignored) {
            //Warming the Soy caches is a _best effort_ attempt to make the first page hit a little less
            //painful. If it goes wrong for any reason, just ignore it
        }
    }
}
