package com.zhiqiyun.open.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhiqiyun.open.annotation.Permission;
import com.zhiqiyun.open.core.service.UserOperateLogService;
import com.zhiqiyun.open.exception.NoLoginException;
import com.zhiqiyun.open.exception.PermissionDeniedException;
import com.zhiqiyun.open.core.models.user.OauthInfo;
import com.zhiqiyun.open.core.models.user.UserOperateLog;
import com.zhiqiyun.open.core.service.OauthService;
import com.zhiqiyun.open.utils.DateUtil;
import com.zhiqiyun.open.utils.ServletContext;
import com.zhiqiyun.open.utils.request.RequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

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

/**
 * @author jtoms
 */
@Slf4j
@Component
public class OauthInterceptor implements HandlerInterceptor {

    @Autowired
    private OauthService oauthService;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private UserOperateLogService userOperateLogService;

    @Resource
    private RedisTemplate<String, UserOperateLog> redisTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws PermissionDeniedException, NoLoginException {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Permission permission = handlerMethod.getMethodAnnotation(Permission.class);
            if (permission != null) {

                String accessToken = ServletContext.getAccessToken();
                if (StringUtils.isBlank(accessToken)) {
                    throw new NoLoginException();
                }

                OauthInfo oauthInfo = oauthService.getAuth(accessToken);
                if (oauthInfo == null || oauthInfo.getId() == null || StringUtils.isBlank(oauthInfo.getAccessToken())) {
                    throw new NoLoginException();
                }
                //把操作日志临时存放到redis中，等处理完后统一保存
                this.saveOperateLogToRedis(permission, oauthInfo);

                String[] arrayAuthorities = permission.value();
                if (arrayAuthorities.length > 0) {
                    if (oauthInfo.getAuthorities() == null || oauthInfo.getAuthorities().isEmpty()) {
                        throw new PermissionDeniedException();
                    }

                    boolean hasAuthority = false;
                    for (String authority : arrayAuthorities) {
                        if (oauthInfo.getAuthorities().contains(authority.trim())) {
                            hasAuthority = true;
                            break;
                        }
                    }
                    if (!hasAuthority) {
                        throw new PermissionDeniedException();
                    }
                }
            }
        }
        return true;
    }

    @Override
    public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) {
        log.error("", ex);
        String requestId = ServletContext.getRequestId();
        String redisKey = String.format(RedisKeys.USER_OPERATE_LOG_TEMP, requestId);
        UserOperateLog userOperateLog = this.redisTemplate.boundValueOps(redisKey).get();
        if (userOperateLog != null) {
            userOperateLog.setResponseTime(DateUtil.current());
            userOperateLog.setId(ServletContext.getRequestId());
            this.threadPoolTaskExecutor.execute(() -> userOperateLogService.save(userOperateLog));
        }
    }

    private void saveOperateLogToRedis(Permission permission, OauthInfo oauthInfo) {
        try {
            String requestId = ServletContext.getRequestId();
            String requestIp = ServletContext.getRemoteIPAddress();
            String fromSource = ServletContext.getFromSource();

            String requestUrl = ServletContext.getRequest().getRequestURI();
            String jsonParam = "";
            if (ServletContext.isJson()) {
                jsonParam = new RequestWrapper(ServletContext.getRequest()).getBodyString();
            }

            Map<String, String> requestParam = ServletContext.getRequestParamMap();

            UserOperateLog userOperateLog = new UserOperateLog();
            userOperateLog.setTags(permission.tags());
            userOperateLog.setUid(oauthInfo.getId());
            userOperateLog.setId(requestId);
            userOperateLog.setFromSource(fromSource);
            userOperateLog.setRequestIp(requestIp);
            userOperateLog.setRequestUrl(requestUrl);
            userOperateLog.setRequestParams(objectMapper.writeValueAsString(requestParam));
            userOperateLog.setRequestData(jsonParam);
            userOperateLog.setRequestTime(DateUtil.current());

            String redisKey = String.format(RedisKeys.USER_OPERATE_LOG_TEMP, requestId);
            this.redisTemplate.boundValueOps(redisKey).set(userOperateLog, 60, TimeUnit.SECONDS);

        } catch (Exception e) {
            log.error("", e);
        }
    }
}
