package com.qmth.teachcloud.common.util;

import com.qmth.boot.api.annotation.Aac;
import com.qmth.boot.api.annotation.BOOL;
import com.qmth.boot.core.enums.Platform;
import com.qmth.boot.tools.signature.SignatureType;
import com.qmth.teachcloud.common.SignatureEntityTest;
import com.qmth.teachcloud.common.bean.auth.AuthBean;
import com.qmth.teachcloud.common.bean.auth.ExpireTimeBean;
import com.qmth.teachcloud.common.contant.SpringContextHolder;
import com.qmth.teachcloud.common.contant.SystemConstant;
import com.qmth.teachcloud.common.entity.SysUser;
import com.qmth.teachcloud.common.entity.TBSession;
import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
import com.qmth.teachcloud.common.enums.PrivilegePropertyEnum;
import com.qmth.teachcloud.common.enums.RoleTypeEnum;
import com.qmth.teachcloud.common.service.CommonCacheService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * @Description: 鉴权工具类util
 * @Param:
 * @return:
 * @Author: wangliang
 * @Date: 2020/11/12
 */
public class AuthUtil {
    private final static Logger log = LoggerFactory.getLogger(AuthUtil.class);

    static CommonCacheService commonCacheService = null;

    /**
     * admin鉴权
     *
     * @param request
     * @param response
     * @param handler
     * @return
     */
    public static boolean adminAuthInterceptor(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String url = request.getServletPath();
        commonCacheService = SpringContextHolder.getBean(CommonCacheService.class);
        Set<String> privilegeUrl = commonCacheService.privilegeUrlCache(PrivilegePropertyEnum.NO_AUTH);
        //无需鉴权的url
        int noAuthCount = Objects.nonNull(privilegeUrl) ? (int) privilegeUrl.stream().filter(s -> s.equalsIgnoreCase(url)).count() : 0;
        if (noAuthCount > 0) {
            return true;
        }
        String method = request.getMethod();
        if (url.equalsIgnoreCase(SystemConstant.ERROR)) {
            throw ExceptionResultEnum.NOT_FOUND.exception();
        }
        Platform platform = ServletUtil.getRequestPlatform();
        String deviceId = ServletUtil.getRequestDeviceId();
        String authorization = ServletUtil.getRequestAuthorization();
        Long time = ServletUtil.getRequestTime();
        log.info("Start authorization: url:{}, method:{}, platform:{}, deviceId:{}, authorization:{}, time:{}", url,
                method, platform, deviceId, authorization, time);

        TBSession tbSession = authHeadCommon(platform, deviceId, authorization, time, method, url);
        Long userId = Long.parseLong(tbSession.getIdentity());
        SysUser sysUser = commonCacheService.userCache(userId);
        request.setAttribute(SystemConstant.SESSION, tbSession);
        request.setAttribute(SystemConstant.USER, sysUser);
        return authFootCommon(userId, SystemConstant.USER_OAUTH_CACHE, request, response, handler);
    }

    /**
     * 鉴权头公用
     *
     * @param platform
     * @param deviceId
     * @param authorization
     * @param time
     * @return
     */
    static TBSession authHeadCommon(Platform platform,
                                    String deviceId,
                                    String authorization,
                                    long time,
                                    String method,
                                    String url) {
        RedisUtil redisUtil = SpringContextHolder.getBean(RedisUtil.class);
//        if (SystemConstant.expire(time)) {
//            log.warn("Authorization faile: time expired, server time=" + System.currentTimeMillis());
//            throw new BusinessException(ExceptionResultEnum.AUTHORIZATION_ERROR);
//        }
        //测试
        final SignatureEntityTest info = SignatureEntityTest.parse(authorization);
        //校验签名信息
//        final SignatureInfo info = SignatureInfo.parse(method.toLowerCase(), url, time, authorization);
        if (info == null) {
            log.warn("Authorization faile: signature decode error");
            throw ExceptionResultEnum.AUTHORIZATION_ERROR.exception();
        }
        if (SignatureType.TOKEN != info.getType()) {
            log.warn("Authorization faile: signature type is not Token");
            throw ExceptionResultEnum.AUTHORIZATION_ERROR.exception();
        }
        //校验session
        String sessionId = info.getInvoker();
        TBSession tbSession = (TBSession) redisUtil.getUserSession(sessionId);
        if (Objects.isNull(tbSession)) {
            log.warn("Authorization faile: session id not exists: " + sessionId);
            throw ExceptionResultEnum.NOT_LOGIN.exception();
        }
        if (tbSession.getExpireTime() <= System.currentTimeMillis() || info.getTimestamp() > tbSession.getExpireTime()) {
            log.warn("Authorization faile: session has expired, expire time=" + tbSession.getExpireTime());
            throw ExceptionResultEnum.NOT_LOGIN.exception();
        }
        if (!info.validate(tbSession.getAccessToken())) {
            log.warn("Authorization faile: access token invalid, session token is " + tbSession.getAccessToken());
            throw ExceptionResultEnum.AUTHORIZATION_ERROR.exception();
        }
        if (!tbSession.getPlatform().equalsIgnoreCase(platform.name())) {
            log.warn("Authorization faile: platform invalid, session platform is " + tbSession.getPlatform());
            throw ExceptionResultEnum.AUTHORIZATION_ERROR.exception();
        }
        if (!tbSession.getDeviceId().equalsIgnoreCase(deviceId)) {
            log.warn("Authorization faile: deviceId invalid, session deviceId is " + tbSession.getDeviceId());
            throw ExceptionResultEnum.AUTHORIZATION_ERROR.exception();
        }
        return tbSession;
    }

    /**
     * 鉴权尾公用
     *
     * @param userId
     * @param type
     * @param request
     * @param response
     * @param handler
     * @return
     */
    static boolean authFootCommon(long userId,
                                  String type,
                                  HttpServletRequest request,
                                  HttpServletResponse response,
                                  Object handler) {
        String url = request.getServletPath();
        //验证权限
        AuthBean authBean = type.contains(SystemConstant.USER_OAUTH_CACHE) ? authBean = commonCacheService.userAuthCache(userId) : null;
        if (Objects.isNull(authBean)) {
            throw ExceptionResultEnum.ROLE_ENABLE_AUTHORIZATION.exception();
        }
        request.setAttribute(SystemConstant.SCHOOL, authBean.getSchool());
        request.setAttribute(SystemConstant.ORG, authBean.getOrg());

        //超级系统管理员拥有所有权限
        int count = Objects.nonNull(authBean) ? (int) authBean.getRoleList().stream().filter(s -> Objects.equals(s.getName(), RoleTypeEnum.ADMIN.getDesc())).count() : 0;
        if (count > 0) {
            return true;
        }
        //系统公用接口不拦截
        Set<String> sysUrls = commonCacheService.privilegeUrlCache(PrivilegePropertyEnum.SYS, SystemConstant.getHeadOrUserSchoolId());
        int sysCount = Objects.nonNull(sysUrls) ? (int) sysUrls.stream().filter(s -> s.equalsIgnoreCase(url)).count() : 0;
        if (sysCount > 0) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Map<String, Boolean> map = new HashMap<>();
        Aac beanTypeAac = handlerMethod.getBeanType().getAnnotation(Aac.class);
        Aac classAac = handlerMethod.getMethodAnnotation(Aac.class);
        if (Objects.nonNull(beanTypeAac)) {
            map.computeIfAbsent(SystemConstant.AUTH, v -> beanTypeAac.auth() == BOOL.TRUE ? true : false);
        }
        if (Objects.nonNull(classAac)) {
            map.computeIfAbsent(SystemConstant.AUTH, v -> classAac.auth() == BOOL.TRUE ? true : false);
        }

        if (Objects.nonNull(map.get(SystemConstant.AUTH)) && !map.get(SystemConstant.AUTH)) {
            return true;
        }
        Set<String> urls = authBean.getUrls();
        int privilegeCount = Objects.nonNull(urls) ? (int) urls.stream().filter(s -> s.equalsIgnoreCase(url)).count() : 0;
        if (privilegeCount == 0) {
            log.warn("Authorization faile: url cannot access");
            throw ExceptionResultEnum.UN_AUTHORIZATION.exception();
        }
        response.setStatus(ExceptionResultEnum.SUCCESS.getCode());
        return true;
    }

    /**
     * 获取过期时间
     *
     * @param platform
     * @return
     */
    public static ExpireTimeBean getExpireTime(Platform platform) {
        ExpireTimeBean expireTimeBean = new ExpireTimeBean();
        Date now = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(now);
        calendar.add(Calendar.DAY_OF_YEAR, SystemConstant.DEFAULT_SESSION_EXPIRE);
        expireTimeBean.setDate(calendar.getTime());
        expireTimeBean.setExpireSeconds(SystemConstant.REDIS_DEFAULT_EXPIRE_TIME);
        return expireTimeBean;
    }
}
