package cn.com.qmth.stmms.api.interceptor;

import cn.com.qmth.stmms.api.exception.ApiException;
import cn.com.qmth.stmms.biz.school.model.School;
import cn.com.qmth.stmms.biz.school.service.SchoolService;
import cn.com.qmth.stmms.biz.user.model.User;
import cn.com.qmth.stmms.biz.user.service.UserService;
import cn.com.qmth.stmms.common.annotation.RoleRequire;
import cn.com.qmth.stmms.common.authorization.AuthorizationService;
import cn.com.qmth.stmms.common.domain.ApiUser;
import cn.com.qmth.stmms.common.domain.AuthInfo;
import cn.com.qmth.stmms.common.enums.Role;
import cn.com.qmth.stmms.common.session.service.SessionService;
import cn.com.qmth.stmms.common.signature.SignatureInfo;
import cn.com.qmth.stmms.common.signature.SignatureType;
import cn.com.qmth.stmms.common.utils.EncryptUtils;
import cn.com.qmth.stmms.common.utils.RequestUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.Date;

/**
 * API接口访问拦截器
 *
 * @author LS
 */
public class ApiInterceptor extends HandlerInterceptorAdapter {

    protected static final Logger log = LoggerFactory.getLogger(ApiInterceptor.class);

    @Autowired
    private AuthorizationService authorizationService;

    @Autowired
    private SchoolService schoolService;

    @Autowired
    private UserService userService;

    @Autowired
    private SessionService sessionService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        HandlerMethod method = (HandlerMethod) handler;
        try {
            return validate(request, response, method.getMethodAnnotation(RoleRequire.class));
        } catch (ApiException e) {
            response.sendError(e.getCode(), StringUtils.trimToEmpty(e.getMessage()));
            return false;
        } catch (Exception ee) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, StringUtils.trimToEmpty(ee.getMessage()));
            return false;
        }
    }

    private boolean validate(HttpServletRequest request, HttpServletResponse response, RoleRequire authConfig) {
        if (authConfig != null && authConfig.value().length > 0) {
            AuthInfo simple;
            // 优先按签名模式解析
            SignatureInfo info = authorizationService.buildSignature(request);
            if (info != null) {
                if (info.getType() == SignatureType.SECRET) {
                    School school = schoolService.findByAccessKey(info.getInvoker());
                    if (school == null || !info.validate(school.getAccessSecret())) {
                        throw ApiException.SIGNATURE_INVALID;
                    }
                    if (!school.isEnable()) {
                        throw ApiException.USER_DISABLED;
                    }
                    if (!matchRole(authConfig, Role.SCHOOL_DEV)) {
                        throw ApiException.USER_ROLE_INVALID;
                    }
                    return buildApiUser(request, response, school);
                } else if (info.getType() == SignatureType.TOKEN) {
                    User user = userService.findByLoginName(info.getInvoker());
                    if (user == null || user.getScanTokenInvalidTime() == null || new Date()
                            .after(user.getScanTokenInvalidTime()) || !info.validate(user.getScanToken())) {
                        try {
                            ObjectMapper mapper = new ObjectMapper();
                            log.warn("api token signature invalud, signature={}, user={}",
                                    mapper.writeValueAsString(info),
                                    user != null ? mapper.writeValueAsString(user) : "{}");
                        } catch (Exception ignored) {
                        }
                        throw ApiException.SIGNATURE_INVALID;
                    }
                    if (!user.isEnable()) {
                        throw ApiException.USER_DISABLED;
                    }
                    if (!matchRole(authConfig, user.getRole())) {
                        throw ApiException.USER_ROLE_INVALID;
                    }
                    return buildApiUser(request, response, user);
                } else {
                    throw ApiException.SIGNATURE_INVALID;
                }
            }
            // 再尝试按简单模式解析
            else if ((simple = authorizationService.buildAuthInfo(request)) != null) {
                User user = userService.findByLoginName(simple.getLoginname());
                if (user == null || !EncryptUtils.md5(simple.getPassword()).equals(user.getPassword())) {
                    throw ApiException.SIGNATURE_INVALID;
                }
                if (!user.isEnable()) {
                    throw ApiException.USER_DISABLED;
                }
                if (!matchRole(authConfig, user.getRole())) {
                    throw ApiException.USER_ROLE_INVALID;
                }
                return buildApiUser(request, response, user);
            } else {
                throw ApiException.AUTHORIZATION_UNEXIST;
            }
        }
        return true;
    }

    private boolean matchRole(RoleRequire authConfig, Role role) {
        int count = 0;
        for (Role r : authConfig.value()) {
            if (r == role) {
                count++;
            }
        }
        if (authConfig.nand()) {
            return count > 0;
        } else {
            return count == authConfig.value().length;
        }
    }

    private boolean buildApiUser(HttpServletRequest request, HttpServletResponse response, School school) {
        RequestUtils.setApiUser(request, new ApiUser(school));
        sessionService.put(request, response, RequestUtils.getSession(request));
        return true;
    }

    private boolean buildApiUser(HttpServletRequest request, HttpServletResponse response, User user) {
        RequestUtils.setApiUser(request, new ApiUser(user));
        sessionService.put(request, response, RequestUtils.getSession(request));
        return true;
    }

}
