package com.keyware.shandan.frame.config.security;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.keyware.shandan.common.util.*;
import com.keyware.shandan.frame.properties.CustomProperties;
import com.keyware.shandan.system.entity.SysUser;
import com.keyware.shandan.system.utils.SysSettingUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

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.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * 校验账号、密码前，先进行验证码处理，需要在这里进行登录解密操作
 */
@Component
@Slf4j
public class CaptchaFilterConfig implements Filter {

    @Value("${server.servlet.context-path:}")
    private String contextPath;

    @Value("${server.servlet.session.cookie.name:JSESSIONID}")
    private String authCookieName;

    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private UserConfig userConfig;

    @Autowired
    private CustomProperties customProperties;

    @Autowired
    private LogoutHandler logoutHandler;


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpSession session = request.getSession();
        String requestURI = request.getRequestURI();

        User user = securityUtil.sessionRegistryGetUserBySessionId(session.getId());
        if (user == null) {
            if (!requestPermit(request)) {
                UserDetails rememberMeUser = getRememberUser(request, session);
                if (rememberMeUser != null) {
                    securityUtil.sessionRegistryAddUser(session.getId(), rememberMeUser);
                }else{
                    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
                    SecurityContextHolder.clearContext();
                    logoutHandler.logout(request, response, auth);
                    return;
                }
            }
        } else if("unknown".equals(user.getUsername())){
            logoutRedirect(request, response);
            return;
        }else{

            String sessionUserName = getUserNameBySecurityContext(session);
            if (StringUtils.isBlank(sessionUserName)) {
                sessionUserName = user.getUsername();
            }

            SysUser sysUser = UserConfig.getLoginCacheUserByLoginName(sessionUserName);
            if (sysUser == null) {
                try {
                    UserDetails userDetails = userConfig.loadUserByUsername(sessionUserName);
                    securityUtil.sessionRegistryAddUser(session.getId(), userDetails);
                } catch (Exception e) {
                    log.error("没有找到该用户信息：" + sessionUserName);
                    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
                    //if(!auth.isAuthenticated()){
                    logoutHandler.logout(request, response, auth);
                    return;
                    //}
                }
            }
        }

        //只拦截登录请求，且开发环境下不拦截
        if ("POST".equals(request.getMethod()) && "/login".equals(requestURI.replaceFirst(contextPath, ""))) {
            //前端公钥
            String publicKey = null;

            //jackson
            ObjectMapper mapper = new ObjectMapper();
            //jackson 序列化和反序列化 date处理
            mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

            //判断api加密开关是否开启
            if (SysSettingUtil.getCurrentSysSetting().getSysApiEncrypt()) {
                //解密
                try {
                    //AES加密后的数据
                    String decrypt = AesUtil.decrypt(request);

                    //设置到方法的形参中，目前只能设置只有一个参数的情况
                    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

                    //new一个自定义RequestWrapper
                    HashMap hashMap = mapper.readValue(decrypt, HashMap.class);
                    ParameterRequestWrapper parameterRequestWrapper = new ParameterRequestWrapper(request);
                    for (Object key : hashMap.keySet()) {
                        parameterRequestWrapper.addParameter(String.valueOf(key), hashMap.get(key));
                    }

                    servletRequest = parameterRequestWrapper;
                    request = (HttpServletRequest) servletRequest;
                } catch (Throwable e) {
                    //输出到日志文件中
                    log.error(ErrorUtil.errorInfoToString(e));
                }
            }

            //从session中获取生成的验证码
            if (customProperties.getCaptchaEnable()) {
                String verifyCode = session.getAttribute("verifyCode").toString();

                if (!verifyCode.equalsIgnoreCase(request.getParameter("captcha"))) {
                    String dataString = "{\"code\":\"400\",\"msg\":\"验证码错误\"}";

                    //判断api加密开关是否开启
                    if (SysSettingUtil.getCurrentSysSetting().getSysApiEncrypt()) {
                        //加密
                        try {
                            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                            //每次响应之前随机获取AES的key，加密data数据
                            String key = AesUtil.getKey();
                            String data = AesUtil.encrypt(dataString, key);

                            //用前端的公钥来解密AES的key，并转成Base64
                            String aesKey = Base64.encodeBase64String(RsaUtil.encryptByPublicKey(key.getBytes(), publicKey));
                            dataString = "{\"data\":{\"data\":\"" + data + "\",\"aesKey\":\"" + aesKey + "\"}}";
                        } catch (Throwable e) {
                            //输出到日志文件中
                            log.error(ErrorUtil.errorInfoToString(e));
                        }
                    }

                    //转json字符串并转成Object对象，设置到Result中并赋值给返回值o
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("application/json; charset=utf-8");
                    PrintWriter out = response.getWriter();
                    out.print(dataString);
                    out.flush();
                    out.close();
                    response.flushBuffer();
                    return;
                }
            }

        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    private void logoutRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SecurityContextHolder.getContext().setAuthentication(null);
        SecurityContextHolder.clearContext();

        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                cookie.setMaxAge(0);
                response.addCookie(cookie);
            }
        }

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.print("<script type='text/javascript'>window.location.href = '" + contextPath + "/login'</script>");
        out.flush();
        out.close();
        response.flushBuffer();
        //response.sendRedirect("/logout");
    }

    private String getUserNameBySecurityContext(HttpSession session) {
        String userName = "";
        Object securityContext = session.getAttribute("SPRING_SECURITY_CONTEXT");
        if (securityContext != null) {
            SecurityContextImpl context = (SecurityContextImpl) securityContext;
            Authentication auth = context.getAuthentication();
            if (auth != null) {
                Object principal = auth.getPrincipal();
                if (principal instanceof User) {
                    userName = ((User) principal).getUsername();
                } else if (principal instanceof String) {
                    userName = (String) principal;
                }
            }
        }
        return userName;
    }

    private UserDetails getRememberUser(HttpServletRequest request, HttpSession session) {
        Cookie rememberMeCookie = SecurityUtil.getRememberMeCookie(request);
        PersistentRememberMeToken token = securityUtil.rememberMeGetTokenForSeries(rememberMeCookie);

        if (!ObjectUtils.isEmpty(token)) {
            log.info("当前session连接开启了免登录，已自动登录！token：" + rememberMeCookie.getValue() + ",userName：" + token.getUsername() + "，最后登录时间：" + token.getDate());
            UserDetails user = userConfig.loadUserByUsername(token.getUsername());
            //注册新的session
            securityUtil.sessionRegistryAddUser(session.getId(), user);
            return user;
        }
        return null;
    }

    public boolean requestPermit(HttpServletRequest request) {
        return StreamUtil.as(antMatchers(SecurityConfig.MATCHERS_PERMITALL_URL)).anyMatch(matcher -> matcher.matches(request));
    }

    public static List<RequestMatcher> antMatchers(String... antPatterns) {
        List<RequestMatcher> matchers = new ArrayList<>();
        for (String pattern : antPatterns) {
            matchers.add(new AntPathRequestMatcher(pattern, null));
        }
        return matchers;
    }
}
