package com.hotent.base.conf;

import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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 com.hotent.base.feign.PortalFeignService;
import com.hotent.base.filter.JwtAuthorizationTokenFilter;
import com.hotent.base.jwt.JwtAuthenticationEntryPoint;
import com.hotent.base.jwt.JwtTokenHandler;
import com.hotent.base.security.CustomAccessDeniedHandler;
import com.hotent.base.security.CustomPwdEncoder;
import com.hotent.base.security.HtDecisionManager;
import com.hotent.base.security.HtFilterSecurityInterceptor;
import com.hotent.base.util.StringUtil;

@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("${hotent.security.ignore.httpUrls:''}")
	String permitAll;
	@Value("${hotent.security.deny.httpUrls:''}")
	String denyAll;
	@Value("${hotent.security.pswd.encoder:}")
	String passwordEncoder;
    @Value("${cors.enable:true}")
    Boolean corsEnable;
	@Resource
	JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
	@Resource
	HtFilterSecurityInterceptor htFilterSecurityInterceptor;
	@Resource
	CustomAccessDeniedHandler customAccessDeniedHandler;
	@Value("${webjar.context:mvue,front,mobile}")
	private List<String> resourceContext;
	@Autowired
	PortalFeignService portalFeignService;
	@Value("${openApi.prefix:'openApi'}")
	String openApiPre;
//	@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("/api/data-middle-platform/**").permitAll()
		.antMatchers("/ueditor/**","/apiManager/getToken").permitAll()
		.anyRequest().authenticated()
		.accessDecisionManager(accessDecisionManager());

		// Custom JWT based security filter
		JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenHandler, jwtConfig.getHeader(),portalFeignService,openApiPre);
		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",
				"/form/formServiceController/v1/getFormAndBoExportXml",
				"/**/bpmRuntimeStartUp",
				"/**/api/org/v1/pushOrgData"
				)
		.antMatchers(
				HttpMethod.GET,
				"/sso/**",
				"/sys/sysLogsSettings/v1/getSysLogsSettingStatusMap",
				"/sys/sysRoleAuth/v1/getMethodRoleAuth",
				"/system/file/v1/getLogoFile",
				"/system/file/v1/getImage",
				/*"/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",
				"/system/file/v1/downloadFile",
				"/portal/sysExternalUnite/v1/getQrCodeInfo",
				"/portal/sysExternalUnite/v1/getDingtalkIdFromScanCode",
				"/portal/sysExternalUnite/v1/getWxWorkIdFromScanCode",
				"/base/tools/v1/getPublicKey",
				"/portalBizSystem/v1/checkSysCodeAndUri",
				"/instance/client/v1/get",
				"/api/file/**"
				)
		// allow anonymous resource requests
		.and()
		.ignoring()
		.antMatchers(
				HttpMethod.GET,
				"/",
				"/error",
				"/*.html",
				"/favicon.ico",
				"/**/*.html",
				"/**/*.css",
				"/**/*.js",
				"/**/image",
				"/profile/**"
				)
		.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/**",
				"/service/**");
		
		// 添加扩展的路径过滤
//		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;
	}
}
