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.CollectionUtils;
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);
    /**
     * 单点登录验证（与模式I、模式II并存）
     * <p>
     * 需要验证单点登录的URI.REGEX
     * -    为空: 默认全不需要单点登录验证
     * -    非空: 配置URI需要单点登录验证
     * </p>
     */
    private List<String> oncheckUrlPattern;
    /**
     * 模式I（与模式II互斥）
     * <p>
     * 无需鉴权的URI.REGEX
     * </p>
     */
    private List<String> excludeUrlPattern;
    /**
     * 模式II（与模式I互斥）
     * <p>
     * 需要鉴权的URI.REGEX
     * </p>
     */
    private List<String> includeUrlPattern;

    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.warn("Ex.ExpiredJwtException:{},responseCode:{}", expiredJwtEx.getMessage(), responseCode = TOKEN_INVALID);
            } catch (Exception ex) {
                log.warn("Ex.Exception:{},responseCode:{}", ex.getMessage(), responseCode = TOKEN_ILLEGAL);
            }
        } else {
            responseCode = TOKEN_ILLEGAL;
        }

        if (!CollectionUtils.isEmpty(excludeUrlPattern)) {
            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);
            }
            return !this.ssoOncheckOptional(uri) || this.authorityHandler(response, uri, token, currentUid, claims);
//            return this.authorityHandler(response, uri, token, currentUid, claims);
        } else if (!CollectionUtils.isEmpty(includeUrlPattern)) {
            for (String urlPattern : includeUrlPattern) {
                if (antPathMatcher.match(urlPattern, uri)) {// 匹配到的需要鉴权
                    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);
                    }
                    return !this.ssoOncheckOptional(uri) || this.authorityHandler(response, uri, token, currentUid, claims);
//                    return this.authorityHandler(response, uri, token, currentUid, claims);
                }
            }
        }
        return true;

//        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 setOncheckUrlPattern(List<String> oncheckUrlPattern) {
        this.oncheckUrlPattern = oncheckUrlPattern;
    }

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

    public void setIncludeUrlPattern(List<String> includeUrlPattern) {
        this.includeUrlPattern = includeUrlPattern;
    }

    /* -- - - -- - - -- - - -- - - -- - - -- - - -- - - -- - - -- - - -- - - -- - - -- - - -- - - -- - - -- - - - */

//    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);
        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));
        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;
        }
    }*/

    /**
     * 根据[oncheckUrlPattern]执行单点登录验证
     *
     * @param uri 请求URI
     * @return true-需要单点验证
     */
    private boolean ssoOncheckOptional(String uri) {
        if (!CollectionUtils.isEmpty(oncheckUrlPattern)) {
            for (String urlPattern : oncheckUrlPattern) {
                if (antPathMatcher.match(urlPattern, uri)) {// 匹配到的单点登录验证
                    return true;
                }
            }
            return false;
        }

        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.getAccessTokenForStation(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:session:{jti}=uid
                String jti = claims.getId();
                if (StringUtils.isBlank(jti)) {
                    return this.responseHandlerRefuse(response, TOKEN_INVALID);
                }
                String sessionUid = this.getAccessToken(jwtValidator.sessionKey(jti));
                return StringUtils.isNotBlank(sessionUid) && sessionUid.equals(currentUid)
                        || this.responseHandlerRefuse(response, TOKEN_INVALID);
            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 {
            long s = System.currentTimeMillis();
            String encrypt = DESUtils.DES().encrypt(ssokey);
//            log.info("#ATH.ENCRYPT耗时:{}ms", System.currentTimeMillis() - s);
//            s = System.currentTimeMillis();
            ResponseDto<String> check = feignAuthorityClient.check(encrypt);
            log.debug("#ATH.VALID耗时:{}ms", System.currentTimeMillis() - s);
            if (check.isSuccess()) {
                String valEncrypt = check.getData();

                if (!StringUtils.isEmpty(valEncrypt)) {
//                    s = System.currentTimeMillis();
                    val = DESUtils.DES().decrypt(valEncrypt);
//                    log.info("#ATH.DECRYPT耗时:{}ms", System.currentTimeMillis() - s);
                }
            }
        } catch (Exception e) {
            log.warn("Ex.Authority Check Exception[ssokey={}]", ssokey, e);
        }
        return val;
    }

    /**
     * 查取专业版账号令牌票据
     *
     * @param msokey 账号令牌KEY
     * @return String 令牌票据
     */
    public String getAccessTokenForStation(String msokey) {
        // 专业版只提供现场验票，且账号体系及验票功能均在`kylin`中，查取逻辑在`kylin`中重写实现
        return null;
    }
}
