/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.component.jolokia.springboot;

import org.jolokia.server.core.config.ConfigKey;
import org.jolokia.support.spring.SpringJolokiaAgent;
import org.jolokia.support.spring.SpringJolokiaConfigHolder;
import org.jolokia.support.spring.SpringJolokiaLogHandlerHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(type = "org.jolokia.support.spring.SpringJolokiaAgent")
@EnableConfigurationProperties(JolokiaComponentConfiguration.class)
@ConditionalOnProperty(name = "camel.component.jolokia.enabled", havingValue = "true", matchIfMissing = true)
public class JolokiaComponentAutoConfiguration {

	private static final Logger LOG = LoggerFactory.getLogger(JolokiaComponentAutoConfiguration.class);

	protected static final String DEFAULT_CA_ON_K8S = "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt";

	@Autowired
	private JolokiaComponentConfiguration configuration;

	@Bean
	public SpringJolokiaAgent jolokia(final SpringJolokiaConfigHolder camelConfigHolder,
			final SpringJolokiaLogHandlerHolder camelLogHandlerHolder) {
		LOG.debug("creating agent instance");

		//in case of custom camelConfigHolder, the properties are merged
		final Map<String, String> mergedMap = mergeConfig(configuration.getServerConfig(), camelConfigHolder.getConfig());
		camelConfigHolder.setConfig(mergedMap);
		configuration.setServerConfig(mergedMap);

		final SpringJolokiaAgent agent = new SpringJolokiaAgent();
		agent.setLookupConfig(configuration.isLookupConfig());
		agent.setLookupServices(configuration.isLookupServices());
		agent.setSystemPropertiesMode(configuration.getSystemPropertiesMode());
		agent.setExposeApplicationContext(configuration.isExposeApplicationContext());
		agent.setConfig(camelConfigHolder);
		agent.setLogHandler(camelLogHandlerHolder);
		printConfiguration();
		return agent;
	}

	@Bean
	@ConditionalOnMissingBean(name = "camelLogHandlerHolder")
	public SpringJolokiaLogHandlerHolder camelLogHandlerHolder() {
		LOG.debug("jolokia logging using slf4j");
		final SpringJolokiaLogHandlerHolder logHandlerHolder = new SpringJolokiaLogHandlerHolder();
		logHandlerHolder.setType("slf4j");
		return logHandlerHolder;
	}

	@Bean
	@ConditionalOnMissingBean(name = "camelConfigHolder")
	public SpringJolokiaConfigHolder camelConfigHolder() {
		LOG.debug("jolokia configuration from properties");
		final SpringJolokiaConfigHolder springJolokiaConfigHolder = new SpringJolokiaConfigHolder();
		setDefaultConfigValue("host", "0.0.0.0");
		setDefaultConfigValue("autoStart", "true");
		if (configuration.isUseCamelRestrictor()
				&& !configuration.getServerConfig().containsKey(ConfigKey.RESTRICTOR_CLASS.getKeyValue())) {
			setDefaultConfigValue(ConfigKey.RESTRICTOR_CLASS.getKeyValue(),
					"org.apache.camel.component.jolokia.springboot.restrictor.CamelRestrictor");
		}
		boolean k8sSet = false;
		if (configuration.isKubernetesDiscover()) {
			LOG.debug("trying to discover k8s environment");
			final String caCert = configuration.isKubernetesUseDefaultCa() ? DEFAULT_CA_ON_K8S
					: configuration.getServerConfig().getOrDefault("caCert", DEFAULT_CA_ON_K8S);
			if (Files.exists(Path.of(caCert))) {
				setDefaultConfigValue("protocol", "https");
				setDefaultConfigValue("useSslClientAuthentication", "true");
				setDefaultConfigValue("caCert", caCert);
				k8sSet = true;
			} else {
				LOG.debug("kubernetesDiscover is enabled but the file {} does not exist, no additional properties will be set", caCert);
			}
		}
		//enable discovery on local JVM not in a POD
		if (!k8sSet && !Files.exists(Path.of(DEFAULT_CA_ON_K8S))) {
			setDefaultConfigValue("discoveryEnabled", "true");
		}
		springJolokiaConfigHolder.setConfig(configuration.getServerConfig());
		return springJolokiaConfigHolder;
	}

	private void setDefaultConfigValue(String key, String defaultValue) {
		final String configValue = configuration.getServerConfig().getOrDefault(key, defaultValue);
		LOG.debug("jolokia config set {} = {}", key, configValue);
		configuration.getServerConfig().put(key, configValue);
	}

	private void printConfiguration() {
		if (LOG.isTraceEnabled()) {
			LOG.trace("jolokia agent configuration: {}", configuration);
		}
	}

	private Map<String, String> mergeConfig(Map<String, String> fromConfig, Map<String, String> fromBean) {
		//in case no custom bean, it is the same object
		if (fromConfig.equals(fromBean)) {
			return fromConfig;
		}
		final Map<String, String> mergedMap = new HashMap<>(fromConfig);
		fromBean.forEach((beanKey, beanVal) -> {
			if (configuration.isConfigFromPropertiesFirst()) {
				mergedMap.put(beanKey, fromConfig.getOrDefault(beanKey, beanVal));
			} else {
				mergedMap.put(beanKey, beanVal);
			}
		});
		return mergedMap;
	}
}
