package quant.rich.emoney.config;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import lombok.extern.slf4j.Slf4j;
import quant.rich.emoney.interfaces.ConfigInfo;
import quant.rich.emoney.interfaces.IConfig;

/**
 * 实现自动化注册 Config<p>
 * Config 放在 quant.rich.emoney.entity.config 包下并且必须实现 IConfig 接口
 * @see quant.rich.emoney.interfaces.IConfig
 */
@Slf4j
@DependsOn("configService")
public class ConfigAutoRegistrar implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        scanner.addIncludeFilter(new AssignableTypeFilter(IConfig.class));
        scanner.addIncludeFilter(new AnnotationTypeFilter(ConfigInfo.class));

        scanner.findCandidateComponents("quant.rich.emoney.entity.config").forEach(beanDefinition -> {
            String className = beanDefinition.getBeanClassName();
            try {
                
                // 确保其 field 规则与 configService 内 field 生成规则一致，即：
                // 如果 @ConfigInfo 指定了 field 的，使用该 field + "Config"
                // 作为 beanName，否则使用首字母小写的 simpleClassName 作为
                // beanName，且 simpleClassName 无论如何必须以 Config 作为结尾。
                Class<?> clazz = Class.forName(className);
                String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase()
                        + clazz.getSimpleName().substring(1);

                if (!IConfig.class.isAssignableFrom(clazz)) {
                    log.error("Ignore config class {} which is not implemented IConfig interface", beanName);
                    return;
                }
                
                if (!beanName.endsWith("Config")) {
                    log.error("Ignore config class {} which class name is not end with \"Config\"", beanName);
                    return;
                }
                
                ConfigInfo info = clazz.getAnnotation(ConfigInfo.class);
                if (info == null) {
                    log.error("Ignore config class {} which is not annotated with @ConfigInfo", clazz.getName());
                    return;
                }
                if (StringUtils.isNotBlank(info.field())) {
                    beanName = info.field() + "Config";
                }
                
                BeanDefinitionBuilder factoryBean = BeanDefinitionBuilder
                        .genericBeanDefinition(ConfigServiceFactoryBean.class)
                        .addConstructorArgValue(clazz);
                /**
                 * 注意此处通过 factoryBean 创建 bean 不意味着 FactoryBean.getObject() 方法
                 * 会被立即执行。Spring 默认会在 bean 被使用时才会创建。如果该 bean 对程序
                 * 启动很重要，需要立即创建的，需在其类上添加 @Bean 及 @Lazy(false) 注解，
                 * 确保一旦准备好，相应的 bean 就会被创建
                 */
                registry.registerBeanDefinition(beanName, factoryBean.getBeanDefinition());
                log.info("Add config class {} to bean register", beanName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException("Cannot found specific config class: " + className, e);
            }
        });
    }
}