package com.elab.redis.componts.config.scope;

import com.elab.redis.config.props.ConfigRefreshProperties;
import com.elab.redis.config.ElabRedisProperties;
import com.elab.redis.consts.CacheConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 核心类：
 * <p>
 * 1.从redis加载配置
 * 2.更新应用配置数据
 *
 * <p>
 * Enviroment propertySource 更新
 * '@Value' 注解配置数据更新,@Value需要在类上加@Scope("RefreshConfig")
 * </p>
 *
 * @Author: posiedon.wh
 */
public class RedisConfigRefreshCallback implements ApplicationContextAware {

    private Logger logger = LoggerFactory.getLogger(RedisConfigRefreshCallback.class);

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    Environment environment;

    private Map configMap = new ConcurrentHashMap();

    private static String redisSourceName = "redisSource";

    private static String scopeName = CacheConstants.REFRESH_CONFIG_SCOPE_NAME;

    private String redisConfigKey = "";

    @Autowired
    private ElabRedisProperties redisProperties;

    private ConfigurableApplicationContext applicationContext;

    private BeanDefinitionRegistry beanDefinitionRegistry;

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

    public String redisRefreshKey() {
        ConfigRefreshProperties configRefresh = redisProperties.getConfigRefresh();
        if (configRefresh != null) {
            return configRefresh.getConfigRefreshCacheKey();
        }
        return null;
    }

    @PostConstruct
    public void init() {
        if (!enableConfigRefresh()) {
            return;
        }

        RefreshConfigScopeRegistry refreshConfigScopeRegister =
            (RefreshConfigScopeRegistry)applicationContext.getBean(CacheConstants.REFRESH_CONFIG_SCOPE_REGISTRY);
        beanDefinitionRegistry = refreshConfigScopeRegister.getBeanDefinitionRegistry();

        redisConfigKey = redisRefreshKey();

        refreshEnv();

        syncInitConfig();
    }

    /**
     * 加载应用中默认的配置
     */
    private void syncInitConfig() {
        ConfigRefreshProperties configRefresh = redisProperties.getConfigRefresh();
        if (configRefresh == null || configRefresh.getDefaultConfig() == null
            || configRefresh.getDefaultConfig().size() == 0) {
            return;
        }

        Map<String, String> defaultConfig = configRefresh.getDefaultConfig();

        defaultConfig.forEach((K, V) -> {
            redisTemplate.opsForHash().putIfAbsent(redisConfigKey, K, V);
        });

    }

    //开始更新配置指令
    public void refresh() {
        logger.info("刷新配置...");
        this.refreshEnv();
    }

    public boolean enableConfigRefresh() {
        ConfigRefreshProperties configRefresh = redisProperties.getConfigRefresh();
        if (configRefresh != null) {
            return configRefresh.isEnable();
        }
        return false;
    }

    /**
     * 刷新IOC容器中的环境变量
     */
    private void refreshEnv() {

        // 是否在配置中开启了刷新配置中心的功能
        if (!enableConfigRefresh()) {
            return;
        }

        logger.info("start to refresh config from redis.");

        //创建sourceProperty
        if (!checkExistRedisSpringProperty()) {

            createRedisSpringProperty();

        }
        try {

            configMap = redisTemplate.opsForHash().entries(redisConfigKey);

            logger.debug("加载可刷新缓存中:" + redisConfigKey + "的key的数据,数据长度大小:" + configMap.size());

            // 更新单例配置工厂中的值
            RedisConfigRefreshHelper.getInstance().refresh(configMap);

        } catch (Exception e) {
            logger.error("load config from redis fail,reason:" + e.getMessage());
        }

        if (configMap.isEmpty()) {
            return;
        }

        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();

        PropertySource<?> source = propertySources.get(redisSourceName);

        // 更新容器中配置对象的值
        ConcurrentHashMap concurrentHashMap = (ConcurrentHashMap)source.getSource();

        // 先清空,然后再加载
        concurrentHashMap.clear();

        Set<Object> keys = configMap.keySet();

        for (Object key : keys) {
            concurrentHashMap.put(key, configMap.get(key));
        }

        // 刷新相关的bean
        refreshBean();

    }

    //创建redis-propertySources 环境资源
    private void createRedisSpringProperty() {
        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        OriginTrackedMapPropertySource redisSource = new OriginTrackedMapPropertySource(redisSourceName, configMap);
        propertySources.addFirst(redisSource);
    }

    /**
     * 检查是否已经将redis的初始化数据加载到容器中
     *
     * @return
     */
    private boolean checkExistRedisSpringProperty() {
        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        if (propertySources.contains(redisSourceName)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 从容器中查找bean中scope属性为
     */
    private void refreshBean() {
        String[] definitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : definitionNames) {
            BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(name);
            if (scopeName.equals(beanDefinition.getScope())) {
                //销毁bean 思考，如果这时候删除了bean，有没有问题？
                applicationContext.getBeanFactory().destroyScopedBean(name);
                //重新实例化
                applicationContext.getBean(name);
            }
        }
    }

}
