package com.hotent.base.filter;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import com.hotent.base.conf.JwtConfig;
import com.hotent.base.enums.ResponseErrorEnums;
import com.hotent.base.exception.BaseException;
import com.hotent.base.feign.PortalFeignService;
import com.hotent.base.jwt.JwtTokenHandler;
import com.hotent.base.model.CommonResult;
import com.hotent.base.util.AppUtil;
import com.hotent.base.util.AuthenticationUtil;
import com.hotent.base.util.Base64;
import com.hotent.base.util.EncryptUtil;
import com.hotent.base.util.HttpUtil;
import com.hotent.base.util.JsonUtil;
import com.hotent.base.util.SecurityUtil;
import com.hotent.base.util.StringUtil;
import com.hotent.base.util.ThreadLocalCleanUtil;

import io.jsonwebtoken.Claims;

public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	private String encryKey;
	private UserDetailsService userDetailsService;
	private JwtTokenHandler jwtTokenHandler;
	private String tokenHeader;
	private PortalFeignService portalFeignService;

	private final String systemCodeKey="sysCode";
	private final String tenantId="tenantId";
	// 外部接口的前缀
	private String OpenApiPre;


	public void setEncryKey(String encryKey) {
		this.encryKey = encryKey;
	}

	public JwtAuthorizationTokenFilter(UserDetailsService userDetailsService, JwtTokenHandler jwtTokenHandler, String tokenHeader, PortalFeignService portalFeignService,String OpenApiPre) {
		this.userDetailsService = userDetailsService;
		this.jwtTokenHandler = jwtTokenHandler;
		this.tokenHeader = tokenHeader;
		this.portalFeignService=portalFeignService;
		this.OpenApiPre=OpenApiPre;
	}

	/**
	 * 解析token
	 * @param token String
	 * @return Claims
	 */
	private Claims getClaims(String token){
		try {
			return jwtTokenHandler.parseToken(token);
		}catch (Exception e){
			Claims claims = jwtTokenHandler.parseFeignToken(token);
			claims.put("isFromFeign",true);
			return claims;
		}
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
		// 清空线程变量  controller运行结束后执行清空线程变量方法
		ThreadLocalCleanUtil.cleanAll();
	    String requestHeader = request.getHeader(this.tokenHeader);
	    boolean isFromFeign = false;
        if (StringUtil.isEmpty(requestHeader)) {
        	requestHeader = request.getHeader("Proxy-Authorization");
        	isFromFeign =  true;
		}
		String username = null;
		String authToken = null;
		Claims claims = null;
		if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
			authToken = requestHeader.substring(7);
			try {
				String requestURI = request.getRequestURI();
				claims = getClaims(authToken);
				String sysCode = (String) claims.get(this.systemCodeKey);
				if(!isFromFeign && claims.containsKey("isFromFeign")) isFromFeign = (boolean) claims.get("isFromFeign");
				if (StringUtil.isNotEmpty(sysCode) && !isFromFeign){
					isFromFeign = StringUtil.isNotEmpty(request.getHeader("Proxy-Authorization"));
				}
				// 证明是外部接口的token
				if (StringUtil.isNotEmpty(sysCode) && requestURI.contains(OpenApiPre)){
					// 验证解析出来的系统编码是否正确
					CommonResult result = portalFeignService.checkSysCodeAndUri(sysCode,null,requestURI,(String) claims.get(tenantId));
					if (!result.getState()){
						send401Error(response, result.getMessage());
						return;
					}
				}
				// 携带了外部接口的token，但访问的不是外部接口，并且不是Feign调用的话，就返回401状态码，否则要放行
				else if(StringUtil.isNotEmpty(sysCode) && !requestURI.contains(OpenApiPre) && !isFromFeign){
					send401Error(response, "当前路径，您无访问权限");
					return;
				}
				username = claims.getSubject();
			} catch(Exception e) {
				logger.warn("the token valid exception", e);
				send401Error(response, e.getMessage());
				return;
			}
		}
		else if (requestHeader != null && requestHeader.startsWith("Basic ") && jwtTokenHandler.enableBasic()) {
			String basicToken = requestHeader.substring(6);
			String userPwd = Base64.getFromBase64(basicToken);
			String[] arys = userPwd.split(":");
			if(arys.length==2) {
				try {
					String pwd = "";
					try {
						pwd = EncryptUtil.decrypt(arys[1]);
					} catch (Exception e) {}
					if( "admin".equals(arys[0]) && pwd.equals(encryKey) ){
						SecurityUtil.login(request, arys[0], "", true);
					}else{
						SecurityUtil.login(request, arys[0], arys[1], false);
					}
				}
				catch(Exception e) {
					logger.error("用户认证错误", e);
					send401Error(response, e.getMessage());
					return;
				}
			}
		}
		else {
			logger.warn("couldn't find bearer string, will ignore the header");
		}

		logger.debug("checking authentication for user '{}'", username);
		if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
			logger.debug("security context was null, so authorizating user");

			// It is not compelling necessary to load the use details from the database. You could also store the information
			// in the token and read it from it. It's up to you ;)
			UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

			// For simple validation it is completely sufficient to just check the token integrity. You don't have to call
			// the database compellingly. Again it's up to you ;)

			// 如果 claims 不等null 并具包含 isFromFeign 代表feign 请求，则过
			if ((claims!=null && claims.containsKey("isFromFeign")) || jwtTokenHandler.validateToken(authToken, userDetails)) {
				//处理单用户登录
				try {
					handleSingleLogin(request, username, authToken, userDetails);
				} catch (Exception e) {
					logger.warn("the token valid exception", e);
					send401Error(response, e.getMessage());
					return;
				}
				UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
				authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
				SecurityContextHolder.getContext().setAuthentication(authentication);
				AuthenticationUtil.setAuthentication(authentication);
			}
		}

		chain.doFilter(request, response);

	}

	// 中止请求并返回401错误
	private void send401Error(HttpServletResponse response, String message) throws IOException {
		response.setCharacterEncoding("utf-8");
		response.setContentType("text/html; charset=utf-8");
		CommonResult<String> result = new CommonResult<>(false, message);
		response.setStatus(HttpStatus.UNAUTHORIZED.value());
		PrintWriter writer = response.getWriter();
		writer.print(JsonUtil.toJson(result));
		writer.flush();
	}

	/**
	 * 处理单用户登录
	 * @param isMobile
	 * @param username
	 * @param token
	 * @throws Exception
	 */
	private void handleSingleLogin(HttpServletRequest request,String username,String token,UserDetails userDetails) throws Exception{
		//如果是单用户登录
		JwtConfig jwtConfig = AppUtil.getBean(JwtConfig.class);
		String header = request.getHeader("Proxy-Authorization");
		if(jwtConfig.isSingle() && StringUtil.isEmpty(header)){
			boolean isMobile = HttpUtil.isMobile(request);
			String userAgent = isMobile ? "mobile" : "pc";
			String tenantId = HttpUtil.getTenantId();
			// 从缓存中获取token
			String oldToken = jwtTokenHandler.getTokenFromCache(userAgent, tenantId, username, jwtConfig.getExpiration());

			if(jwtConfig.isStricty()) {
				if(StringUtil.isEmpty(token) || !token.equals(oldToken)) {
					throw new BaseException(ResponseErrorEnums.TOKEN_INVALID);
				}
			}
			else if(StringUtil.isNotEmpty(oldToken)) {
				if(jwtTokenHandler.validateToken(oldToken, userDetails) && !oldToken.equals(token)){
					throw new BaseException(ResponseErrorEnums.KICK_OFF_BY_ANOTHER);
				}
			}
		}
	}
}
