package quant.rich.emoney.service;

import lombok.extern.slf4j.Slf4j;
import quant.rich.emoney.EmoneyAutoApplication;
import quant.rich.emoney.entity.config.SmartViewWriter;
import quant.rich.emoney.interfaces.ConfigInfo;
import quant.rich.emoney.interfaces.IConfig;
import quant.rich.emoney.util.SmartResourceResolver;
import quant.rich.emoney.util.SpringContextHolder;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

import io.micrometer.core.instrument.util.IOUtils;
import jakarta.annotation.PostConstruct;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.stereotype.Service;

/**
 * 配置类服务
 * 
 * @author Doghole
 * @see quant.rich.emoney.config.ConfigAutoRegistrar
 * @see quant.rich.emoney.config.ConfigServiceFactoryBean
 */
@Service
@Slf4j
public class ConfigService implements InitializingBean {

    @Autowired
    Reflections reflections;
    
    static final boolean isJar = "jar".equals(EmoneyAutoApplication.class.getProtectionDomain().getCodeSource().getLocation().getProtocol());
    static final ObjectMapper mapper = new ObjectMapper();
    
    static {
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 从
     * <b><code> field </code></b>
     * 到<b><code> ConfigClass </code></b>的<b>一对一</b>映射关系<br>
     * 例: <code>"deviceInfo" &lt;--&gt; DeviceInfoConfig.class</code><br>
     */
    @SuppressWarnings("rawtypes")
    BiMap<String, Class<? extends IConfig>> fieldClassCache = HashBiMap.create();
    
    /**
     * 从
     * <b><code>ConfigClass</code></b>
     * 到对应实例化对象的映射关系。可以理解为对应类的运行时缓存。
     */
    @SuppressWarnings("rawtypes")
    private Map<Class<? extends IConfig>, Object> classObjectCache = new HashMap<>();
    
    static final Pattern FIELD_PATTERN = Pattern.compile("^[a-zA-Z_$][a-zA-Z0-9_$]+$");
 
    /**
     * 初始化各项配置缓存，只有添加了 @ConfigInfo 的配置类才会被识别并缓存
     */
    @SuppressWarnings("unchecked")
    @PostConstruct
    public void afterPropertiesSet() throws Exception {
        
        if (reflections == null) {
            reflections = new Reflections(new ConfigurationBuilder()
                    .addScanners(Scanners.MethodsAnnotated, Scanners.SubTypes, Scanners.TypesAnnotated)
                    .forPackages("quant.rich.emoney"));
        }

        Set<Class<?>> configClasses = reflections.getTypesAnnotatedWith(ConfigInfo.class);

        for (Class<?> clazz : configClasses) {
            ConfigInfo info = clazz.getAnnotation(ConfigInfo.class);
            String field = info.field();
            if (StringUtils.isEmpty(field)) {
                // 如果 ConfigInfo 注解上 field() 为空，则默认从类名初始化该 field
                // 规则是该配置类名必须以 Config 结尾，否则不合法
                field = clazz.getSimpleName();
                int lastIndexOfConfig = field.lastIndexOf("Config");
                if (lastIndexOfConfig == -1) {
                    log.warn(
                        "Annotation @ConfigInfo doesn't set a field, "
                        + "so try to init it from className \"{}\", but "
                        + "it doesn't end with \"Config\", which is invalid, so ignore.", field);
                    continue;
                }
                field = field.substring(0, lastIndexOfConfig);
                field = firstToLower(field);
            }
            else if (!FIELD_PATTERN.matcher(field).matches()) {
                // field 必须是合法的 Java 变量名
                log.warn("Invalid field name \"{}\", it cannot be a legal java variable name, so ignore.", field);
                continue;
            }
            if (fieldClassCache.containsKey(field)) {
                Class<? extends IConfig<?>> existedClass = 
                        (Class<? extends IConfig<?>>) fieldClassCache.get(field);
                log.warn("Found class {} with expected name {} which is already assigned to class {}, ignored.",
                        clazz.getName(), field, existedClass.getName());
                continue;
            }
            Class<? extends IConfig<?>> configClass = (Class<? extends IConfig<?>>) clazz;
            fieldClassCache.put(field, configClass);
        }
    }

    /**
     * 根据 Config 的类名在缓存字典中取其值，这里偷懒了用了运行时缓存，您也可以根据需要换成 Redis 缓存
     * 
     * @param <Config> 实现了接口 ConfigInterface 的网站配置类泛型
     * @param clazz        实现了接口 ConfigInterface 的网站配置类
     * @return <T>
     */
    @SuppressWarnings("unchecked")
    private <Config extends IConfig<Config>> Config getCache(Class<Config> clazz) {
        return (Config) classObjectCache.get(clazz);
    }

    /**
     * 将给定的 Config 的实体类缓存到缓存字典中， 这里偷懒了用了运行时缓存， 您也可以根据需要换成 Redis 缓存
     * 
     * @param <Config> 实现了接口 ConfigInterface 的网站配置类泛型
     * @param config   实现了接口 ConfigInterface 的网站配置类的实例化对象
     * @return
     */
    private <Config extends IConfig<Config>> void setCache(Config config) {
        classObjectCache.put(config.getClass(), config);
    }

    /**
     * 获取注释在继承了 ConfigInterface 上的接口
     * 
     * @param <Config>
     * @param config
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <Config extends IConfig<Config>> ConfigInfo getConfigInfo(Config config) {
        return getConfigInfo(config.getClass());
    }
    
    /**
     * 获取注释在继承了 ConfigInterface 上的接口
     * 
     * @param <Config>
     * @param clazz
     * @return
     */
    public static <Config extends IConfig<Config>> ConfigInfo getConfigInfo(Class<Config> clazz) {
        ConfigInfo info = clazz.getAnnotation(ConfigInfo.class);
        if (info == null) {
            throw new RuntimeException("Cannot get config info of " + clazz.toString()
                    + ", please check if @ConfigInfo annotation is set correctly");
        }
        return info;
    }

    /**
     * 通过注解 @ConfigInfo 的 field 获取其本身
     * 
     * @param field
     * @return
     */
    @SuppressWarnings("unchecked")
    public ConfigInfo getConfigInfoByField(String field) {
        return getConfigInfo(fieldClassCache.get(field));
    }

    /**
     * 通过注解 @ConfigInfo 的 field 从缓存中获取 Config 的类型
     * 
     * @param field
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T extends IConfig<T>> Class<T> getConfigClassByField(String field) {
        return (Class<T>) fieldClassCache.get(field);
    }

    /**
     * 获取给定类型的 Config 实例
     * <p>
     * 当缓存中有时从缓存取，缓存没有时从数据库取并更新到缓存，数据库也没有时，
     * 如果指定的 Config 的 @ConfigInfo
     * 注解开启了 initDefault = true， 则尝试返回一个初始 Config，否则返回 null.
     * 原本不存在的 Config 如果 @ConfigInfo 开启了 save()，会持久化到本地。
     * 
     * @param <Config>
     * @param clazz 配置类
     * @return
     * @see ConfigInfo
     * @see #getOrCreateConfig(String)
     * 
     */
    public <Config extends IConfig<Config>> Config getConfig(Class<Config> clazz) {
        if (classObjectCache.containsKey(clazz)) {
            try {
                return getCache(clazz);
            } catch (Exception e) {
                log.warn("Cannot get config info of {} from cache, try to read from database", clazz.toString(), e);
            }
        }
        String field = fieldClassCache.inverse().get(clazz);
        // 和 doghouse 不同，配置不是从数据库拿的，而是从本地文件系统
        Config config = getOrCreateConfig(field);
        if (config == null) {
            throw new RuntimeException("Cannot get or create config field named " + field);
        }
        setCache(config);
        return config;
    }

    /**
     * 持久化 Config，同时设置缓存、更新注入
     * 
     * @param <Config>
     * @param config
     * @return
     */
    @ManagedAttribute
    public <Config extends IConfig<Config>> boolean saveOrUpdate(Config config) {
        config.beforeSaving();
        String field = fieldClassCache.inverse().get(config.getClass());
        ConfigInfo info = getConfigInfoByField(field);
        
        SmartViewWriter writer = new SmartViewWriter();
        String configJoString = writer.writeWithSmartView(config, IConfig.Views.Persistence.class);
        
        if (info.save()) {
            try {
                String filePath = getConfigFilePath(field, false);
                SmartResourceResolver.saveText(filePath, configJoString);
            } catch (IOException e) {
                log.error("Cannot write config to local file {}.json, error: {}", field, e.getMessage());
                return false;
            }
        }
        config.afterSaving();
        setCache(config);
        SpringContextHolder.updateBean(field + "Config", config);
        return true;
    }
    
    /**
     * 从指定路径获取配置文件并转换为实例对象
     * @param <Config>
     * @param path
     * @param configClass
     * @return
     */
    private <Config extends IConfig<Config>> Config getFromFile(String path, Class<Config> configClass) {
        String configString;
        Config config = null;
        
        try  {
            // 此处只是读取文件，并不关心该文件是否可写
            configString = IOUtils.toString(SmartResourceResolver.loadResource(path), Charset.defaultCharset());
        } catch (IOException e) {
            String field = fieldClassCache.inverse().get(configClass);
            log.warn("Cannot read config {}.json: {}", field, e.getMessage());
            return config;
        }
        try {
            config = mapper.readValue(configString, configClass);
        } catch (Exception e) {
            String field = fieldClassCache.inverse().get(configClass);
            log.warn("Cannot parse configString of {} to Config", field);
            e.printStackTrace();
        }
        return config;
    }

    /**
     * 根据 config field 获取 Config，将读取本地存储的配置，若失败则试图初始化并存储
     * 
     * @param configField
     * @return
     */
    @SuppressWarnings("unchecked")
    public <Config extends IConfig<Config>> Config getOrCreateConfig(String field) {
        
        Class<Config> configClass = (Class<Config>) fieldClassCache.get(field);
        if (configClass == null) {
            log.warn("Cannot get config class from fieldClassCache, field name {} not exist", field);
            return null;
        }
        ConfigInfo info = getConfigInfo(configClass);
        
        Config config = null;
        
        // 先从持久化路径拿
        // fallback 流程：如果持久化文件不存在或者从中反序列化失败，则走 fallback 流程
        // 如果 fallback 也失败，走初始化流程。但是初始化出的内容不参与 fallback 的持久化
        // 也就是无论如何，fallback 都不应由程序来写入
        String filePath = getConfigFilePath(field, false);
        config = getFromFile(filePath, configClass);
        boolean needSave = false;
        if (config == null) {
            log.info("Cannot init config from local file of {}Config, try fallback", field);
            // 走 fallback 流程
            config = getFromFile(getConfigFilePath(field, true), configClass);
            needSave = true;
        }
        
        if (config == null) {
            log.info("Cannot init config from fallback file of {}Config", field);
            if (info.initDefault()) {
                // 还为 null 且允许初始化默认值，则初始化一份
                log.info("Init {} by default non-args constructor", field);
                try {
                    config = 
                            configClass.getDeclaredConstructor()
                            .newInstance();
                    needSave = true;
                } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                        | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                    // 一般是初始化方法内出现未被捕获的错误
                    log.warn("Specific class " + configClass.toString()
                            + " has @ConfigInfo annotation and enables initDefault, but init failed", e);
                }
            }
        }
        
        if (config == null) {
            log.warn("Cannot read or init from default/fallback for config {}Config, it will be null", field);
        }
        else if (needSave) {
            // 走了 fallback 或者初始化了则保存一份
            saveOrUpdate(config);
        }
        
        return config;
    }

    /**
     * 根据 config field 生成配置文件路径
     * 
     * @param configField
     * @return
     */
    private static String getConfigFilePath(String field, boolean isFallback) {
        StringBuilder sb = new StringBuilder();
        sb.append("./conf/system/");
        sb.append(field);
        sb.append(isFallback ? ".fallback.json" : ".json");
        return sb.toString();
    }

    /**
     * 首字母小写
     * 
     * @param s
     * @return
     */
    private static String firstToLower(String s) {
        if (StringUtils.isNotEmpty(s)) {
            char[] cs = s.toCharArray();
            if (cs[0] >= 'A' && cs[0] <= 'Z') {
                cs[0] += 32;
            }
            return String.valueOf(cs);
        }
        return s;
    }
}