package com.cpop.core.config;

import com.cpop.common.constant.Constants;
import com.cpop.common.utils.StringUtils;
import com.cpop.core.filter.JwtAuthenticationFilter;
import com.cpop.core.filter.RepeatableFilter;
import com.cpop.core.gateway.miniProgram.MiniProgramAuthenticationFilter;
import com.cpop.core.gateway.miniProgram.MiniProgramAuthenticationProvider;
import com.cpop.core.gateway.sys.SysAuthenticationFilter;
import com.cpop.core.gateway.sys.SysAuthenticationProvider;
import com.cpop.core.handler.*;
import com.cpop.core.utils.JwtUtils;
import com.cpop.core.utils.PasswordEncoder;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Arrays;
import java.util.Collections;

/**
 * @author DB
 * @Description: Security配置中心
 * @since 2023-08-05 8:01
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity
public class SecurityConfig implements WebMvcConfigurer {

    @Autowired
    private JwtLogoutSuccessHandler jwtLogoutSuccessHandler;

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    private JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Autowired
    private RepeatableFilter repeatableFilter;

    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;

    @Autowired
    private CpopConfig cpopConfig;

    @Autowired
    private JwtUtils jwtUtils;

    /**
     * Security过滤拦截配置
     *
     * @param http 请求
     * @return SecurityFilterChain 认证过滤链
     * @throws Exception 异常
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                //登录配置(多登陆模式自定义成功与失败->现已注释)
                /*.formLogin(formLogin -> {
                    formLogin.successHandler(loginSuccessHandler)
                            .failureHandler(loginFailureHandler);
                })*/
                //登出配置
                .logout(logout -> logout.logoutSuccessHandler(jwtLogoutSuccessHandler))
                //禁用session
                .sessionManagement(sessionManagement -> {
                    sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
                })
                //配置拦截规则
                .authorizeHttpRequests(authorizeHttpRequests -> {
                    authorizeHttpRequests.antMatchers(StringUtils.split(jwtUtils.getWhiteList(), ",")).permitAll()
                            .anyRequest().authenticated();
                })
                //异常处理器
                .exceptionHandling(exceptionHandling -> {
                    exceptionHandling.authenticationEntryPoint(jwtAuthenticationEntryPoint).accessDeniedHandler(jwtAccessDeniedHandler);
                })
                //自定义认证管理器
                .authenticationManager(authenticationManager())
                //流重复使用
                .addFilterBefore(repeatableFilter, UsernamePasswordAuthenticationFilter.class)
                // 添加 JWT 过滤器，JWT 过滤器在退出认证过滤器之前
                .addFilterBefore(authFilter(), LogoutFilter.class)
                //.addFilterAt(oamLoginFilter(http.getSharedObject(AuthenticationManager.class)), UsernamePasswordAuthenticationFilter.class)
                //系统登陆
                .addFilterAt(sysAuthenticationFilter(http.getSharedObject(AuthenticationManager.class)), UsernamePasswordAuthenticationFilter.class)
                //小程序认证
                .addFilterAt(miniProgramAuthenticationFilter(http.getSharedObject(AuthenticationManager.class)), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    /**
     * @Description: 小程序登陆认证方式(可自定义其他模式)
     * @param authenticationManager 认证管理器
     * @return MiniProgramAuthenticationFilter
     * @author DB
     * @Date: 2023/8/24 0024 10:43
     */
    @Bean
    public SysAuthenticationFilter sysAuthenticationFilter(AuthenticationManager authenticationManager) {
        SysAuthenticationFilter filter = new SysAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager);
        filter.setAuthenticationSuccessHandler(loginSuccessHandler);
        filter.setAuthenticationFailureHandler(loginFailureHandler);
        return filter;
    }

    @Bean
    public SysAuthenticationProvider sysAuthenticationProvider() {
        return new SysAuthenticationProvider();
    }

    /**
     * @Description: 小程序登陆认证方式(可自定义其他模式)
     * @param authenticationManager 认证管理器
     * @return MiniProgramAuthenticationFilter
     * @author DB
     * @Date: 2023/8/24 0024 10:43
     */
    @Bean
    public MiniProgramAuthenticationFilter miniProgramAuthenticationFilter(AuthenticationManager authenticationManager) {
        MiniProgramAuthenticationFilter filter = new MiniProgramAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager);
        filter.setAuthenticationSuccessHandler(loginSuccessHandler);
        filter.setAuthenticationFailureHandler(loginFailureHandler);
        return filter;
    }

    @Bean
    public MiniProgramAuthenticationProvider miniProgramAuthenticationProvider() {
        return new MiniProgramAuthenticationProvider();
    }

    /**
     * 获取AuthenticationManager（认证管理器），登录时认证使用
     * @return AuthenticationManager
     */
    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(Arrays.asList(sysAuthenticationProvider(), miniProgramAuthenticationProvider()));
    }

    /**
     * 密码明文加密方式配置
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder();
    }

    /**
     * jwt 校验过滤器，从 http 头部 Authorization 字段读取 token 并校验
     * @return JwtAuthenticationFilter
     */
    @Bean
    public JwtAuthenticationFilter authFilter() {
        return new JwtAuthenticationFilter();
    }

    /**
     * 配置跨源访问(CORS)
     * @return CorsConfigurationSource
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
        configuration.applyPermitDefaultValues();
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    /**
     * @param registry 资源注册
     * @Description: 资源映射
     * @Author DB
     * @Date: 2023/5/27 13:57
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //本地文件上传路径
        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
                .addResourceLocations("file:" + cpopConfig.getProfile() + "/");
    }
}
