package com.tzld.piaoquan.api.config;

import com.alibaba.fastjson.JSON;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.tzld.piaoquan.api.annotation.JwtIgnore;
import com.tzld.piaoquan.api.common.enums.ExceptionEnum;
import com.tzld.piaoquan.api.common.exception.CommonException;
import com.tzld.piaoquan.api.model.config.LoginUserContext;
import com.tzld.piaoquan.api.model.po.contentplatform.ContentPlatformAccount;
import com.tzld.piaoquan.api.service.contentplatform.ContentPlatformAccountService;
import com.tzld.piaoquan.growth.common.utils.RedisUtils;
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.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@ConfigurationProperties(prefix = "author.interceptor")
public class JwtInterceptor implements HandlerInterceptor {

    ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 4,
            0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100000),
            new ThreadFactoryBuilder().setNameFormat("login-service-pool-%d").build(), new ThreadPoolExecutor.AbortPolicy());

    private static final String SLASH = "/";
    public static String TOKEN_PREFIX = "login.{token}";
    private Set<String> excludePaths;
    private Set<String> excludeUris;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private ContentPlatformAccountService accountService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (HttpMethod.OPTIONS.name().equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Methods", "*");
            response.setHeader("Access-Control-Allow-Headers", "*");
            response.setHeader("Access-Control-Max-Age", "3600");
            return true;
        }

        if ((StringUtils.isNotBlank(request.getRequestURI()) && request.getRequestURI().contains("auth")) ||
                (Objects.nonNull(request.getRequestURL()) && request.getRequestURL().toString().contains("auth"))
        ) {
            return Boolean.TRUE;
        }
        // 忽略带JwtIgnore注解的请求, 不做后续token认证校验
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            JwtIgnore jwtIgnore = handlerMethod.getMethodAnnotation(JwtIgnore.class);
            if (jwtIgnore != null) {
                return Boolean.TRUE;
            }
        }
        String uri = request.getServletPath();
        if (this.exclude(uri)) {
            //放行excludePaths包含的目录路径和excludeUris包含的URI
            return true;
        }
        final String authHeader = request.getHeader("token");
        if (StringUtils.isBlank(authHeader)) {
            throw new CommonException(ExceptionEnum.Not_LOGIN);
        }

        validToken(authHeader);

        return Boolean.TRUE;
    }


    /**
     * 校验Token
     */
    private void validToken(String authHeader) {
        String loginUserString = redisUtils.getString(TOKEN_PREFIX.replace("{token}", authHeader));
        if (StringUtils.isBlank(loginUserString)) {
            throw new CommonException(ExceptionEnum.Not_LOGIN);
        }
        ContentPlatformAccount account;
        try {
            account = JSON.parseObject(loginUserString, ContentPlatformAccount.class);
        } catch (Exception e) {
            throw new CommonException(ExceptionEnum.Not_LOGIN);
        }
        // token验证完成后获取对应信息，存放在本地线程中
        LoginUserContext.setLoginUser(account);
        // 异步处理续期
        renewalToken(authHeader);
    }

    private boolean exclude(String uri) {
        if (SLASH.equals(uri)) {
            return true;
        }
        // 仅校验内容平台接口
        if (!uri.startsWith("/contentPlatform")) {
            return true;
        }
        for (String path : excludePaths) {
            if (uri.startsWith(path)) {
                return true;
            }
        }
        return excludeUris.contains(uri);
    }

    public Set<String> getExcludePaths() {
        return excludePaths;
    }

    public void setExcludePaths(Set<String> excludePaths) {
        this.excludePaths = excludePaths;
    }

    public Set<String> getExcludeUris() {
        return excludeUris;
    }

    public void setExcludeUris(Set<String> excludeUris) {
        this.excludeUris = excludeUris;
    }

    private void renewalToken(String token) {
        executor.execute(() -> {
            try {
                String key = TOKEN_PREFIX.replace("{token}", token);
                String loginUserString = redisUtils.getString(key);
                if (StringUtils.isBlank(loginUserString)) {
                    return;
                }
                ContentPlatformAccount account = JSON.parseObject(loginUserString, ContentPlatformAccount.class);
                Long seconds = redisUtils.getKeyExpire(key);
                if (seconds < 24 * 60 * 60) {
                    // 续期时重新从数据库获取最新账户信息并更新缓存
                    if (Objects.nonNull(account) && Objects.nonNull(account.getId())) {
                        ContentPlatformAccount latestAccount = accountService.getAccountById(account.getId());
                        if (Objects.nonNull(latestAccount)) {
                            redisUtils.setValueWithExpire(key, JSON.toJSONString(latestAccount), RedisUtils.DEFAULT_EXPIRE_TIME);
                        }
                    }
                }
            } catch (Exception e) {
                // 续期失败不影响正常请求，仅记录日志
                log.error("renewalToken error, token={}", token, e);
            }
        });
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception
            ex) throws Exception {
        LoginUserContext.remove();
    }
}