package com.artfess.base.filter;

import com.artfess.base.conf.JwtConfig;
import com.artfess.base.constants.TenantConstant;
import com.artfess.base.jwt.JwtTokenHandler;
import com.artfess.base.model.CommonResult;
import com.artfess.base.util.AppUtil;
import com.artfess.base.util.AuthenticationUtil;
import com.artfess.base.util.Base64;
import com.artfess.base.util.EncryptUtil;
import com.artfess.base.util.HttpUtil;
import com.artfess.base.util.JsonUtil;
import com.artfess.base.util.SecurityUtil;
import com.artfess.base.util.StringUtil;
import com.artfess.base.util.ThreadLocalCleanUtil;
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 javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

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;

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

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

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 清空线程变量  controller运行结束后执行清空线程变量方法
        ThreadLocalCleanUtil.cleanAll();
        String requestHeader = request.getHeader(this.tokenHeader);
        if (StringUtil.isEmpty(requestHeader)) {
            requestHeader = request.getHeader("Proxy-Authorization");
        }
        String username = null;
        String authToken = null;
        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            authToken = requestHeader.substring(7);
            try {
                username = jwtTokenHandler.getUsernameFromToken(authToken);
            } catch (Exception e) {
                logger.warn("the token valid exception", e);
                send401Error(response, e.getMessage());
                return;
            }
        } else if (requestHeader != null && requestHeader.startsWith("Basic ")) {
            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 ;)
            if (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 request
     * @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();
            tenantId = StringUtil.isNotEmpty(tenantId) ? tenantId : TenantConstant.PLATFORM_TENANT_ID;
            // 从缓存中获取token
            String oldToken = jwtTokenHandler.getTokenFromCache(userAgent, tenantId, username, jwtConfig.getExpiration());

            if (jwtConfig.isStricty()) {
                if (StringUtil.isEmpty(token) || !token.equals(oldToken)) {
                    throw new Exception("当前登录状态已过期！");
                }
            } else if (StringUtil.isNotEmpty(oldToken)) {
                if (jwtTokenHandler.validateToken(oldToken, userDetails) && !oldToken.equals(token)) {
                    throw new Exception("当前账号已在另一地方登录，若不是本人操作，请注意账号安全！");
                }
            }
        }
    }
}
