package org.apereo.cas.web.report;

import org.apache.commons.lang3.tuple.Pair;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.CasConfigurationPropertiesEnvironmentManager;
import org.apereo.cas.support.events.config.CasConfigurationModifiedEvent;
import org.apereo.cas.util.RegexUtils;
import org.apereo.cas.web.report.util.ControllerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.cloud.endpoint.RefreshEndpoint;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;

/**
 * Controller that exposes the CAS internal state and beans
 * as JSON. The report is available at {@code /status/config}.
 *
 * @author Misagh Moayyed
 * @since 4.1
 */
public class ConfigurationStateController extends BaseCasMvcEndpoint {

    private static final String VIEW_CONFIG = "monitoring/viewConfig";

    @Autowired
    private RefreshEndpoint refreshEndpoint;

    @Autowired
    private EnvironmentEndpoint environmentEndpoint;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Autowired
    private ConfigurableEnvironment environment;

    @Autowired(required = false)
    @Qualifier("configurationPropertiesEnvironmentManager")
    private CasConfigurationPropertiesEnvironmentManager configurationPropertiesEnvironmentManager;

    private CasConfigurationProperties casProperties;

    public ConfigurationStateController(final CasConfigurationProperties casProperties) {
        super("configstate", "/config", casProperties.getMonitor().getEndpoints().getConfigurationState(), casProperties);
        this.casProperties = casProperties;
    }

    /**
     * Handle request.
     *
     * @param request  the request
     * @param response the response
     * @return the model and view
     * @throws Exception the exception
     */
    @GetMapping
    public ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
        ensureEndpointAccessIsAuthorized(request, response);
        final Map<String, Object> model = new HashMap<>();
        final String path = request.getContextPath();
        ControllerUtils.configureModelMapForConfigServerCloudBusEndpoints(path, model);
        model.put("enableRefresh", isRefreshEnabled());
        model.put("enableUpdate", isUpdateEnabled());
        return new ModelAndView(VIEW_CONFIG, model);
    }

    private Boolean isRefreshEnabled() {
        return !casProperties.getEvents().isTrackConfigurationModifications() && refreshEndpoint.isEnabled()
                && environment.getProperty("spring.cloud.config.enabled", Boolean.class);
    }

    private Boolean isUpdateEnabled() {
        return !environment.getProperty("spring.cloud.config.enabled", Boolean.class);
    }

    /**
     * Gets configuration.
     *
     * @param request  the request
     * @param response the response
     * @return the configuration
     */
    @GetMapping("/getConfiguration")
    @ResponseBody
    public Map getConfiguration(final HttpServletRequest request, final HttpServletResponse response) {
        ensureEndpointAccessIsAuthorized(request, response);
        final Pattern pattern = RegexUtils.createPattern("(configService:|applicationConfig:).+(application|cas).+");

        if (environmentEndpoint.isEnabled()) {
            final Map results = new TreeMap();
            final Map<String, Object> environmentSettings = environmentEndpoint.invoke();
            environmentSettings.entrySet()
                    .stream()
                    .filter(entry -> pattern.matcher(entry.getKey()).matches())
                    .forEach(entry -> {
                        final Map<String, Object> keys = (Map<String, Object>) entry.getValue();
                        keys.keySet().forEach(key -> {
                            if (!results.containsKey(key)) {
                                final String propHolder = String.format("${%s}", key);
                                final String value = this.environment.resolvePlaceholders(propHolder);
                                results.put(key, environmentEndpoint.sanitize(key, value));
                            }
                        });
                    });

            return results;
        }

        return new LinkedHashMap();
    }

    /**
     * Update configuration map.
     *
     * @param jsonInput the json input
     * @param request   the request
     * @param response  the response
     */
    @PostMapping("/updateConfiguration")
    @ResponseBody
    public void updateConfiguration(@RequestBody final Map<String, Map<String, String>> jsonInput,
                                    final HttpServletRequest request,
                                    final HttpServletResponse response) {
        ensureEndpointAccessIsAuthorized(request, response);
        if (isUpdateEnabled()) {
            final Map<String, String> newData = jsonInput.get("new");
            configurationPropertiesEnvironmentManager.savePropertyForStandaloneProfile(Pair.of(newData.get("key"), newData.get("value")));
            eventPublisher.publishEvent(new CasConfigurationModifiedEvent(this, !casProperties.getEvents().isTrackConfigurationModifications()));
        }
    }
}
