package com.changhu.config;

import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ClassUtil;
import com.changhu.common.annotation.IsWhiteList;
import com.changhu.common.annotation.JsonBody;
import com.changhu.filter.BodyWrapperFilter;
import com.changhu.interceptor.JsonBodyInterceptor;
import com.changhu.interceptor.OpenApiAuthInterceptor;
import com.changhu.interceptor.UserTypeInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * author: luozhun
 * desc: WebConfig
 * createTime: 2023/8/18 10:56
 */
@Slf4j
@Configuration
public class WebConfig implements WebMvcConfigurer {

    private final List<String> whiteList = new ArrayList<>();

    /**
     * 扫描所有Controller
     */
    private static Set<Class<?>> scanRestController() {
        //需要过滤出JsonBody
        Set<Class<?>> classes = ClassUtil.scanPackage("com.changhu", clazz -> !JsonBody.class.equals(clazz));
        return classes.stream().filter(aClass -> {
            //判断类上是否有Controller注解
            if (aClass.isAnnotationPresent(Controller.class) || aClass.isAnnotationPresent(RestController.class)) {
                return true;
            }
            Annotation[] annotations = aClass.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation.annotationType().isAnnotationPresent(Controller.class) || annotation.annotationType().isAnnotationPresent(RestController.class)) {
                    return true;
                }
            }
            return false;
        }).collect(Collectors.toSet());
    }

    public WebConfig() {
        Set<String> w = new HashSet<>();
        for (Class<?> clazz : scanRestController()) {
            //类上的注解
            RequestMapping classRequestMapping = AnnotatedElementUtils.findMergedAnnotation(clazz, RequestMapping.class);
            IsWhiteList clazzIsWhiteList = clazz.getAnnotation(IsWhiteList.class);
            if (classRequestMapping != null && clazzIsWhiteList != null) {
                //直接放行当前控制器的所有方法
                w.addAll(Arrays.stream(classRequestMapping.value()).map(path -> {
                    if (!path.startsWith("/")) {
                        path = "/" + path;
                    }
                    if (!path.endsWith("/")) {
                        path = path + "/";
                    }
                    return path + "**";
                }).toList());
                continue;
            }
            //看方法上是否有白名单注解
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                IsWhiteList methodIsWhiteList = method.getAnnotation(IsWhiteList.class);
                RequestMapping methodRequestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
                if (methodRequestMapping != null && methodIsWhiteList != null) {
                    List<List<String>> list1 = Arrays.stream(methodRequestMapping.value()).map(path -> {
                        if (!path.startsWith("/")) {
                            path = "/" + path;
                        }
                        if (path.endsWith("/")) {
                            path = path.substring(0, path.length() - 1);
                        }
                        List<String> list = new ArrayList<>();
                        if (classRequestMapping != null && CollUtil.isNotEmpty(Arrays.asList(classRequestMapping.value()))) {
                            for (String p : classRequestMapping.value()) {
                                if (!p.startsWith("/")) {
                                    p = "/" + p;
                                }
                                if (p.endsWith("/")) {
                                    p = p.substring(0, p.length() - 1);
                                }
                                list.add(p + path);
                            }
                        } else {
                            list.add(path);
                        }
                        return list;
                    }).toList();
                    w.addAll(list1.stream().flatMap(List::stream).toList());
                }
            }
        }
        log.info("加载路由白名单:{}", w);
        whiteList.addAll(w);

        //网站图标
        whiteList.add("/favicon.ico");
        //druid console
        whiteList.add("/druid/**");
        //knife4j
        whiteList.add("/doc.html/**");
        whiteList.add("/static/**");
        whiteList.add("/swagger-resources");
        whiteList.add("/**webjars/**");
        whiteList.add("/v3/**");
    }

    @Override
    public void addInterceptors(@NotNull InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器，校验规则为 StpUtil.checkLogin() 登录校验。
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                .excludePathPatterns(whiteList);
        // 注册jsonBody拦截器 用于标识是否需要JsonResult返回
        registry.addInterceptor(new JsonBodyInterceptor());
        // 注册用户类型拦截器 用于校验当前用户是否匹配操作客户端
        registry.addInterceptor(new UserTypeInterceptor());
        // 注册开放接口认证拦截器 用于校验第三方是否授权
        registry.addInterceptor(new OpenApiAuthInterceptor())
                .addPathPatterns("/open/**");
    }

    @Bean
    public FilterRegistrationBean<BodyWrapperFilter> loggingFilter() {
        FilterRegistrationBean<BodyWrapperFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new BodyWrapperFilter());
        registrationBean.addUrlPatterns("/open/*"); // 指定过滤的URL模式
        registrationBean.setOrder(1000);
        return registrationBean;
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "OPTION", "PUT", "DELETE")
                .allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
                        "Access-Control-Request-Headers", "Authorization", "Token", "*")
                .allowCredentials(true)
                .maxAge(3600);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("doc.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

}
