package com.starry.admin.common.security.filter;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.starry.admin.common.component.JwtToken;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.impl.PlayClerkUserInfoServiceImpl;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import com.starry.admin.modules.custom.service.impl.PlayCustomUserInfoServiceImpl;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.admin.modules.system.service.ISysTenantService;
import com.starry.admin.modules.system.service.SysUserService;
import com.starry.admin.modules.weichat.service.WxTokenService;
import com.starry.admin.utils.SecurityUtils;
import com.starry.common.constant.Constants;
import com.starry.common.redis.RedisCache;
import com.starry.common.result.R;
import com.starry.common.result.ResultCodeEnum;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;

/**
 * @author admin JWT登录授权过滤器
 * @since 2021/9/6
 */
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    WxTokenService tokenService;

    @Resource
    private JwtToken jwtToken;

    @Resource
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Resource
    private PlayCustomUserInfoServiceImpl customUserInfoService;

    @Resource
    private PlayClerkUserInfoServiceImpl clerkUserInfoService;

    @Resource
    private ISysTenantService sysTenantService;

    @Resource
    private RedisCache redisCache;
    @Resource
    private SysUserService userService;

    /**
     * 不需要登录的的路径
     */
    Set<String> noLoginPathRequired = new HashSet<>(Arrays.asList("/wx/common/area/tree", "/wx/common/file/upload",
            "/wx/common/audio/upload", "/wx/oauth2/getConfigAddress", "/wx/clerk/user/queryByPage",
            "/wx/clerk/user/queryGiftById", "/wx/clerk/user/queryPriceById", "/wx/clerk/user/queryTrendsById",
            "/wx/clerk/user/queryEvaluateById"));

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest httpServletRequest,
            @NotNull HttpServletResponse httpServletResponse, @NotNull FilterChain filterChain)
            throws ServletException, IOException {
        log.info("url = {}", httpServletRequest.getRequestURI());

        // 微信支付回调接口,不需要验证
        if ("/wx/pay/jsCallback".equals(httpServletRequest.getServletPath())) {
            log.info("微信支付回调,不需要验证");
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } else {// 微信公众号的请求，必须存在tenantkey，否则抛出异常
            if (httpServletRequest.getServletPath().startsWith("/wx/")) {

                String tenantKey = httpServletRequest.getHeader("tenantkey");
                if (StrUtil.isBlank(tenantKey)) {
                    resolver.resolveException(httpServletRequest, httpServletResponse, null,
                            new CustomException("tenantkey不能为空"));
                    return;
                }
                if (noLoginPathRequired.contains(httpServletRequest.getServletPath())) {
                    // 非必须登录的请求，验证租户信息是否正常
                    String tenantId;
                    try {
                        tenantId = getTenantId(null, null, tenantKey);
                    } catch (Exception e) {
                        httpServletResponse.setCharacterEncoding("UTF-8");
                        httpServletResponse.setContentType("application/json");
                        httpServletResponse.getWriter()
                                .println(JSONUtil.parse(R.error(ResultCodeEnum.UNAUTHORIZED.getCode(),
                                        ResultCodeEnum.UNAUTHORIZED.getMessage())));
                        httpServletResponse.getWriter().flush();
                        return;
                        // 返回401‘
                        //
                        // return;
                        // throw new RuntimeException("token验证失败,需要重新登录");
                    }
                    if (!checkTenantId(tenantId)) {
                        // 返回500
                        resolver.resolveException(httpServletRequest, httpServletResponse, null,
                                new CustomException("租户信息异常"));
                        return;
                    }
                    SecurityUtils.setTenantId(tenantId);
                } else {
                    String clerkToken = httpServletRequest.getHeader(Constants.CLERK_USER_LOGIN_TOKEN);
                    String customToken = httpServletRequest.getHeader(Constants.CUSTOM_USER_LOGIN_TOKEN);
                    String tenantId;
                    try {
                        tenantId = getTenantId(clerkToken, customToken, tenantKey);
                    } catch (Exception e) {
                        httpServletResponse.setCharacterEncoding("UTF-8");
                        httpServletResponse.setContentType("application/json");
                        httpServletResponse.getWriter()
                                .println(JSONUtil.parse(R.error(ResultCodeEnum.UNAUTHORIZED.getCode(),
                                        ResultCodeEnum.UNAUTHORIZED.getMessage())));
                        httpServletResponse.getWriter().flush();
                        // 返回401
                        return;
                        // throw new CustomException("token验证失败,需要重新登录");
                    }
                    if (!checkTenantId(tenantId)) {
                        // 返回500
                        resolver.resolveException(httpServletRequest, httpServletResponse, null,
                                new CustomException("租户信息异常"));
                        return;
                    }
                    SecurityUtils.setTenantId(tenantId);
                }
            } else {
                // 管理端的请求
                LoginUser jwtUser = jwtToken.getNewLoginUser(httpServletRequest);
                if (null != jwtUser && null == SecurityContextHolder.getContext().getAuthentication()) {
                    jwtToken.verifyToken(jwtUser);
                    userService.selectUserById(jwtUser.getUserId());
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            jwtUser, null, jwtUser.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        }
    }

    /**
     * 微信公众号端访问时，获取当前用户租户ID 如果用户（陪聊或客户）已登录，从token中获取租户ID 如果用户未登录，从tenantKey中获取租户ID
     *
     * @param clerkToken
     *            陪聊登录Key
     * @param customToken
     *            客户登录key
     * @param tenantKey
     *            租户标识
     */
    public String getTenantId(String clerkToken, String customToken, String tenantKey) {
        String tenantId = "";
        // 如果用户（陪聊或客户）已登录，从token中获取租户ID
        if (StrUtil.isNotBlank(clerkToken) || StrUtil.isNotBlank(customToken)) {
            String userId = tokenService.getWxUserIdByToken(StrUtil.isNotBlank(clerkToken) ? clerkToken : customToken);
            String redisKey = "TENANT_INFO:" + userId;
            SecurityUtils.setTenantId(redisCache.getCacheObject(redisKey));
            if (clerkToken != null) {
                PlayClerkUserInfoEntity entity = clerkUserInfoService.selectById(userId);
                if (entity == null) {
                    log.error("当前登录陪聊不存在,clerkToken={}，tenantKey={}", clerkToken, tenantKey);
                    throw new CustomException("用户不存在");
                }
                return entity.getTenantId();
            } else {
                PlayCustomUserInfoEntity entity = customUserInfoService.selectById(userId);
                if (entity == null) {
                    log.error("当前登录顾客不存在,customToken={}，tenantKey={}", customToken, tenantKey);
                    throw new CustomException("用户不存在");
                }
                return entity.getTenantId();
            }
        } else {
            // 如果用户未登录，从tenantKey中获取租户ID，然后验证租户ID是否存在，以及租户是否过期等
            SysTenantEntity entity = sysTenantService.selectByTenantKey(tenantKey);
            if (entity != null) {
                return entity.getTenantId();
            }
        }
        return tenantId;
    }

    /**
     * 校验租户是否正常
     *
     * @param tenantId
     *            租户ID
     * @return true:租户正常，false:租户不正常
     */
    public boolean checkTenantId(String tenantId) {
        if (StrUtil.isBlankIfStr(tenantId)) {
            return false;
        }
        if (tenantId.equals("-1")) {
            return true;
        }
        SysTenantEntity entity = sysTenantService.selectSysTenantByTenantId(tenantId);
        if (entity == null) {
            return false;
        }
        // 判断租户是否过期
        if (DateUtil.compare(entity.getTenantTime(), new Date()) < 0) {
            return false;
        }
        // ...
        // 校验通过
        return true;
    }
}
