package com.tykj.dev.device.user.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.tykj.dev.device.user.config.handler.MyFailHandler;
import com.tykj.dev.device.user.config.handler.MyLogoutHandler;
import com.tykj.dev.device.user.config.handler.MySuccessHandler;
import com.tykj.dev.device.user.config.url.ApiResult;
import com.tykj.dev.device.user.config.url.UrlAccessDecisionManager;
import com.tykj.dev.device.user.config.url.UrlAccessDeniedHandler;
import com.tykj.dev.device.user.config.url.UrlFilterInvocationSecurityMetadataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static java.util.stream.Collectors.toList;
import static org.springframework.http.HttpMethod.*;

/**
 * @author zjm
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyEntryPoint myEntryPoint;

    @Autowired
    private MyUserDetailsServiceImpl myUserDetailsServiceImpl;

    @Autowired
    private MyLogoutHandler myLogoutHandler;

    @Autowired
    private MyFailHandler failHandler;

    @Autowired
    private MySuccessHandler successHandler;

    /**
     * 获取访问url所需要的角色信息
     */
    @Autowired
    private UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource;
    /**
     * 认证权限处理 - 将上面所获得角色权限与当前登录用户的角色做对比，如果包含其中一个角色即可正常访问
     */
    @Autowired
    private UrlAccessDecisionManager urlAccessDecisionManager;
    /**
     * 自定义访问无权限接口时403响应内容
     */
    @Autowired
    private UrlAccessDeniedHandler urlAccessDeniedHandler;

    /**
     * 跨域请求设置
     *
     * @return {@link CorsConfigurationSource}
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        List<String> methodNames = Lists.newArrayList(GET, POST, PUT, PATCH, DELETE, OPTIONS)
                .stream()
                .map(Enum::name)
                .collect(toList());
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setAllowedMethods(methodNames);
        configuration.setAllowedHeaders(Arrays.asList("Origin", "X-Requested-With", "Content-Type", "Accept"));
        configuration.setMaxAge(3600L);
        configuration.setAllowCredentials(true);
//        configuration.addExposedHeader("Location");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }


    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.headers().frameOptions().disable();
        httpSecurity
                .cors()
                .and()
                .authorizeRequests()
                .antMatchers("/file/**","/actuator/**","/admin/**").permitAll()
                .antMatchers("/file/llq/**").permitAll()
                .antMatchers("/login/impersonate*").permitAll()
                .antMatchers("/logout/impersonate*").permitAll()
                .antMatchers("/packageVersion/getVersion","/equip/packageVersion/getVersion").permitAll()//
                .antMatchers("/equip/file/llq/**").permitAll()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
//                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
//                    @Override
//                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
//                        o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);
//                        o.setAccessDecisionManager(urlAccessDecisionManager);
//                        return o;
//                    }
//                })
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/userLogin")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/user/userLogout")
                .logoutSuccessHandler(myLogoutHandler)
                .deleteCookies("JESSIONID")
                .and()
                .exceptionHandling()
                .accessDeniedHandler(urlAccessDeniedHandler)
                .authenticationEntryPoint(myEntryPoint)
                .and()
                .addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> {
                    HttpServletResponse resp = event.getResponse();
                    resp.setContentType("application/json;charset=utf-8");
                    resp.setStatus(401);
                    PrintWriter out = resp.getWriter();
                    out.write(new ObjectMapper().writeValueAsString(ApiResult.build(405, "您已在另一台设备登录，本次登录已下线!")));
                    out.flush();
                    out.close();
                }), ConcurrentSessionFilter.class)
                .addFilterAt(myFilter(), UsernamePasswordAuthenticationFilter.class)
                .csrf().disable();
//                .headers().frameOptions().disable();

    }

    @Override
    public void configure(WebSecurity web) {
        //swagger静态资源访问
        web.ignoring().antMatchers("/v2/api-docs", "/v2/api-docs-ext", "/configuration/ui", "/swagger-resources/**", "/configuration/security", "/swagger-ui/", "/swagger-ui/**", "/swagger-ui.html", "/doc.html", "/webjars/**", "/swagger-resources/configuration/ui", "**/swagge‌​r-ui.html", "/static/**", "**/**/index.html", "**/index.html", "/**/index.html", "/js/**", "/css/**", "/fonds/**", "/img/**","/access/send","/user/s");
    }

    @Bean
    public MyFilter myFilter() throws Exception {
        MyFilter filter = new MyFilter();
        filter.setAuthenticationSuccessHandler(successHandler);
        filter.setAuthenticationFailureHandler(failHandler);
        filter.setFilterProcessesUrl("/userLogin");
        filter.setAuthenticationManager(this.authenticationManager());
        ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
        sessionStrategy.setMaximumSessions(1);
        filter.setSessionAuthenticationStrategy(sessionStrategy);
        return filter;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsServiceImpl);
//        auth.authenticationProvider()
//        auth.inMemoryAuthentication();
    }

    @Bean
    HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    @Bean
    public SessionRegistryImpl sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SwitchUserFilter switchUserFilter() {
        SwitchUserFilter filter = new SwitchUserFilter();
        filter.setUserDetailsService(userDetailsService());
        filter.setUsernameParameter("username");
        filter.setSwitchUserUrl("/login/impersonate");
        filter.setExitUserUrl("/logout/impersonate");
        filter.setSuccessHandler(successHandler);
        return filter;
    }

}
