package com.artfess.base.conf;

import com.artfess.base.filter.JwtAuthorizationTokenFilter;
import com.artfess.base.jwt.JwtAuthenticationEntryPoint;
import com.artfess.base.jwt.JwtTokenHandler;
import com.artfess.base.security.CustomAccessDeniedHandler;
import com.artfess.base.security.CustomPwdEncoder;
import com.artfess.base.security.HtDecisionManager;
import com.artfess.base.security.HtFilterSecurityInterceptor;
import com.artfess.base.util.StringUtil;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationManager;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import javax.annotation.Resource;
import java.util.List;



@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
	private static final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
	@Resource
	UserDetailsService userDetailsService;
	@Resource
	JwtTokenHandler jwtTokenHandler;
	@Resource
	JwtConfig jwtConfig;
	@Value("${feign.encry.key:feignCallEncry}")
	private String encryKey;
	@Value("${artfess.security.ignore.httpUrls:''}")
	String permitAll;
	@Value("${artfess.security.deny.httpUrls:''}")
	String denyAll;
	@Value("${artfess.security.pswd.encoder:}")
	String passwordEncoder;
	@Value("${cors.enable:true}")
	Boolean corsEnable;
	@Resource
	JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
	@Resource
	HtFilterSecurityInterceptor htFilterSecurityInterceptor;
	@Resource
	CustomAccessDeniedHandler customAccessDeniedHandler;
	@Value("${webjar.context:mvue,fvue,mobilevue}")
	private List<String> resourceContext;

//	@Resource
//	List<WebSecurityExtend> webExtends;

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsService)
				.passwordEncoder(getCustomPasswordEncoder());
	}

	/**
	 * 获取配置的自定义密码加密类
	 * @return
	 */
	public PasswordEncoder getCustomPasswordEncoder() {
		CustomPwdEncoder encoder = (CustomPwdEncoder) defaultPasswordEncoderBean();
		if (StringUtil.isNotEmpty(passwordEncoder)) {
			try {
				logger.info("Use config password encoder : " + passwordEncoder);
				PasswordEncoder delegate = (PasswordEncoder) Class.forName(passwordEncoder).newInstance();
				encoder.setDelegateEncoder(delegate);
			} catch (Exception e) {
				logger.error("Create custom password encoder config class["+passwordEncoder+"] failed.");
			}
		}
		return encoder;
	}

	@Bean
	public WebSecurityExtend emptyExtend() {
		return new WebSecurityEmptyExtend();
	}

	@Bean
	public PasswordEncoder defaultPasswordEncoderBean() {
		return new CustomPwdEncoder();
	}

	// 注册后台权限控制器
	@Bean
	public AccessDecisionManager accessDecisionManager() {
		return new HtDecisionManager();
	}

	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}

	@Override
	protected void configure(HttpSecurity httpSecurity) throws Exception {
		String[] permitAlls = new String[]{};
		String[] denyAlls = new String[]{};
		if(StringUtil.isNotEmpty(permitAll)){
			permitAlls = permitAll.split(",");
		}
		// 将webjar中的resource资源添加到可匿名访问中
		for(String rc : resourceContext) {
			permitAlls = (String[]) ArrayUtils.add(permitAlls, String.format("/%s/**", rc));
		}

		if(StringUtil.isNotEmpty(denyAll)){
			denyAlls = denyAll.split(",");
		}
		httpSecurity
				// we don't need CSRF because our token is invulnerable
				.csrf().disable()
				.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
				.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler).and()
				// don't create session
				.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
				.authorizeRequests()
				.antMatchers(permitAlls).permitAll()
				.antMatchers(denyAlls).denyAll()
				.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
				.antMatchers("/auth/**").permitAll()
				.antMatchers("/static/**").permitAll()
				.antMatchers("/ueditor/**").permitAll()
				.antMatchers("/bizAPP/v1/appDownload").permitAll()
				.anyRequest().authenticated()
				.accessDecisionManager(accessDecisionManager());

		// Custom JWT based security filter
		JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenHandler, jwtConfig.getHeader());
		authenticationTokenFilter.setEncryKey(encryKey);
		httpSecurity
				.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
		httpSecurity
				.addFilterBefore(htFilterSecurityInterceptor, FilterSecurityInterceptor.class);
		httpSecurity
				.addFilterBefore(corsFilter(), ChannelProcessingFilter.class);

		// add custom filter
//		for (WebSecurityExtend extend : webExtends) {
//			for (Map.Entry<Class<? extends Filter>, Filter> entry : extend.getCustomBeforeFilter().entrySet()) {
//				httpSecurity.addFilterBefore(entry.getValue(), entry.getKey());
//			}
//		}

		// disable page caching
		httpSecurity
				.headers()
				.frameOptions().sameOrigin()  // required to set for H2 else H2 Console will be blank.
				.cacheControl();
	}

	@Override
	public void configure(WebSecurity web) throws Exception {
		// AuthenticationTokenFilter will ignore the below paths
		web
				.ignoring()
				.antMatchers(
						HttpMethod.POST,
						"/auth",
						"/error",
						"/sys/sysLogs/v1/loginLogs",
						"/sys/sysLogs/v1/saveLogs",
						"/api/user/v1/user/loadUserByUsername",
						"/actuator/cert",
						"/uc/AuthorizationModel/v1/downloadFileLic",
						"/uc/AuthorizationModel/v1/uploadAuthorizationFile",
						"/form/formServiceController/v1/getFormAndBoExportXml",
						"/v1/3rd/**"
				)
				.antMatchers(
						HttpMethod.GET,
						"/sso/**",
						"/sys/sysLogsSettings/v1/getSysLogsSettingStatusMap",
						"/sys/sysRoleAuth/v1/getMethodRoleAuth",
						"/file/v1/getLogoFile",
						/*"/flow/bpmTaskReminder/v1/executeTaskReminderJob",*/
						"/flow/def/v1/bpmnXml",
						"/file/onlinePreviewController/v1/getFileByPathAndId**",
						"/file/onlinePreviewController/v1/getFileById**",
						"/portal/main/v1/appProperties",
						"/sys/sysProperties/v1/getByAlias",
						"/uc/tenantManage/v1/getTenantByCode",
						"/sys/sysProperties/v1/getDecryptBySysSetting",
						"/portal/shorturlManage/v1/getLongUrlByShortUrl",
						"/file/v1/downloadFile",
						//"/websocket/**",
						"/jmreport/**",
						"/interface-ui/**",
						"/dataway/api/v1/**",
						"/v1/3rd/**"
				)
				// allow anonymous resource requests
				.and()
				.ignoring()
				.antMatchers(
						HttpMethod.GET,
						"/",
						"/error",
						"/*.jpg",
						"/*.gif",
						"/*.html",
						"/favicon.ico",
						"/**/*.html",
						"/**/*.css",
						"/**/*.js",
						"/**/image",
						"/**/json",
						"/**/ftl",
						"/interface-ui/**",
						"/jmreport/**",
						"/static/**"
				)
				.and()
				.ignoring()
				.antMatchers("/v2/api-docs",
//						"/swagger-resources/configuration/ui",
//						"/swagger-resources",
//						"/swagger-resources/configuration/security",
//						"/swagger-ui.html",
						"/proxy.stream",
						"/hystrix.stream",
						"/druid/**",
						"/hystrix/**",
//						"/actuator/**",
//						"/interface-ui/**",
						"/service/**",
						"/jmreport/**");

		// 添加扩展的路径过滤
//		for (WebSecurityExtend extend : webExtends) {
//			web
//			.ignoring()
//			.antMatchers(HttpMethod.POST, extend.getIgnoringPostUrl())
//			.and()
//			.ignoring()
//			.antMatchers(HttpMethod.GET, extend.getIgnoringGetUrl())
//			.and()
//			.ignoring()
//			.antMatchers(extend.getIgnoringUrl());
//		}
	}

	/**
	 * 允许跨域访问的源
	 * @return
	 */
	@Bean
	public CorsFilter corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		if(corsEnable){
			CorsConfiguration corsConfiguration = new CorsConfiguration();
			corsConfiguration.addAllowedOrigin("*");
			corsConfiguration.addAllowedHeader("*");
			corsConfiguration.addAllowedMethod("*");
			source.registerCorsConfiguration("/**", corsConfiguration);
		}
		return new CorsFilter(source);
	}

	@Bean
	public HtFilterSecurityInterceptor htFilterSecurityInterceptor(AccessDecisionManager accessDecisionManager) throws Exception{
		HtFilterSecurityInterceptor htFilterSecurityInterceptor = new HtFilterSecurityInterceptor();
		htFilterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager);
		return htFilterSecurityInterceptor;
	}
}
