package com.atlassian.stash.internal.plugin.web.fragments;

import com.atlassian.plugin.PluginAccessor;
import com.atlassian.plugin.web.ContextProvider;
import com.atlassian.plugin.webresource.WebResourceIntegration;
import com.atlassian.sal.api.web.context.HttpContext;
import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

@Component
public class RequestCacheClientPageDataHandler implements ClientPageDataHandler {

    private PluginAccessor pluginAccessor;
    private final WebResourceIntegration webResourceIntegration;
    private final AuthenticationContext authenticationContext;
    private final HttpContext httpContext;

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

    private static final String PAGE_DATA_KEY = "page-data-plugin";

    @Autowired
    public RequestCacheClientPageDataHandler(PluginAccessor pluginAccessor, WebResourceIntegration webResourceIntegration,
                                             AuthenticationContext authenticationContext, HttpContext httpContext) {
        this.pluginAccessor = pluginAccessor;
        this.webResourceIntegration = webResourceIntegration;
        this.authenticationContext = authenticationContext;
        this.httpContext = httpContext;
    }

    private Map<String, Object> getPageData(String context, Map<String, Object> args) {

        Map<String, Object> pageDataByModuleKey = new HashMap<>();

        for (PageDataProviderModuleDescriptor descriptor :
                pluginAccessor.getEnabledModuleDescriptorsByClass(PageDataProviderModuleDescriptor.class)) {
            if (descriptor.getPageDataContexts().contains(context)) {
                ContextProvider provider = descriptor.getPageDataProvider();
                if (provider != null) {
                    Object pageData = provider.getContextMap(args);
                    if (pageData != null) {
                        pageDataByModuleKey.put(descriptor.getCompleteKey(), pageData);
                    }
                }
            }
        }

        return pageDataByModuleKey;
    }

    @Override
    public void requirePageData(String location, Map<String, Object> context) {
        Map<String, Map<String, Object>> pageDataByContext = getRequiredPageData();

        if (pageDataByContext.containsKey(location)) {
            log.debug("Context {} was required already. Ignoring.", location);
            return;
        }

        Map<String, Object> newContext = Maps.newHashMap(context);
        newContext.put("request", httpContext.getRequest());

        ApplicationUser user = authenticationContext.getCurrentUser();
        if (user != null) {
            newContext.put("currentUser", user);
        }

        pageDataByContext.put(location, getPageData(location, newContext));
    }

    public Map<String, Map<String, Object>> getRequiredPageData() {
        Map<String, Map<String, Object>> pageDataByContext = (Map<String, Map<String, Object>>) webResourceIntegration.getRequestCache().get(PAGE_DATA_KEY);

        if (pageDataByContext == null) {
            pageDataByContext = new HashMap<>();
            webResourceIntegration.getRequestCache().put(PAGE_DATA_KEY, pageDataByContext);
        }

        return pageDataByContext;
    }

    @Override
    public String toPageDataScript() {
        return toPageDataScript(true);
    }

    public String toPageDataScript(boolean clearCache) {
        Map<String, Map<String, Object>> pageDataByContext = getRequiredPageData();

        StringWriter sw = new StringWriter();

        try {
            PageDataUtils.appendScriptTag(sw, pageDataByContext);
        } catch (IOException e) {
            throw new RuntimeException("Error writing page-data output to String.", e);
        }

        if (clearCache) {
            pageDataByContext.clear();
        }

        return sw.toString();
    }
}
