package com.liquidnet.common.web.filter;

import com.liquidnet.commons.lang.core.JwtValidator;
import com.liquidnet.commons.lang.util.CurrentUtil;
import com.liquidnet.commons.lang.util.DESUtils;
import com.liquidnet.commons.lang.util.JsonUtils;
import com.liquidnet.service.base.ErrorMapping;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.feign.auth.rsc.FeignAuthorityClient;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.DigestUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

@Component
@ConfigurationProperties(prefix = "global-auth")
public class GlobalAuthorityInterceptor extends HandlerInterceptorAdapter {
    private static final Logger log = LoggerFactory.getLogger(GlobalAuthorityInterceptor.class);

    //private List<String> includeUrlPattern;
    private List<String> excludeUrlPattern;

    private static final String CONTENT_TYPE = "application/json;charset=utf-8";
    private static final String TOKEN_ILLEGAL = "40001";
    private static final String TOKEN_KICK = "40002";
    private static final String TOKEN_INVALID = "40003";

    private final static AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    JwtValidator jwtValidator;
    @Autowired
    FeignAuthorityClient feignAuthorityClient;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String authorization = request.getHeader(CurrentUtil.uToken), uri = request.getRequestURI(),
                responseCode = null, token = null, currentUid = null;

        Claims claims = null;
        if (StringUtils.isNotBlank(authorization) && StringUtils.length(authorization) > 7) {
            token = authorization.substring(7);
            try {
                // 解析没有异常则表示token验证通过，如有必要可根据自身需求增加验证逻辑
                claims = jwtValidator.parse(token);

                currentUid = (String) claims.get(CurrentUtil.TOKEN_SUB);

                request.setAttribute(CurrentUtil.uToken, token);
                request.setAttribute(CurrentUtil.TOKEN_SUB, currentUid);
                request.setAttribute(CurrentUtil.uTag, JsonUtils.toJson(claims));

            } catch (ExpiredJwtException expiredJwtEx) {
                log.error("Ex.ExpiredJwtException:{},responseCode:{}", expiredJwtEx.getMessage(), responseCode = TOKEN_INVALID);
            } catch (Exception ex) {
                log.error("Ex.Exception:{},responseCode:{}", ex.getMessage(), responseCode = TOKEN_ILLEGAL);
            }
        } else {
            responseCode = TOKEN_ILLEGAL;
        }

        for (String urlPattern : excludeUrlPattern) {
            if (antPathMatcher.match(urlPattern, uri)) {
                return true;
            }
        }
        if (StringUtils.isNotEmpty(responseCode)) {
            log.warn("Authority failed:{},uri:[{}],authorization:{}", responseCode, uri, authorization);
            return this.responseHandlerRefuse(response, responseCode);
        }
        if (StringUtils.isEmpty(currentUid)) {
            return this.responseHandlerRefuse(response, TOKEN_ILLEGAL);
        }
        if (this.authorityHandler(response, uri, token, currentUid, claims)) {
            return true;
        }
        return false;
    }

    public void setExcludeUrlPattern(List<String> excludeUrlPattern) {
        this.excludeUrlPattern = excludeUrlPattern;
    }

    private void responseHandler(HttpServletResponse response, String responseCode) throws IOException {
        ResponseDto<Object> responseDto = ResponseDto.failure(ErrorMapping.get(responseCode));
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType(CONTENT_TYPE);
        response.getWriter().write(JsonUtils.toJson(responseDto));
    }

    private boolean responseHandlerRefuse(HttpServletResponse response, String responseCode) throws IOException {
        this.responseHandler(response, responseCode);
        return false;
    }

    /*private boolean authorityHandler(HttpServletResponse response, String uri, String token, String currentUid, Claims claims) throws IOException {
        String tokenType = (String) claims.get(CurrentUtil.TOKEN_TYPE);
        switch (tokenType) {
            case CurrentUtil.TOKEN_TYPE_VAL_STATION:// 专业版APP
                // adam:identity:sso:${uid}:MD5(${token})=${1-在线|0-离线}
                String ssoUidM5TokenKey = jwtValidator.getMsoRedisKey()
                        .concat(currentUid).concat(":").concat(DigestUtils.md5DigestAsHex(token.getBytes(StandardCharsets.UTF_8)));
                Integer online = (Integer) redisUtil.get(ssoUidM5TokenKey);
                if (null == online || online != 1) {
                    // 已离线
                    this.responseHandler(response, TOKEN_INVALID);

                    return false;
                } else {
                    return true;
                }
            case CurrentUtil.TOKEN_TYPE_VAL_USER:
                // adam:identity:sso:${uid}=MD5(${token})
                String ssoKey = jwtValidator.getSsoRedisKey().concat(currentUid), md5Token;

                if (StringUtils.isEmpty(md5Token = (String) redisUtil.get(ssoKey))) {
                    // 已离线
                    this.responseHandler(response, TOKEN_INVALID);

                    return false;
                } else {
                    // 与在线TOKEN比对
                    if (md5Token.equals(DigestUtils.md5DigestAsHex(token.getBytes(StandardCharsets.UTF_8)))) {
                        // 一致则放行
                        return true;
                    } else {
                        // 不一致则被踢下线
                        this.responseHandler(response, TOKEN_KICK);

                        return false;
                    }
                }
            default:
                log.warn("Authority failed:{} (Unknown token type).uri:[{}],token:{}", TOKEN_ILLEGAL, uri, token);
                this.responseHandler(response, TOKEN_ILLEGAL);
                return false;
        }
    }*/

    private boolean authorityHandler(HttpServletResponse response, String uri, String token, String currentUid, Claims claims) throws IOException {
        String tokenType = (String) claims.get(CurrentUtil.TOKEN_TYPE);
        switch (tokenType) {
            case CurrentUtil.TOKEN_TYPE_VAL_STATION:// [专业版APP] adam:identity:sso:${uid}:MD5(${token})=${1-在线|0-离线}
                String ssoUidM5TokenKey = jwtValidator.getMsoRedisKey()
                        .concat(currentUid).concat(":").concat(DigestUtils.md5DigestAsHex(token.getBytes(StandardCharsets.UTF_8)));

                String val = this.getAccessToken(ssoUidM5TokenKey);
                Integer online = null == val ? null : Integer.valueOf(val);

                return null != online && online == 1 || this.responseHandlerRefuse(response, TOKEN_INVALID);
            case CurrentUtil.TOKEN_TYPE_VAL_USER:// adam:identity:sso:${uid}=MD5(${token})
                String ssoKey = jwtValidator.getSsoRedisKey().concat(currentUid);

                String md5Token = this.getAccessToken(ssoKey);

                return StringUtils.isEmpty(md5Token) ? this.responseHandlerRefuse(response, TOKEN_INVALID)
                        : (md5Token.equals(DigestUtils.md5DigestAsHex(token.getBytes(StandardCharsets.UTF_8))) || this.responseHandlerRefuse(response, TOKEN_KICK));
            default:
                log.warn("Authority failed:{} (Unknown token type).uri:[{}],token:{}", TOKEN_ILLEGAL, uri, token);
                return this.responseHandlerRefuse(response, TOKEN_ILLEGAL);
        }
    }

    /**
     * 查取服务器令牌票据
     *
     * @param ssokey 用户令牌KEY
     * @return String 令牌票据
     */
    public String getAccessToken(String ssokey) {
        String val = null;
        try {
            ResponseDto<String> check = feignAuthorityClient.check(DESUtils.DES().encrypt(ssokey));
            if (check.isSuccess()) {
                String valEncrypt = check.getData();

                if (!StringUtils.isEmpty(valEncrypt)) {
                    val = DESUtils.DES().decrypt(valEncrypt);
                }
            }
        } catch (Exception e) {
            log.error("Authority.check exception", e);
        }
        return val;
    }
}
