package com.njzscloud.common.security.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import com.njzscloud.common.security.controller.PermissionController;
import com.njzscloud.common.security.controller.VerificationCodeController;
import com.njzscloud.common.security.handler.AccessDeniedExceptionHandler;
import com.njzscloud.common.security.handler.AuthExceptionHandler;
import com.njzscloud.common.security.handler.LogoutPostHandler;
import com.njzscloud.common.security.module.code.CodeAuthenticationProvider;
import com.njzscloud.common.security.module.code.CodeLoginPreparer;
import com.njzscloud.common.security.module.password.PasswordAuthenticationProvider;
import com.njzscloud.common.security.module.password.PasswordLoginPreparer;
import com.njzscloud.common.security.module.wechat.mini.WechatMiniAuthenticationProvider;
import com.njzscloud.common.security.module.wechat.mini.WechatMiniLoginPreparer;
import com.njzscloud.common.security.permission.DefaultPermissionLoader;
import com.njzscloud.common.security.permission.PermissionLoader;
import com.njzscloud.common.security.permission.PermissionManager;
import com.njzscloud.common.security.support.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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.core.Authentication;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.njzscloud.common.security.contant.Constants.LOGOUT_URL;

@Slf4j
@Configuration
@EnableConfigurationProperties({WebSecurityProperties.class})
public class WebSecurityAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(LoginHistoryRecorder.class)
    public LoginHistoryRecorder loginHistoryRecorder() {
        return new DefaultLoginHistoryRecorder();
    }

    @Bean
    @ConditionalOnMissingBean(PermissionLoader.class)
    public PermissionLoader permissionLoader() {
        return new DefaultPermissionLoader();
    }

    @Bean
    public PermissionController permissionController(PermissionManager permissionManager) {
        return new PermissionController(permissionManager);
    }

    @Bean
    public PermissionManager permissionManager(PermissionLoader permissionLoader) {
        return new PermissionManager(permissionLoader);
    }

    @Bean
    @ConditionalOnMissingBean({IAuthService.class})
    public DefaultAuthenticationProvider defaultAuthenticationProvider() {
        return new DefaultAuthenticationProvider();
    }

    @Bean
    @ConditionalOnBean({IAuthService.class})
    public PasswordAuthenticationProvider passwordAuthenticationProvider(IAuthService iAuthService) {
        return new PasswordAuthenticationProvider(iAuthService);
    }

    @Bean
    @ConditionalOnBean({IAuthService.class})
    public PasswordLoginPreparer passwordLoginPreparer() {
        return new PasswordLoginPreparer();
    }

    @Bean
    @ConditionalOnBean({IAuthService.class})
    public WechatMiniAuthenticationProvider wechatMiniAuthenticationProvider(IAuthService iAuthService) {
        return new WechatMiniAuthenticationProvider(iAuthService);
    }

    @Bean
    @ConditionalOnBean({IAuthService.class})
    public WechatMiniLoginPreparer wechatMiniLoginPreparer() {
        return new WechatMiniLoginPreparer();
    }

    @Bean
    public VerificationCodeService verificationCodeService() {
        return new VerificationCodeService(true);
    }

    @Bean
    @ConditionalOnBean({IAuthService.class, VerificationCodeService.class})
    public CodeAuthenticationProvider codeAuthenticationProvider(IAuthService iAuthService, VerificationCodeService verificationCodeService) {
        return new CodeAuthenticationProvider(iAuthService, verificationCodeService);
    }

    @Bean
    @ConditionalOnBean({VerificationCodeService.class})
    public VerificationCodeController verificationCodeController(VerificationCodeService verificationCodeService) {
        return new VerificationCodeController(verificationCodeService);
    }

    @Bean
    public CodeLoginPreparer codeLoginPreparer() {
        return new CodeLoginPreparer();
    }

    /**
     * SpringSecurity 配置
     */
    @Slf4j
    @Configuration
    @EnableWebSecurity
    @RequiredArgsConstructor
    public static class AuthenticationServerConfigurer {

        private final LoginHistoryRecorder loginHistoryRecorder;

        private final WebSecurityProperties webSecurityProperties;

        private final PermissionManager permissionManager;

        private final ObjectProvider<LoginPreparer> loginPreparerObjectProvider;

        private final ObjectProvider<AuthenticationProvider> abstractAuthenticationProviderObjectProvider;

        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            List<LoginPreparer> loginPreparers = loginPreparerObjectProvider.orderedStream().collect(Collectors.toList());
            List<AuthenticationProvider> authenticationProviders = abstractAuthenticationProviderObjectProvider.orderedStream().collect(Collectors.toList());
            ProviderManager providerManager = new ProviderManager(authenticationProviders);
            String[] authAllows = webSecurityProperties.getAuthAllows().toArray(new String[0]);
            LogoutPostHandler logoutPostHandler = new LogoutPostHandler();
            return http

                .csrf(AbstractHttpConfigurer::disable)
                .anonymous(AbstractHttpConfigurer::disable)
                .requestCache(AbstractHttpConfigurer::disable)
                .sessionManagement(AbstractHttpConfigurer::disable)

                .securityContext(it -> it.securityContextRepository(new TokenSecurityContextRepository()))
                .authorizeHttpRequests(it -> it
                    .requestMatchers(authAllows).permitAll()
                    .anyRequest()
                    .access((AuthorizationManager<RequestAuthorizationContext>) (authentication, object) -> {
                        // 获取当前请求路径
                        HttpServletRequest request = object.getRequest();
                        // 获取当前认证用户
                        Authentication auth = authentication.get();

                        int vote = permissionManager.vote(auth, request);

                        return new AuthorizationDecision(vote >= 0);
                    })
                )
                // .addFilter(securityInterceptor)
                // 退出登录
                .logout(it -> it.logoutUrl(LOGOUT_URL).addLogoutHandler(logoutPostHandler).logoutSuccessHandler(logoutPostHandler))

                .with(new CombineAuthenticationConfigurer(),
                    it -> it.authenticationManager(providerManager)
                        .loginPreparers(loginPreparers)
                        .loginHistoryRecorder(loginHistoryRecorder))

                // 异常处理
                .exceptionHandling(it -> it
                    .authenticationEntryPoint(AuthExceptionHandler.INSTANCE)
                    .accessDeniedHandler(AccessDeniedExceptionHandler.INSTANCE))
                .build();
        }

        @Bean
        public WebSecurityCustomizer webSecurityCustomizer() {
            return (web) -> {
                WebSecurity.IgnoredRequestConfigurer ignoring = web.ignoring();
                Set<String> authIgnore = webSecurityProperties.getAuthIgnores();
                if (CollUtil.isNotEmpty(authIgnore)) {
                    ignoring.requestMatchers(ArrayUtil.toArray(authIgnore, String.class));
                }
                // ignoring.requestMatchers("/error");
            };
        }
    }

}
