package com.docus.server.common.auth;

import com.alibaba.fastjson.JSON;
import com.docus.infrastructure.web.exception.ApiException;
import com.docus.infrastructure.web.exception.ExceptionCode;
import com.docus.server.common.IgnoreValidate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

public class TokenInterceptor extends HandlerInterceptorAdapter {

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

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //A/访问后自动延长重置token过期时间
    private boolean overtimeAfterAccess = true;

    public void setovertimeAfterAccess(boolean overtimeAfterAccess) {
        this.overtimeAfterAccess = overtimeAfterAccess;
    }

    private String tokenHeaderName = "api-token";
    private String userIdHeaderName = "user-id";
    private String tokenRedisKeyPrefix = "apiToken:";
    private String userIdPropertyName = "userId";

    public TokenInterceptor() {

    }

    public TokenInterceptor(String tokenHeaderName, String userIdHeaderName, String tokenRedisKeyPrefix) {
        this.tokenHeaderName = tokenHeaderName;
        this.userIdHeaderName = userIdHeaderName;
        this.tokenRedisKeyPrefix = tokenRedisKeyPrefix;
    }

    public TokenInterceptor(String tokenHeaderName, String userIdHeaderName, String tokenRedisKeyPrefix, String userIdPropertyName) {
        this.tokenHeaderName = tokenHeaderName;
        this.userIdHeaderName = userIdHeaderName;
        this.tokenRedisKeyPrefix = tokenRedisKeyPrefix;
        this.userIdPropertyName = userIdPropertyName;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String path = request.getRequestURI();
        // 跟swagger冲突，跳出response包装
        if (path.contains("swagger") || path.contains("api-docs") || path.contains("error") || path.contains("/monitor/url-mappings") || path.contains("doc.html")) {
            //忽略验证
            return super.preHandle(request, response, handler);
        }
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Class<?> controller = handlerMethod.getMethod().getDeclaringClass();
            if (handlerMethod.hasMethodAnnotation(IgnoreValidate.class) || controller.getAnnotation(IgnoreValidate.class) != null) {
                //忽略验证
                return super.preHandle(request, response, handler);
            }
        }
        validateToken(request);
        return super.preHandle(request, response, handler);
    }

    //验证token是否有效，并把token对应的用户信息存入上下文变量
    protected void validateToken(HttpServletRequest request) {
        String token = request.getHeader(tokenHeaderName);
        if (StringUtils.isEmpty(token)) {
            logger.info("token is empty");
            throw new ApiException(ExceptionCode.TokenError);
        }

        String redisKey = tokenRedisKeyPrefix + token;
        if (stringRedisTemplate == null) {
            throw new RuntimeException("启用token验证依赖redis");
        }
        String redisValue = stringRedisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isEmpty(redisValue)) {
            logger.info("token {} is not exist or expired", token);
            throw new ApiException(ExceptionCode.TokenError);
        }
        String userId = JSON.parseObject(redisValue).getString(userIdPropertyName);
        if (StringUtils.isEmpty(userId)) {
            logger.info("userId property {} not exists", userIdPropertyName);
            throw new ApiException(ExceptionCode.TokenError);
        }
        String headerUserId = request.getHeader(userIdHeaderName);
        if (!userId.equalsIgnoreCase(headerUserId)) {
            //非法 token , token和userid不匹配
            logger.info("token {} is not match userId {}", token, headerUserId);
            throw new ApiException(ExceptionCode.TokenError);
        }
        //重置token过期时间
        if (overtimeAfterAccess) {
            Long expireMinutes = stringRedisTemplate.getExpire(redisKey, TimeUnit.MINUTES);
            //-1表示不过期
            if (expireMinutes > -1 && expireMinutes < 60) {
                stringRedisTemplate.expire(redisKey, 60, TimeUnit.MINUTES);
            }
        }
    }
}
