package com.liquidnet.service.adam.interceptor;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.common.exception.constant.ErrorCode;
import com.liquidnet.commons.lang.util.DESUtils;
import com.liquidnet.service.ResponseDto;
import com.liquidnet.service.adam.biz.AdamUserLoginBiz;
import com.liquidnet.service.adam.constant.AdamRedisConstants;
import com.liquidnet.service.adam.constant.AdamUserConstants;
import com.liquidnet.service.adam.interceptor.annotation.NoAuth;
import com.liquidnet.service.adam.interceptor.annotation.RequiresPermissions;
import com.liquidnet.service.adam.service.IAdamUserPermissionService;
import com.liquidnet.service.adam.service.IAdamUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Value("${liquidnet.conf.des.key}")
    private String desKey;

    @Value("${liquidnet.conf.login.token.expire-time}")
    private int tokenExpireTime;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private IAdamUserService adamUserService;

    @Autowired
    private AdamUserLoginBiz adamUserLoginBiz;

    @Autowired
    private IAdamUserPermissionService adamUserPermissionService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //检查是否有NoAuth注释，有则跳过认证
        if (method.isAnnotationPresent(NoAuth.class)) {
            NoAuth passToken = method.getAnnotation(NoAuth.class);
            if (passToken.required()) {
                return true;
            }
        }

        ResponseDto<Object> dto = new ResponseDto<>();
        String token = request.getHeader("token");// 从 http 请求头中取出 token
        // 执行认证
        if (Strings.isEmpty(token)) {
            dto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            dto.setMessage("token 为空");
            returnJson(response, dto);
            return false;
        }

        DESUtils desUtils = new DESUtils(desKey);
        String result = null;
        try {
            result = desUtils.decrypt(token);
        } catch (Exception e) {
            dto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            dto.setMessage(ErrorCode.HTTP_SYSTEM_ERROR.getMessage());
            returnJson(response, dto);
            return false;
        }
        if (Strings.isEmpty(result)
                || result.split("_").length != 2) {
            dto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            dto.setMessage("token非法");
            returnJson(response, dto);
            return false;
        }
        String userId = String.valueOf(result.split("_")[1]);
        String userLoginKey = adamUserLoginBiz.getUserLoginKey(userId);
        Object tokenValue = redisUtil.hget(userLoginKey, AdamRedisConstants.USER_LOGIN_ITEM_TOKEN);
        if (null == tokenValue) {
            dto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            dto.setMessage("token失效");
            returnJson(response, dto);
            return false;
        } else if (!token.equalsIgnoreCase(tokenValue.toString())) {
            dto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            dto.setMessage(AdamUserConstants.UserLoginLogMsgTypeEnum.LOGIN_OUT_FORCE_MSG_OTHER_SITE.getDesc());
            returnJson(response, dto);
            return false;
        }

        //判断是否是主动登出
        if (method.getName().equalsIgnoreCase("userLogOut")) {
            log.info("[USER LOGIN OUT] User: userId: {} ", userId);
            adamUserLoginBiz.userLogout(userId);
            return true;
        }

        //判断是否需要强制退出
        ResponseDto<Object> loginOutCheckDto = adamUserLoginBiz.loginOutCheck(userId, token);
        String msgTypeStr = loginOutCheckDto.getData().toString();
        if (!loginOutCheckDto.getCode().equalsIgnoreCase(ErrorCode.SUCCESS.getCode())) {
            //强制退出
            adamUserLoginBiz.forceLogout(userId, AdamUserConstants.UserLoginLogMsgTypeEnum.getEnumByName(msgTypeStr));
            returnJson(response, loginOutCheckDto);
            return false;
        }
        //刷新用户登录token
        adamUserLoginBiz.refreshUserTokenExpireTime(userId, token);

        if (!this.isValidFunctionPermissions(method, userId, userLoginKey)) {
            dto.setCode(ErrorCode.HTTP_FORBIDDEN.getCode());
            dto.setMessage("Insufficient permissions");
            returnJson(response, dto);
            return false;
        }

        return true;
    }

    private void returnJson(HttpServletResponse response, Object body) {
        String jsonObjectStr = JSONObject.toJSONString(body);
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        try {
            writer = response.getWriter();
            writer.print(jsonObjectStr);

        } catch (IOException e) {
            log.error("response error", e);
        } finally {
            if (writer != null)
                writer.close();
        }
    }

    /**
     * Function permissions verification
     *
     * @param method request method
     * @param userId        user ID
     * @return boolean
     */
    private boolean isValidFunctionPermissions(Method method, String userId, String userLoginKey) {
        RequiresPermissions rpAnnotation = method.getAnnotation(RequiresPermissions.class);
        if (Objects.isNull(rpAnnotation)) {
            return true;
        }
        String[] perms = rpAnnotation.value();
        if (perms.length == 0) {
            return true;
        }
        List<String> userPermitList = null;
        try {
            userPermitList = (List<String>) redisUtil.hget(userLoginKey, AdamRedisConstants.USER_LOGIN_ITEM_PERMIT);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (CollectionUtils.isEmpty(userPermitList)) {
            userPermitList = adamUserPermissionService.queryPermissionKeyByUserId(userId);
            if (CollectionUtils.isEmpty(userPermitList)) {
                return false;
            }
            try {
                redisUtil.hset(userLoginKey, AdamRedisConstants.USER_LOGIN_ITEM_PERMIT, userPermitList);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        log.debug("verify:{}.{}:userPermitList:{}", method.getDeclaringClass().getSimpleName(), method.getName(), JSON.toJSONString(userPermitList));

        if (perms.length == 1) {
            return userPermitList.contains(perms[0]);
        } else if (AdamUserConstants.Logical.AND.equals(rpAnnotation.logical())) {
            userPermitList.retainAll(Arrays.asList(perms));
            return perms.length == userPermitList.size();
        } else {
            userPermitList.retainAll(Arrays.asList(perms));
            return userPermitList.size() > 0;
        }
    }
}
