package com.uas.platform.b2b.manage.web.filter;

import com.uas.platform.b2b.manage.core.support.SystemSession;
import com.uas.platform.b2b.manage.model.Constant;
import com.uas.platform.b2b.manage.model.User;
import com.uas.platform.b2b.manage.support.SecurityConstant;
import org.apache.log4j.Logger;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * 访问拦截
 *
 * @author hejq
 * @date 2018-04-23 15:48
 */
public class SsoInterceptor extends HandlerInterceptorAdapter implements Filter {

    private static final Logger log = Logger.getLogger(SsoInterceptor.class);

    /**
     * This implementation always returns {@code true}.
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return super.preHandle(request, response, handler) ? this.authenticate(request, response) : false;
    }

    private final boolean authenticate(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Cookie[] cookies = request.getCookies();
        List<Cookie> cookieList = new ArrayList<>();
        if (null != cookies) {
            cookieList = Arrays.asList(cookies);
        }
        final boolean[] cookieExist = {false};
        boolean cookieFlag = checkCookie(cookieExist, cookieList, request);
        if (!cookieFlag) {
            if (!this.onAuthenticateFailed(request, response)) {
                return false;
            } else {
                return true;
            }
        }
        // 通过HttpServletRequest获取登录信息
        logSession(request);
        User user = SystemSession.getUser();
        // 未登录则要求登录
        if (user == null) {
            logoutSession(request);
            if (!this.onAuthenticateFailed(request, response)) {
                return false;
            } else {
                return true;
            }
        } else {
            checkLogin(response);
        }
        return true;
    }

    /**
     * 校验cookie是否存在
     *
     * @param cookieExist 是否存在cookie
     * @param cookieList cookie列表
     * @param request HttpServletRequest
     */
    private boolean checkCookie(boolean[] cookieExist, List<Cookie> cookieList, HttpServletRequest request) {
        cookieList.forEach(cookie -> {
            switch (cookie.getName()) {
                case (Constant.COOKIE_NAME):
                    if (request.getSession().getId().equals(cookie.getValue())) {
                        cookieExist[0] = true;
                    }
                    break;
                default:
            }
        });
        return cookieExist[0];
    }

    /**
     * 校验登录信息
     *
     * @param response HttpServletResponse
     * @throws IOException IO异常
     */
    protected static void checkLogin(HttpServletResponse response) throws IOException {
        User user = SystemSession.getUser();
        if (null == user) {
            response.sendRedirect(SecurityConstant.LOGIN_URL);
        }
    }

    /**
     * 将user信息放在当前线程里面
     *
     * @param request HttpServletRequest
     */
    private void logSession(HttpServletRequest request) throws UnsupportedEncodingException {
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        if (null != user) {
            SystemSession.setUser(user);
            log.info("登录成功，" + user.getName());
        }
    }

    /**
     * 线程池策略下，不会频繁删除线程，置于线程内的对象须手动删除
     */
    private void logoutSession(HttpServletRequest request) {
        SystemSession.clear();
        request.getSession().invalidate();
    }

    /**
     * 验证失败重新登录
     *
     * @param request HttpServletRequest
     * @param response HttpServletResponse
     * @return
     * @throws IOException IO异常
     */
    private boolean onAuthenticateFailed(HttpServletRequest request, HttpServletResponse response) throws IOException {
        removeLocalSession(request);
        if (SecurityConstant.AUTHENTICATION_URL.equals(request.getRequestURI())) {
            return true;
        }
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.sendRedirect(SecurityConstant.LOGIN_URL);
        return false;
    }

    /**
     * 清除登录信息
     *
     * @param request HttpServletRequest
     */
    private void removeLocalSession(HttpServletRequest request) {
        SystemSession.clear();
        request.getSession().removeAttribute("user");
        User user = SystemSession.getUser();
        if (null != user) {
            removeLocalSession(request);
        }
    }

    /**
     * This implementation is empty.
     */
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
    }

    /**
     * This implementation is empty.
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
    }

    /**
     * Called by the web container to indicate to a filter that it is being placed into
     * service. The servlet container calls the init method exactly once after instantiating the
     * filter. The init method must complete successfully before the filter is asked to do any
     * filtering work. <br><br>
     * <p>
     * The web container cannot place the filter into service if the init method either<br>
     * 1.Throws a ServletException <br>
     * 2.Does not return within a time period defined by the web container
     *
     * @param filterConfig
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    /**
     * The <code>doFilter</code> method of the Filter is called by the container
     * each time a request/response pair is passed through the chain due
     * to a client request for a resource at the end of the chain. The FilterChain passed in to this
     * method allows the Filter to pass on the request and response to the next entity in the
     * chain.<p>
     * A typical implementation of this method would follow the following pattern:- <br>
     * 1. Examine the request<br>
     * 2. Optionally wrap the request object with a custom implementation to
     * filter content or headers for input filtering <br>
     * 3. Optionally wrap the response object with a custom implementation to
     * filter content or headers for output filtering <br>
     * 4. a) <strong>Either</strong> invoke the next entity in the chain using the FilterChain object (<code>chain.doFilter()</code>), <br>
     * * 4. b) <strong>or</strong> not pass on the request/response pair to the next entity in the filter chain to block the request processing<br>
     * * 5. Directly set headers on the response after invocation of the next entity in the filter chain.
     *
     * @param request
     * @param response
     * @param chain
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse hsr = (HttpServletResponse) response;
        // HTTP 1.1.
        hsr.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        // HTTP 1.0.
        hsr.setHeader("Pragma", "no-cache");
        // Proxies.
        hsr.setDateHeader("Expires", 0);
        chain.doFilter(request, hsr);
    }

    /**
     * Called by the web container to indicate to a filter that it is being taken out of service. This
     * method is only called once all threads within the filter's doFilter method have exited or after
     * a timeout period has passed. After the web container calls this method, it will not call the
     * doFilter method again on this instance of the filter. <br><br>
     * <p>
     * This method gives the filter an opportunity to clean up any resources that are being held (for
     * example, memory, file handles, threads) and make sure that any persistent state is synchronized
     * with the filter's current state in memory.
     */
    @Override
    public void destroy() {

    }
}
