package com.njzscloud.common.security.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
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.password.PasswordAuthenticationProvider;
import com.njzscloud.common.security.module.password.PasswordLoginPreparer;
import com.njzscloud.common.security.permission.DefaultPermissionManager;
import com.njzscloud.common.security.permission.PermissionManager;
import com.njzscloud.common.security.permission.PermissionSecurityMetaDataSource;
import com.njzscloud.common.security.permission.PermissionVoter;
import com.njzscloud.common.security.support.*;
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.access.vote.AffirmativeBased;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
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.WebSecurityCustomizer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

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

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

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

    @Bean
    @ConditionalOnMissingBean(PermissionManager.class)
    public PermissionManager permissionManager() {
        return new DefaultPermissionManager();
    }

    @Bean
    @ConditionalOnBean({IUserService.class, IRoleService.class, IResourceService.class})
    public PasswordAuthenticationProvider passwordAuthenticationProvider(IUserService iUserService, IRoleService iRoleService, IResourceService iResourceService) {
        return new PasswordAuthenticationProvider(iUserService, iRoleService, iResourceService);
    }

    @Bean
    @ConditionalOnMissingBean({IUserService.class, IRoleService.class, IResourceService.class})
    public DefaultAuthenticationProvider defaultAuthenticationProvider() {
        return new DefaultAuthenticationProvider();
    }

    @Bean
    @ConditionalOnBean({IUserService.class, IRoleService.class, IResourceService.class})
    public PasswordLoginPreparer passwordLoginPreparer() {
        return new PasswordLoginPreparer();
    }

    /**
     * SpringSecurity 配置
     */
    @Slf4j
    @Configuration
    @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;

        private FilterSecurityInterceptor createFilterSecurityInterceptor(HttpSecurity http, AuthenticationManager authenticationManager) {
            FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
            securityInterceptor.setSecurityMetadataSource(new PermissionSecurityMetaDataSource(permissionManager));
            securityInterceptor.setAccessDecisionManager(new AffirmativeBased(Collections.singletonList(new PermissionVoter())));
            securityInterceptor.setAuthenticationManager(authenticationManager);
            securityInterceptor.setRejectPublicInvocations(false);
            securityInterceptor.setValidateConfigAttributes(false);
            securityInterceptor.afterPropertiesSet();

            http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
            return securityInterceptor;
        }

        @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);

            FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(http, providerManager);


            LogoutPostHandler logoutPostHandler = new LogoutPostHandler();
            return http

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

                    .securityContext()
                    .securityContextRepository(new TokenSecurityContextRepository())

                    .and()
                    .addFilter(securityInterceptor)
                    // 退出登录
                    .logout()

                    .addLogoutHandler(logoutPostHandler)
                    .logoutSuccessHandler(logoutPostHandler)

                    .and()
                    .apply(new CombineAuthenticationConfigurer())
                    .authenticationManager(providerManager)
                    .loginPreparers(loginPreparers)
                    .loginHistoryRecorder(loginHistoryRecorder)

                    // 异常处理
                    .and()
                    .exceptionHandling()
                    .authenticationEntryPoint(AuthExceptionHandler.INSTANCE)
                    .accessDeniedHandler(AccessDeniedExceptionHandler.INSTANCE)

                    .and()
                    .build();
        }

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

}
