package me.qwq.doghouse.util;

import java.util.Set;

import org.apache.commons.lang3.Validate;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
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;


@Component
@Slf4j
public class SpringContextHolder implements ApplicationContextAware {

    private static ApplicationContext ctx = null;

    public static ApplicationContext getApplicationContext() {
        assertInjected();
        return ctx;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        assertInjected();
        return (T) ctx.getBean(name);
    }

    public static <T> T getBean(Class<T> requiredType) {
        assertInjected();
        return ctx.getBean(requiredType);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        ctx = applicationContext;
    }
    
    public static boolean updateBean(String name, Object newBean) {
        assertInjected();
        try {
            Object bean = ctx.getBean(name);
            Class<?> beanType = ctx.getType(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 (isNeverRefreshable(beanType)) {
                	log.warn("Bean {} ({}) belongs to never refreshable beans, ignore", name, beanType);
                    return false;
                }
            }
            BeanUtils.copyProperties(newBean, bean);
            rebindSingleton(name, bean);
            return true;
        } catch (Exception e) {
            log.error("Cannot rebind to " + name, e);
        }
        return false;
    }
    
    /**
     * 重绑定单例模式 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) {
        assertInjected();
        try {
            Object bean = ctx.getBean(name);

            DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry)
                    ctx.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 (isNeverRefreshable(bean.getClass())) {
                    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
    private static boolean isNeverRefreshable(Class<?> beanType) {
        Set<String> forbidden = StringUtils.commaDelimitedListToSet(
                ctx.getEnvironment().getProperty(
                        "spring.cloud.refresh.never-refreshable",
                        "com.zaxxer.hikari.HikariDataSource"));

        if (forbidden.contains(beanType.getName())) return true;
        return false;
    }
    
    /**
     * 检查ApplicationContext不为空.
     */
    private static void assertInjected() {
        Validate.validState(ctx != null,
                "applicaitonContext 属性未注入，请检查程序是否未正常启动");
    }
}