package com.liquidnet.common.web.filter;

import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.commons.lang.core.JwtValidator;
import com.liquidnet.commons.lang.util.CurrentUtil;
import com.liquidnet.commons.lang.util.JsonUtils;
import com.liquidnet.service.base.ErrorMapping;
import com.liquidnet.service.base.ResponseDto;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment;
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;

@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "global-auth")
public class GlobalAuthorityInterceptor extends HandlerInterceptorAdapter {
    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 static final String START_TIME = "_startTime";
    private static final String ENV_PROD = "prod";
    private static final String ENV_ACTIVE = "spring.profiles.active";

    private static final String KYLIN_STATION_JWT_VALID = "/station/**";

    private final static AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    Environment env;
    @Autowired
    JwtValidator jwtValidator;
    @Autowired
    RedisUtil redisUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.setAttribute(START_TIME, System.currentTimeMillis());

        String authorization = request.getHeader(CurrentUtil.uToken), uri = request.getRequestURI(),
                responseCode = null, token = null, currentUid = null;

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

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

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

            } catch (ExpiredJwtException expiredJwtEx) {
                responseCode = TOKEN_INVALID;
            } catch (Exception ex) {
                responseCode = TOKEN_ILLEGAL;
            }
        } else {
            responseCode = TOKEN_ILLEGAL;
        }

        for (String urlPattern : excludeUrlPattern) {
            if (antPathMatcher.match(urlPattern, uri)) {
                return true;
            }
        }
        if (StringUtils.isNotEmpty(responseCode)) {
            this.responseHandler(response, responseCode);
            return false;
        }
        if (StringUtils.isEmpty(currentUid)) {
            this.responseHandler(response, TOKEN_ILLEGAL);
            return false;
        }
        if (this.authorityHandler(response, uri, token, currentUid)) {
            return true;
        }
        return false;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        if (!StringUtils.equalsIgnoreCase(env.getProperty(ENV_ACTIVE), ENV_PROD)) {
            Long endTime = System.currentTimeMillis();
            Long startTime = (Long) request.getAttribute("_startTime");
            log.info("[{}]耗时:{}s", request.getRequestURI(), ((endTime - startTime) * 1.000) / 1000);
        }
    }

    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 authorityHandler(HttpServletResponse response, String uri, String token, String currentUid) throws IOException {
        if (antPathMatcher.match(KYLIN_STATION_JWT_VALID, uri)) {// 专业版APP
            // adam:identity:sso:${uid}:MD5(${token})=${1-在线|0-离线}
//            String ssoUidM5TokenKey = jwtValidator.getSsoRedisKey()
//                    .concat(uid).concat(":").concat(DigestUtils.md5DigestAsHex(uToken.getBytes(StandardCharsets.UTF_8)));
//            Integer online = (Integer) redisUtil.get(ssoUidM5TokenKey);
//            if (null == online || online != 1) {
//                this.respHandler(ctx, TOKEN_INVALID);
//            } else {
            return true;
//            }
        } else {
            // 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;
                }
            }
        }
    }
}
