package me.qwq.doghouse.service;

import lombok.extern.slf4j.Slf4j;
import me.qwq.doghouse.annotation.ConfigInfo;
import me.qwq.doghouse.dao.ConfigMapper;
import me.qwq.doghouse.entity.RawConfig;
import me.qwq.doghouse.interfaces.IConfig;
import me.qwq.doghouse.util.SpringContextHolder;

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

import jakarta.annotation.PostConstruct;

import java.lang.reflect.InvocationTargetException;
import java.time.LocalDateTime;
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.annotation.Autowired;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.stereotype.Service;

/**
 * 配置类服务
 * @author Doghole
 *
 */
@Service
@Slf4j
public class ConfigService extends BaseServiceImpl<ConfigMapper, RawConfig> {

    @Autowired
    Reflections reflections;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    /**
     * 从
     * <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();
    
    
    @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("me.qwq.doghouse"));
        }

        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 <T> 实现了接口 IConfig 的网站配置类泛型
     * @param clazz 实现了接口 IConfig 的网站配置类
     * @return <T>
     */
    @SuppressWarnings("unchecked")
    private <T extends IConfig<T>> T getCache(Class<T> clazz) {
        return (T) classObjectCache.get(clazz);
    }

    /**
     * 将给定的 Config 的实体类缓存到缓存字典中，
     * 这里偷懒了用了运行时缓存，
     * 您也可以根据需要换成 Redis 缓存
     * @param <T> 实现了接口 IConfig 的网站配置类泛型
     * @param iConfig 实现了接口 IConfig 的网站配置类的实例化对象
     * @return
     */
    private <T extends IConfig<T>> void setCache(T iConfig) {
        classObjectCache.put(iConfig.getClass(), iConfig);
    }
    
    /**
     * 获取在继承了 IConfig 上的 @ConfigInfo 注解
     * @param <T>
     * @param clazz
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T extends IConfig<T>> ConfigInfo getConfigInfo(T iConfig) {
        return getConfigInfo(iConfig.getClass());
    }
    
    /**
     * 获取在继承了 IConfig 上的 @ConfigInfo 注解
     * @param <T>
     * @param clazz
     * @return
     */
    public static <T extends IConfig<T>> ConfigInfo getConfigInfo(Class<T> 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;
    }
    
    /**
     * 获取 Config
     * <p>当缓存中有时从缓存取，
     * 缓存没有时从数据库取并更新到缓存，
     * 数据库也没有时，
     * 如果指定的 Config 的 @ConfigInfo 注解开启了 initDefault = true，
     * 则尝试返回一个初始 Config，否则返回 null
     * @param <T>
     * @param clazz
     * @return
     */
    public <T extends IConfig<T>> T getConfig(Class<T> clazz) {
        if (classObjectCache.containsKey(clazz)) {
            try {
                return getCache(clazz);
            }
            catch (Exception e) {
                log.warn(
                    "Cannot get config info of " + clazz.toString() + 
                    " from staticAttributeClassCache, try to read from database", e);
            }
        }
        String field = fieldClassCache.inverse().get(clazz);
        ConfigInfo info = getConfigInfoByField(field);
        RawConfig rawConfig = getById(field);
        if (rawConfig == null) {
            if (info.initDefault()) {
                try {
                    T instance = clazz.getDeclaredConstructor().newInstance();
                    setCache(instance);
                    return instance;
                } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                    log.warn(
                        "Specific class " + clazz.toString() + 
                        " has @ConfigInfo annotation and enables initDefault, but init failed", e);
                    return null;
                }
            }
            return null;
        }
        T iConfig;
        try {
            iConfig = objectMapper.readValue(rawConfig.getConfigValue(), clazz);
        }
        catch (JsonProcessingException e) {
            throw new IllegalStateException(
                    "Failed to deserialize config: " + info.field(), e
            );
        }
        setCache(iConfig);
        return iConfig;
    }
    
    /**
     * 将 Config 保存到数据库中
     * @param <T>
     * @param iConfig
     * @return
     */
    @ManagedAttribute
    public <T extends IConfig<T>> boolean saveOrUpdate(T iConfig) {
        ConfigInfo info = getConfigInfo(iConfig);
        RawConfig rawConfig = getById(info.field());
        
        if (rawConfig == null) {
            rawConfig = new RawConfig().setConfigField(info.field())
                    .setConfigName(info.name());
        }
        else {
            rawConfig.setUpdateTime(LocalDateTime.now());
        }
        try {
            rawConfig.setConfigValue(
                    objectMapper.writeValueAsString(iConfig)
            );
        } catch (JsonProcessingException e) {
            throw new IllegalStateException(
                    "Failed to serialize config: " + info.field(), e
            );
        }
        iConfig.beforeSaving();
        
        if (saveOrUpdate(rawConfig)) {
            iConfig.afterSaving();
            setCache(iConfig);
            SpringContextHolder.updateBean(info.field() + "Config", iConfig);
            return true;
        }
        return false;
    }
    
    /**
     * 通过注解 @ConfigInfo 的 field() 获取注解本身
     * @param configField
     * @return
     */
    @SuppressWarnings("unchecked")
    public ConfigInfo getConfigInfoByField(String configField) {
        return getConfigInfo(fieldClassCache.get(configField));
    }
    
    
    /**
     * 通过注解 @ConfigInfo 的 field 从缓存中获取 Config 的类型
     * 
     * @param field
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T extends IConfig<T>> Class<T> getConfigClassByField(String field) {
        return (Class<T>) fieldClassCache.get(field);
    }

    /**
     * 首字母小写
     * 
     * @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;
    }
    
}