package link.at17.mid.tushare.component;


import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import link.at17.mid.tushare.annotation.StaticAttribute;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;


/**
 * 注入拦截器
 */
@Component
@Slf4j
public class StaticAttributeInterceptor implements HandlerInterceptor {

    @Autowired
    Reflections reflections;
    
    /**
     * 静态注入的类缓存
     */
    Map<String, Class<?>> staticAttributeClassCache;
    
    /**
     * 静态注入的枚举字段缓存
     */
    Map<String, Map<String, Enum<?>>> options = null;
    Map<String, String> optionNameMap = new HashMap<>();
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        injectNecessary(request, handler);
        return true;
    }
    
    /**
     * 为前端模板注入变量
     * @param request
     */
    public void injectNecessary(HttpServletRequest request, Object handler) {
     
        // 只在 Controller（HandlerMethod）里才做注入
        if (!(handler instanceof HandlerMethod)) {
            // 静态资源、图片、css、js 都会被 ResourceHttpRequestHandler 处理，
            // 这里一律跳过
            return;
        }
        
        // 排除 @ResponseBody/json 接口
        HandlerMethod hm = (HandlerMethod) handler;
        if (hm.hasMethodAnnotation(ResponseBody.class) 
         || hm.getBeanType().isAnnotationPresent(RestController.class)) {
            return;
        }
        
        // 查找添加了 @StaticAttribute 注解的类，将其中的枚举注入前端供使用
        if (staticAttributeClassCache == null) {
            log.debug("init static attribute classes.");
            staticAttributeClassCache = new HashMap<>();
            
            
            Set<Class<?>> staticClasses = reflections.getTypesAnnotatedWith(StaticAttribute.class);
            for (Class<?> staticClass : staticClasses) {
                StaticAttribute sa = staticClass.getAnnotation(StaticAttribute.class);
                String name = staticClass.getSimpleName();
                if (StringUtils.isNotBlank(sa.value())) {
                    name = sa.value();
                }
                
                if (staticAttributeClassCache.containsKey(name)) {
                    // 缓存中已经存在了这个名字，直接忽略
                    log.warn("StaticAttribute 注解的类 {} 指定的名称 {} 已经存在, 旧值会被覆盖",
                            staticClass.getName(),
                            name);
                }
                staticAttributeClassCache.put(name, staticClass);
                log.debug("{} 类以 {} 为名称注入", staticClass, name);
            }
        }
        else if (staticAttributeClassCache.isEmpty()) {
            // 跑完一次后，缓存仍然为空，可能不正常，提示一下
            log.warn("初始化后，StaticAttribute 缓存为空，这仅在无任何类被 @StaticAttribute 注解的情况下出现。如果这与您的期望不符，请检查");
        }
        Iterator<Map.Entry<String, Class<?>>> iterator = staticAttributeClassCache.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Class<?>> entry = iterator.next();
            request.setAttribute(entry.getKey(), entry.getValue());
        }
        
        if (options == null) {
            options = new HashMap<>();
            Set<Field> enumOptionFields = reflections.getFieldsAnnotatedWith(StaticAttribute.class);
            for (Field f : enumOptionFields) {
                if (!f.isAnnotationPresent(StaticAttribute.class)) continue;
                if (!Enum.class.isAssignableFrom(f.getType())) continue;
    
                // 拿到注解和名前缀
                StaticAttribute anno = f.getAnnotation(StaticAttribute.class);
                String prefix = anno.value().isBlank() ?
                        f.getName() + "Enum" :
                        anno.value();
                
                prefix = prefix.toUpperCase().charAt(0) + prefix.substring(1);
                
                if (optionNameMap.containsKey(prefix)) {
                    log.warn("EnumOption name {}:{} has already been taken by {}, please check",
                            prefix, f.getType().getName(), optionNameMap.get(prefix));
                    continue;
                }
                
                optionNameMap.put(prefix, f.getType().getName());
    
                // enum 值列表
                @SuppressWarnings("unchecked")
                Class<? extends Enum<?>> enumType = (Class<? extends Enum<?>>) f.getType();
                Enum<?>[] constants = enumType.getEnumConstants();
    
                // 构造 Map<name, constant>
                Map<String, Enum<?>> optionsMap = new LinkedHashMap<>();
                for (Enum<?> c : constants) {
                    optionsMap.put(c.name(), c);
                }
    
                // 放到 Model 里
                options.put(prefix, optionsMap);
            }

        }
        for (Entry<String, Map<String, Enum<?>>> entry : options.entrySet()) {
            request.setAttribute(entry.getKey(), entry.getValue());
            log.debug("Inject enums {}: {} to request {}",
                    entry.getKey(), optionNameMap.get(entry.getKey()),
                    request.getRequestURI());
        }
    }
    
}
