package com.sonic.common.stat;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.sonic.common.auth.AuthCaller;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.Order;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.sonic.common.auth.AuthInterceptor;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/**
 * api访问统计, 放在其他拦截器之后执行
 * @author code
 */
@Order
public class ApiStatInterceptor implements HandlerInterceptor, ApplicationListener<ContextRefreshedEvent> {

    private WebApplicationContext webApplicationContext;

    private BiConsumer<String, Integer> touchConsumer;

    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    LoadingCache<HandlerMethod, Optional<RequestMappingInfo>> mappingCache = CacheBuilder.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(1, TimeUnit.DAYS)
            .build(new CacheLoader<HandlerMethod, Optional<RequestMappingInfo>>() {
                @Override
                public Optional<RequestMappingInfo> load(HandlerMethod handler) throws Exception {
                    //spring mvc会对handler进行代理, 因此需要获取其被代理对象. 如果不是代理对象这里则应该是该对象自身
                    HandlerMethod originHandlerMethod = handler.getResolvedFromHandlerMethod() == null
                            ? handler
                            : handler.getResolvedFromHandlerMethod();

                    //从requestMappingHandlerMapping中找到当前handler的RequestMappingInfo
                    Optional<Map.Entry<RequestMappingInfo, HandlerMethod>> mappingInfo = requestMappingHandlerMapping.getHandlerMethods()
                            .entrySet().stream()
                            .filter(p -> p.getValue() == originHandlerMethod)
                            .findFirst();

                    return mappingInfo.map(e -> e.getKey());
                }
            });

    public ApiStatInterceptor(WebApplicationContext webApplicationContext, BiConsumer<String, Integer> touchConsumer) {
        this.touchConsumer = touchConsumer;
        this.webApplicationContext = webApplicationContext;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler == null
                || !handler.getClass().isAssignableFrom(HandlerMethod.class)) {
            return true;
        }

        Optional<RequestMappingInfo> unchecked = mappingCache.getUnchecked((HandlerMethod) handler);
        if (!unchecked.isPresent()) {
            return true;
        }

        String appId = getAppId(request);
        //从 RequestMappingInfo 找到匹配的当前 request 的 urlMapping
        Objects.requireNonNull(unchecked.get().getPatternsCondition().getMatchingCondition(request)).getPatterns()
                .forEach(e -> touchConsumer.accept(e + ":" + appId, 1));

        return true;
    }

    private String getAppId(HttpServletRequest request) {
        AuthCaller authCaller = (AuthCaller)request.getAttribute(AuthInterceptor.CALLER_PAYLOAD_KEY);
        return Optional.ofNullable(authCaller)
                .map(AuthCaller::getAppId)
                .orElse("unknown");
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        RequestMappingHandlerMapping mapping = webApplicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        this.requestMappingHandlerMapping = mapping;

        Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
        handlerMethods.entrySet()
                .stream()
                .map(e -> e.getKey().getPatternsCondition().getPatterns())
                .flatMap(e -> e.stream())
                .forEach(e -> touchConsumer.accept(e, 0));
    }
}
