
package com.tmerclub.cloud.common.security.filter;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.tmerclub.cloud.api.auth.bo.UidInfoBO;

import com.tmerclub.cloud.api.auth.constant.SysTypeEnum;
import com.tmerclub.cloud.api.auth.feign.TokenFeignClient;
import com.tmerclub.cloud.api.platform.vo.SysAccessKeyVO;
import com.tmerclub.cloud.api.rbac.constant.HttpMethodEnum;
import com.tmerclub.cloud.api.rbac.feign.PermissionFeignClient;
import com.tmerclub.cloud.common.constant.StatusEnum;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.handler.HttpHandler;
import com.tmerclub.cloud.common.response.ResponseEnum;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.security.AuthTokenContext;
import com.tmerclub.cloud.common.security.AuthUserContext;
import com.tmerclub.cloud.common.security.PmsContext;
import com.tmerclub.cloud.common.security.adapter.AuthConfigAdapter;
import com.tmerclub.cloud.common.security.adapter.DefaultSignAuthAdapter;
import com.tmerclub.cloud.common.security.bo.SignResponse;
import com.tmerclub.cloud.common.security.util.SignUtils;
import com.tmerclub.cloud.common.util.Json;
import com.tmerclub.cloud.common.wrapper.ResponseWrapper;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.dubbo.config.annotation.DubboReference;
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.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 授权过滤，只要实现AuthConfigAdapter接口，添加对应路径即可：
 *
 * @author tmerclub
 * @date 2020/7/11
 */
@Component
public class AuthFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);

    @Value("${sa-token.token-name}")
    private String tokenName;
    @Autowired
    private AuthConfigAdapter authConfigAdapter;
    @Autowired
    private HttpHandler httpHandler;
    @Autowired
    private SignUtils signUtils;
    @Autowired
    private DefaultSignAuthAdapter defaultSignAuthAdapter;
    @DubboReference
    private TokenFeignClient tokenFeignClient;
    @DubboReference
    private PermissionFeignClient permissionFeignClient;
    /**
     * 成功状态码
     */
    public static final int SUCCESS_CODE = 200;


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        String requestUri = req.getRequestURI();

        Set<String> excludePathPatterns = authConfigAdapter.excludePathPatterns();

        AntPathMatcher pathMatcher = new AntPathMatcher();
        String accessToken = req.getHeader(tokenName);
        AuthTokenContext.setToken(accessToken);
        // 如果匹配不需要授权的路径，就不需要校验是否需要授权
        if (CollectionUtil.isNotEmpty(excludePathPatterns)) {
            for (String excludePathPattern : excludePathPatterns) {
                if (pathMatcher.match(excludePathPattern, requestUri)) {
                    chain.doFilter(req, resp);
                    AuthTokenContext.cleanToken();
                    return;
                }
            }
        }

        String grantType = req.getHeader(SignUtils.GRANT_TYPE);
        // 也许需要登录，不登陆也能用的uri
        // 比如优惠券接口，登录的时候可以判断是否已经领取过
        // 不能登录的时候会看所有的优惠券，等待领取的时候再登录
        boolean mayAuth = pathMatcher.match(AuthConfigAdapter.MAYBE_AUTH_URI, requestUri);


        UidInfoBO uidInfoBO = null;

        try {
            // 通过签名访问
            if (Objects.equals(grantType, SignUtils.GRANT_TYPE_VALUE)) {
                signAccess(chain, req, resp);
                return;
            }

            // 如果有token，就要获取token
            else if (StrUtil.isNotBlank(accessToken)) {
                // 校验登录，并从缓存中取出用户信息
                ServerResponseEntity<UidInfoBO> uidInfoBOVoServerResponseEntity = checkUriAndGetToken(req, accessToken, mayAuth, pathMatcher);
                if (!uidInfoBOVoServerResponseEntity.isSuccess()) {
                    httpHandler.printServerResponseToWeb(uidInfoBOVoServerResponseEntity);
                    return;
                }
                uidInfoBO = uidInfoBOVoServerResponseEntity.getData();
            } else if (!mayAuth) {
                httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
                return;
            }
            // 保存上下文
            AuthUserContext.set(uidInfoBO);

            chain.doFilter(req, resp);
        } catch (Exception e) {
            if (e instanceof LuckException) {
                httpHandler.printServerResponseToWeb((LuckException) e);
                return;
            }
            throw e;
        } finally {
            AuthUserContext.clean();
            AuthTokenContext.cleanToken();
            PmsContext.clean();
        }

    }

    /**
     * 根据不同平台的token判断是否有url的权限
     */
    private ServerResponseEntity<UidInfoBO> checkUriAndGetToken(HttpServletRequest req, String accessToken, boolean mayAuth, AntPathMatcher pathMatcher) {
        // 校验token，并返回用户信息
        UidInfoBO uidInfoBO = tokenFeignClient.checkToken(accessToken);
        if (Objects.isNull(uidInfoBO) && !mayAuth) {
            return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
        }
        String requestUri = req.getRequestURI();
        // 退出登录的url是统一的
        if (Objects.isNull(uidInfoBO) || pathMatcher.match(AuthConfigAdapter.LOGIN_OUT_URI, requestUri)) {
            return ServerResponseEntity.success(uidInfoBO);
        }

        boolean isPlatform = pathMatcher.match(AuthConfigAdapter.PLATFORM_URI, requestUri);
        boolean isMultiShop = pathMatcher.match(AuthConfigAdapter.MULTISHOP_URI, requestUri);
        boolean isSupplier = pathMatcher.match(AuthConfigAdapter.SUPPLIER_URI, requestUri);
        boolean isStation = Objects.equals(SysTypeEnum.STATION.value(), uidInfoBO.getSysType());
        boolean isPlatformOrMultiShop = pathMatcher.match(AuthConfigAdapter.MULTISHOP_PLATFORM_URI, requestUri);
        // 平台端的uri问题
        if (isPlatform && !Objects.equals(SysTypeEnum.PLATFORM.value(), uidInfoBO.getSysType())) {
            return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
        }

        // 商家端的uri问题
        if (isMultiShop) {
            if (!Objects.equals(SysTypeEnum.MULTISHOP.value(), uidInfoBO.getSysType())) {
                return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
            }
            if (Objects.equals(uidInfoBO.getIsPassShop(), StatusEnum.DISABLE.value()) && !pathMatcher.match(AuthConfigAdapter.MULTSHOP_APPLY_URI, requestUri)) {
                // 当商家处于申请开店状态并且调用不属于开店接口时
                throw new LuckException("你没有权限访问该页面");
            }
        }
        // 供应商端的uri问题
        if (isSupplier) {
            if (!Objects.equals(SysTypeEnum.SUPPLIER.value(), uidInfoBO.getSysType())) {
                return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
            }
            if (Objects.equals(uidInfoBO.getIsPassShop(), StatusEnum.DISABLE.value()) && !pathMatcher.match(AuthConfigAdapter.SUPPLIER_APPLY_URI, requestUri)) {
                // 当供应商处于申请开店状态并且调用不属于开店接口时
                throw new LuckException("你没有权限访问该页面");
            }
        }
        // 商家、供应商或平台的uri
        if (isPlatformOrMultiShop && !Objects.equals(SysTypeEnum.PLATFORM.value(), uidInfoBO.getSysType())
                && !Objects.equals(SysTypeEnum.MULTISHOP.value(), uidInfoBO.getSysType())
                && !Objects.equals(SysTypeEnum.SUPPLIER.value(), uidInfoBO.getSysType())) {
            return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
        }

        // 平台和商家和供应商还需要校验用户角色权限
        if (isPlatform || isMultiShop || isSupplier || isPlatformOrMultiShop) {
            // 需要用户角色权限，就去根据用户角色权限判断是否
            if (!checkRbac(uidInfoBO, requestUri, req.getMethod())) {
                return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
            }
        }

        // 用户端的uri问题
        if (!isPlatform && !isMultiShop && !isSupplier && !isPlatformOrMultiShop
                && !isStation
                && !Objects.equals(SysTypeEnum.ORDINARY.value(), uidInfoBO.getSysType())) {
            return ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED);
        }
        return ServerResponseEntity.success(uidInfoBO);
    }
    /**
     * 用户角色权限校验
     *
     * @param uri uri
     * @return 是否校验成功
     */
    public boolean checkRbac(UidInfoBO uidInfoBO, String uri, String method) {
        ServerResponseEntity<Boolean> booleanServerResponseEntity = permissionFeignClient
                .checkPermission(uidInfoBO.getUserId(), uidInfoBO.getSysType(), uri, uidInfoBO.getIsAdmin(), HttpMethodEnum.valueOf(method.toUpperCase(Locale.ROOT)).value());

        if (!booleanServerResponseEntity.isSuccess()) {
            return false;
        }

        return booleanServerResponseEntity.getData();
    }

    @SuppressWarnings("unchecked")
    private void signAccess(FilterChain chain, HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        UidInfoBO uidInfoBO;
        ServerResponseEntity<SignResponse> verifyResponse = signUtils.verify(req);
        SignResponse signResponse = verifyResponse.getData();
        SysAccessKeyVO sysAccessKey = signResponse.getSysAccessKeyVO();
        long timestamp = System.currentTimeMillis();

        if (!verifyResponse.isSuccess()) {
            verifyResponse.setSign(SignUtils.sign(signResponse.getAppSecret(), timestamp, null));
            verifyResponse.setTimestamp(timestamp);
            verifyResponse.setData(null);
            ServerResponseEntity<String> response = ServerResponseEntity.success(Json.toJsonString(verifyResponse));
            response.setSign(verifyResponse.getSign());
            httpHandler.printServerResponseToWeb(response);
            return;
        }
        ServerResponseEntity<UidInfoBO> tokenResponse = defaultSignAuthAdapter.loadUserInfoInToken(signResponse.getDataMap());
        if (!tokenResponse.isSuccess()) {
            verifyResponse.setSign(SignUtils.sign(signResponse.getAppSecret(), timestamp, null));
            verifyResponse.setTimestamp(timestamp);
            verifyResponse.setData(null);
            verifyResponse.setMsg(tokenResponse.getMsg());
            ServerResponseEntity<String> response = ServerResponseEntity.success(Json.toJsonString(verifyResponse));
            response.setSign(verifyResponse.getSign());
            httpHandler.printServerResponseToWeb(response);
            return;
        }
        // 商家端、平台端、供应商端将权限保存至上下文
        if (Objects.equals(sysAccessKey.getSysType(), SysTypeEnum.PLATFORM.value())
                || Objects.equals(sysAccessKey.getSysType(), SysTypeEnum.MULTISHOP.value())
                ||Objects.equals(sysAccessKey.getSysType(), SysTypeEnum.SUPPLIER.value())) {
            PmsContext.set(sysAccessKey.getPrems());
        }


        uidInfoBO = tokenResponse.getData();
        // 校验权限
        if (!checkRbac(uidInfoBO, req.getRequestURI(), req.getMethod())) {
            verifyResponse.setSign(SignUtils.sign(signResponse.getAppSecret(), timestamp, null));
            verifyResponse.setTimestamp(timestamp);
            verifyResponse.setData(null);
            verifyResponse.setMsg(ResponseEnum.UNAUTHORIZED.getMsg());
            verifyResponse.setCode(ResponseEnum.UNAUTHORIZED.value());
            ServerResponseEntity<String> response = ServerResponseEntity.success(Json.toJsonString(verifyResponse));
            response.setSign(verifyResponse.getSign());
            httpHandler.printServerResponseToWeb(response);
            return;
        }
        // 保存上下文
        AuthUserContext.set(uidInfoBO);

        ResponseWrapper responseWrapper = new ResponseWrapper(resp);
        chain.doFilter(signResponse.getReq(), responseWrapper);
        byte[] content = responseWrapper.getContent();
        // 获取相应数据
        String data = null;
        if (content.length > 0) {
            data = new String(content, StandardCharsets.UTF_8);
        }

        ServerResponseEntity<String> successResponse;

        if (StrUtil.isNotBlank(data)) {
            successResponse = Json.parseObject(data, ServerResponseEntity.class);
        } else if (responseWrapper.getStatus() != SUCCESS_CODE) {
            successResponse = ServerResponseEntity.showFailMsg(data);
        } else {
            successResponse = ServerResponseEntity.success(data);
        }

        if (Objects.nonNull(successResponse)) {
            String responseData = JSON.toJSONString(successResponse.getData());

            successResponse.setSign(SignUtils.sign(signResponse.getAppSecret(), timestamp, responseData));
            successResponse.setTimestamp(timestamp);
            successResponse.setData(responseData);
            httpHandler.printServerResponseToWeb(successResponse);
        }
    }

    private static Map<String, Object> getFeignMap(HttpServletRequest req) {
        Map<String, Object> dataMap = new HashMap<>(6);
        dataMap.put("sysType", req.getHeader("sysType"));
        dataMap.put("sysUserId", req.getHeader("sysUserId"));
        dataMap.put("shopId", req.getHeader("shopId"));
        dataMap.put("userId", req.getHeader("userId"));
        dataMap.put("supplierId", req.getHeader("supplierId"));
        dataMap.put("stationId", req.getHeader("stationId"));
        return dataMap;
    }

}
