/*
 * Copyright 2013-present the original author or authors.
 *
 * Licensed 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
 *
 *      https://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 com.alibaba.cloud.nacos.refresh;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.client.NacosPropertySource;
import com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

public class NacosPropertySourceRefreshListener implements BeanPostProcessor, SmartApplicationListener, ApplicationContextAware {

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

	private Map<String, ConfigurationPropertiesBean> beans = new HashMap<>();

	private @Nullable ApplicationContext applicationContext;

	private final AtomicBoolean ready = new AtomicBoolean(false);

	NacosConfigManager nacosConfigManager;

	public NacosPropertySourceRefreshListener(NacosConfigManager nacosConfigManager) {
		this.nacosConfigManager = nacosConfigManager;
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (this.applicationContext == null) {
			return bean;
		}
		ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean.get(this.applicationContext, bean,
				beanName);
		if (propertiesBean != null) {
			this.beans.put(beanName, propertiesBean);
		}
		return bean;
	}

	@Override
	public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
		return ApplicationReadyEvent.class.isAssignableFrom(eventType) || NacosConfigRefreshEvent.class.isAssignableFrom(eventType);
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	public void handle(ApplicationReadyEvent event) {
		this.ready.compareAndSet(false, true);
	}

	@Override
	public void onApplicationEvent(ApplicationEvent event) {

		if (event instanceof ApplicationReadyEvent) {
			handle((ApplicationReadyEvent) event);
		}
		else if (event instanceof NacosConfigRefreshEvent) {
			handle((NacosConfigRefreshEvent) event);
		}
	}

	public void handle(NacosConfigRefreshEvent event) {
		if (this.ready.get() && this.applicationContext != null) { // don't handle events before app is ready
			if (!applicationContext.containsBean("nacosConfigSpringCloudRefreshEventListener")) {
				log.info("Event received " + event.getEventDesc());

				NacosPropertySourceBuilder builder = new NacosPropertySourceBuilder(
						nacosConfigManager.getConfigService(),
						nacosConfigManager.getNacosConfigProperties().getTimeout(),
						nacosConfigManager.getNacosConfigProperties().getNamespace());
				String sourceName = String.join(NacosConfigProperties.COMMAS, event.dataId, event.group);
				ConfigurableEnvironment environment = ((ConfigurableApplicationContext) applicationContext).getEnvironment();
				MutablePropertySources target = environment.getPropertySources();
				PropertySource<?> prevpropertySource = target.get(sourceName);
				if (prevpropertySource instanceof NacosPropertySource) {
					String dataId = event.getDataId();
					String group = event.getGroup();
					if (dataId == null || group == null) {
						log.warn("Event dataId or group is null, skipping refresh");
						return;
					}
					NacosPropertySource prev = (NacosPropertySource) prevpropertySource;
					String fileExtension = prev.getSuffix() != null ? prev.getSuffix() : "properties";
					NacosPropertySource newProperSource = builder.build(dataId, group,
							fileExtension, prev.isRefreshable());
					target.replace(sourceName, newProperSource);
					log.info("Replace Nacos Property Source : " + sourceName);
				}

			}

		}
	}
}
