package quant.rich.emoney.interceptor;


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.*;

import jakarta.servlet.http.*;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;
import java.lang.annotation.*;

@Component
@Slf4j
public class EnumOptionsInterceptor implements HandlerInterceptor {

    @Autowired
    Reflections reflections;
    
    private Map<String, Map<String, Enum<?>>> options = null;
    
    private Map<String, String> optionNameMap = new HashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler) throws Exception {
        
        // 只在 Controller（HandlerMethod）里才做注入
        if (!(handler instanceof HandlerMethod)) {
            // 静态资源、图片、css、js 都会被 ResourceHttpRequestHandler 处理，
            // 这里一律跳过
            return true;
        }

        // 排除 @ResponseBody/json 接口
        HandlerMethod hm = (HandlerMethod) handler;
        if (hm.hasMethodAnnotation(ResponseBody.class) 
         || hm.getBeanType().isAnnotationPresent(RestController.class)) {
            return true;
        }
        
        if (options == null) {
            options = new HashMap<>();
            Set<Field> enumOptionFields = reflections.getFieldsAnnotatedWith(EnumOptions.class);
            for (Field f : enumOptionFields) {
                if (!f.isAnnotationPresent(EnumOptions.class)) continue;
                if (!Enum.class.isAssignableFrom(f.getType())) continue;
    
                // 拿到注解和名前缀
                EnumOptions anno = f.getAnnotation(EnumOptions.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());
        }
        return true;
    }
    
    /**
     * 将其注解到类型为 enum 的字段上，可以将 enum 注入到 Model 中，供 thymeleaf 使用
     * <p>例：某 entity 的某字段为：</p>
     * <code><b>private</b> Proxy.Type proxyType;</code>
     * <p>则在其上注解 @EnumOptions("ProxyTypeEnum"), 即可在 thymeleaf 中直接使用：</p>
     * ProxyTypeEnum.DIRECT
     * <p>如果只是注解 @EnumOptions，未指定 value，则在 thymeleaf 为字段名 + Options，上例中便为：</p>
     * ProxyTypeOptions.DIRECT
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public static @interface EnumOptions {
        /**
         * 注入到 Model 时用的属性名前缀，
         * 默认：字段名 + "Options"
         */
        String value() default "";
    }
    
    
}