package com.zsElectric.boot.config;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.ArrayUtil;
import com.zsElectric.boot.common.util.electric.queryToken.ThirdPartyJwtAuthFilter;
import com.zsElectric.boot.config.property.SecurityProperties;
import com.zsElectric.boot.core.filter.RateLimiterFilter;
import com.zsElectric.boot.security.filter.CaptchaValidationFilter;
import com.zsElectric.boot.security.filter.TokenAuthenticationFilter;
import com.zsElectric.boot.security.handler.MyAccessDeniedHandler;
import com.zsElectric.boot.security.handler.MyAuthenticationEntryPoint;
import com.zsElectric.boot.security.provider.SmsAuthenticationProvider;
import com.zsElectric.boot.security.provider.WxMiniAppCodeAuthenticationProvider;
import com.zsElectric.boot.security.provider.WxMiniAppPhoneAuthenticationProvider;
import com.zsElectric.boot.security.provider.WxMiniAppPhoneCodeAuthenticationProvider;
import com.zsElectric.boot.security.token.TokenManager;
import com.zsElectric.boot.security.service.SysUserDetailsService;
import com.zsElectric.boot.system.service.ConfigService;
import com.zsElectric.boot.system.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * Spring Security 配置类
 *
 * @author Ray.Hao
 * @since 2023/2/17
 */
@Slf4j
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final RedisTemplate<String, Object> redisTemplate;
    private final PasswordEncoder passwordEncoder;

    private final TokenManager tokenManager;
    private final WxMaService wxMaService;
    private final UserService userService;
    private final SysUserDetailsService userDetailsService;

    private final CodeGenerator codeGenerator;
    private final ConfigService configService;
    private final SecurityProperties securityProperties;
    private final ThirdPartyJwtAuthFilter thirdPartyAuthFilter;
    
    // 注入 UserInfoService 用于小程序认证
    private final com.zsElectric.boot.business.service.UserInfoService userInfoService;

    // 仅针对第三方URL的安全过滤链，只挂第三方认证过滤器
    @Bean
    @Order(1)
    public SecurityFilterChain thirdPartySecurityFilterChain(HttpSecurity http) throws Exception {

        log.info("第三方认证过滤器: {}", thirdPartyAuthFilter);

        String[] thirdPartyUrls = securityProperties.getThirdPartyUrls();
        if (thirdPartyUrls == null || thirdPartyUrls.length == 0) {
            log.warn("第三方URL未配置或为空，使用占位符避免匹配所有请求");
            thirdPartyUrls = new String[]{"/__thirdparty_noop__"};
        }
        log.info("========== 配置第三方 SecurityFilterChain, urls: {} ==========", (Object) thirdPartyUrls);

        http
                .securityMatcher(thirdPartyUrls)  // 只匹配第三方URL
                .authorizeHttpRequests(registry -> registry
                        .anyRequest().authenticated()
                )
                .exceptionHandling(configurer ->
                        configurer
                                .authenticationEntryPoint(new MyAuthenticationEntryPoint())
                                .accessDeniedHandler(new MyAccessDeniedHandler())
                )
                .sessionManagement(configurer ->
                        configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .csrf(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
                // 只加第三方认证过滤器
                .addFilterBefore(thirdPartyAuthFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }


    /**
     * 配置安全过滤链 SecurityFilterChain
     */
    @Bean
    @Order(2)
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        
        log.info("========== 配置 SecurityFilterChain ==========");

        return http
                .authorizeHttpRequests(requestMatcherRegistry -> {
                            // 配置无需登录即可访问的公开接口
                            String[] ignoreUrls = securityProperties.getIgnoreUrls();
                    log.info("安全白名单 ignore-urls: {}", (Object) ignoreUrls);
                            if (ArrayUtil.isNotEmpty(ignoreUrls)) {
                                requestMatcherRegistry.requestMatchers(ignoreUrls).permitAll();
                            }

                            // 配置完全绕过安全检查的路径（原 unsecuredUrls）
                            String[] unsecuredUrls = securityProperties.getUnsecuredUrls();
                    log.info("非安全端点 unsecured-urls: {}", (Object) unsecuredUrls);
                            if (ArrayUtil.isNotEmpty(unsecuredUrls)) {
                                requestMatcherRegistry.requestMatchers(unsecuredUrls).permitAll();
                            }

                            // 其他所有请求需登录后访问
                            requestMatcherRegistry.anyRequest().authenticated();
                        }
                )
                .exceptionHandling(configurer ->
                        configurer
                                .authenticationEntryPoint(new MyAuthenticationEntryPoint()) // 未认证异常处理器
                                .accessDeniedHandler(new MyAccessDeniedHandler()) // 无权限访问异常处理器
                )

                // 禁用默认的 Spring Security 特性，适用于前后端分离架构
                .sessionManagement(configurer ->
                        configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态认证，不使用 Session
                )
                .csrf(AbstractHttpConfigurer::disable)      // 禁用 CSRF 防护，前后端分离无需此防护机制
                .formLogin(AbstractHttpConfigurer::disable) // 禁用默认的表单登录功能，前后端分离采用 Token 认证方式
                .httpBasic(AbstractHttpConfigurer::disable) // 禁用 HTTP Basic 认证，避免弹窗式登录
                // 禁用 X-Frame-Options 响应头，允许页面被嵌套到 iframe 中
                .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
                // 限流过滤器
                .addFilterBefore(new RateLimiterFilter(redisTemplate, configService), UsernamePasswordAuthenticationFilter.class)
                // 验证码校验过滤器
                .addFilterBefore(new CaptchaValidationFilter(redisTemplate, codeGenerator), UsernamePasswordAuthenticationFilter.class)
                // 验证和解析过滤器
                .addFilterBefore(new TokenAuthenticationFilter(tokenManager), UsernamePasswordAuthenticationFilter.class)
                .build();
    }

    /**
     * 配置Web安全自定义器，以忽略特定请求路径的安全性检查。
     * <p>
     * 该配置用于指定哪些请求路径不经过Spring Security过滤器链。通常用于静态资源文件。
     * 注意：这些警告可以忽略，因为Swagger等静态资源需要完全绕过过滤器链才能正常访问。
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> {
            String[] unsecuredUrls = securityProperties.getUnsecuredUrls();
            if (ArrayUtil.isNotEmpty(unsecuredUrls)) {
                web.ignoring().requestMatchers(unsecuredUrls);
            }
        };
    }

    /**
     * 默认密码认证的 Provider
     */
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        return daoAuthenticationProvider;
    }

    /**
     * 微信小程序Code认证Provider
     */
    @Bean
    public WxMiniAppCodeAuthenticationProvider wxMiniAppCodeAuthenticationProvider() {
        return new WxMiniAppCodeAuthenticationProvider(userService, wxMaService);
    }

    /**
     * 微信小程序手机号认证Provider
     */
    @Bean
    public WxMiniAppPhoneAuthenticationProvider wxMiniAppPhoneAuthenticationProvider() {
        return new WxMiniAppPhoneAuthenticationProvider(userService, wxMaService);
    }

    /**
     * 微信小程序手机号Code认证Provider（新版接口）
     * <p>
     * 使用 UserInfoService 直接操作 c_user_info 表
     */
    @Bean
    public WxMiniAppPhoneCodeAuthenticationProvider wxMiniAppPhoneCodeAuthenticationProvider() {
        return new WxMiniAppPhoneCodeAuthenticationProvider(userInfoService, wxMaService);
    }

    /**
     * 短信验证码认证 Provider
     */
    @Bean
    public SmsAuthenticationProvider smsAuthenticationProvider() {
        return new SmsAuthenticationProvider(userService, redisTemplate);
    }

    /**
     * 认证管理器
     */
    @Bean
    public AuthenticationManager authenticationManager(
            DaoAuthenticationProvider daoAuthenticationProvider,
            WxMiniAppCodeAuthenticationProvider wxMiniAppCodeAuthenticationProvider,
            WxMiniAppPhoneAuthenticationProvider wxMiniAppPhoneAuthenticationProvider,
            WxMiniAppPhoneCodeAuthenticationProvider wxMiniAppPhoneCodeAuthenticationProvider,
            SmsAuthenticationProvider smsAuthenticationProvider
    ) {
        return new ProviderManager(
                daoAuthenticationProvider,
                wxMiniAppCodeAuthenticationProvider,
                wxMiniAppPhoneAuthenticationProvider,
                wxMiniAppPhoneCodeAuthenticationProvider,
                smsAuthenticationProvider
        );
    }
}
