package quant.rich.emoney.util;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.Validate;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.util.ProxyUtils;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import lombok.extern.slf4j.Slf4j;
import quant.rich.emoney.config.ConstructionGuard;

/**
 * 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候取出ApplicaitonContext.
 * 
 */
@Component
@Slf4j
@Lazy(false)
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {

    private static ApplicationContext applicationContext = null;

    /**
     * 取得存储在静态变量中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        assertContextInjected();
        return applicationContext;
    }

    /**
     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        assertContextInjected();
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(Class<T> requiredType) {
        assertContextInjected();
        return applicationContext.getBean(requiredType);
    }

    /**
     * 清除SpringContextHolder中的ApplicationContext为Null.
     */
    public static void clearHolder() {
        if (log.isDebugEnabled()) {
            log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
        }
        applicationContext = null;
    }

    /**
     * 实现ApplicationContextAware接口, 注入Context到静态变量中.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringContextHolder.applicationContext = applicationContext;
    }

    /**
     * 实现DisposableBean接口, 在Context关闭时清理静态变量.
     */
    @Override
    public void destroy() throws Exception {
        SpringContextHolder.clearHolder();
    }

    public static boolean updateBean(String name, Object newBean) {
        if (SpringContextHolder.applicationContext != null) {
            if (!applicationContext.containsBean(name)) {
                log.debug("updateBean is only for initialized bean's updating, but {} doesn't exist or hasn't been initialized yet");
                return false;
            }
            try {
                Class<?> beanType = SpringContextHolder.applicationContext.getType(name);
                if (ConstructionGuard.isConstructing(beanType)) {
                    log.debug("updateBean is called but {} is initializing", name);
                    return false;
                }
                Object bean = SpringContextHolder.applicationContext.getBean(name);
                Class<?> newBeanType = newBean.getClass();
                if (beanType != newBeanType) {
                    log.warn("Cannot set new bean type {} to {}", newBeanType.getName(), name);
                    return false;
                }
                if (bean != null) {
                    if (AopUtils.isAopProxy(bean)) {
                        bean = ProxyUtils.getUserClass(bean);
                    }
                    if (getNeverRefreshable().contains(bean.getClass().getName())) {
                        return false;
                    }
                }
                Field[] declaredFields = beanType.getDeclaredFields();
                for (Field field : declaredFields) {
                    int modifiers = field.getModifiers();
                    if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) {
                        continue;
                    }
                    field.setAccessible(true);
                    try {
                        setFieldData(field, bean, field.get(newBean));
                    } catch (Exception e) {
                        log.error("Change bean failed", e);
                    }
                    field.setAccessible(false);
                }
                rebindSingleton(name, bean);
                return true;
            } catch (Exception e) {
                log.error("Cannot rebind to " + name, e);
            }
        }
        return false;
    }

    private static void setFieldData(Field field, Object bean, Object obj) throws Exception {
        Class<?> type = field.getType();
        if (obj == null) {
            field.set(bean, null);
        } else if (type.equals(String.class)) {
            field.set(bean, String.valueOf(obj));
        } else if (type.equals(Integer.class)) {
            field.set(bean, Integer.valueOf(String.valueOf(obj)));
        } else if (type.equals(Long.class)) {
            field.set(bean, Long.valueOf(String.valueOf(obj)));
        } else if (type.equals(Double.class)) {
            field.set(bean, Double.valueOf(String.valueOf(obj)));
        } else if (type.equals(Short.class)) {
            field.set(bean, Short.valueOf(String.valueOf(obj)));
        } else if (type.equals(Byte.class)) {
            field.set(bean, Byte.valueOf(String.valueOf(obj)));
        } else if (type.equals(Boolean.class)) {
            field.set(bean, Boolean.valueOf(String.valueOf(obj)));
        } else if (type.equals(Date.class)) {
            field.set(bean, new Date(Long.valueOf(String.valueOf(obj))));
        } else if (obj instanceof List) {
            field.set(bean, new ArrayList<>((List<?>) obj));
        } else {
            field.set(bean, obj);
        }
    }

    /**
     * 重绑定单例模式 Bean
     * 
     * @param name
     * @return
     * @see <a href=
     *      "http://blog.gxitsky.com/2023/01/31/SpringBoot-63-Auto-Refresh-Environment-Config-Data/">
     *      Spring Boot 2系列(六十三)：动态刷新环境配置和Bean属性值</a>
     */
    @ManagedOperation
    public static boolean rebindSingleton(String name, Object newBean) {
        if (SpringContextHolder.applicationContext != null) {
            try {
                Object bean = SpringContextHolder.applicationContext.getBean(name);

                DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) SpringContextHolder.applicationContext
                        .getAutowireCapableBeanFactory();
                if (bean != null) {
                    if (AopUtils.isAopProxy(bean)) {
                        bean = ProxyUtils.getUserClass(bean);
                    }
                    // TODO: determine a more general approach to fix this.
                    // see https://github.com/spring-cloud/spring-cloud-commons/issues/571
                    if (getNeverRefreshable().contains(bean.getClass().getName())) {
                        return false; // ignore
                    }
                    registry.destroySingleton(name);
                }
                registry.registerSingleton(name, newBean);
                return true;
            } catch (Exception e) {
                log.error("Cannot rebind to " + name, e);
            }
        }
        return false;
    }

    @ManagedAttribute
    public static Set<String> getNeverRefreshable() {
        String neverRefresh = SpringContextHolder.applicationContext.getEnvironment()
                .getProperty("spring.cloud.refresh.never-refreshable", "com.zaxxer.hikari.HikariDataSource");
        return StringUtils.commaDelimitedListToSet(neverRefresh);
    }

    /**
     * 检查ApplicationContext不为空.
     */
    private static void assertContextInjected() {
        Validate.validState(applicationContext != null, "applicaitonContext 属性未注入，请检查程序是否未正常启动");
    }
}