package com.bonus.common.security.interceptor;

import com.bonus.common.core.context.SecurityContextHolder;
import com.bonus.common.core.utils.SafeUtil;
import com.bonus.common.core.utils.StringUtils;
import com.bonus.common.core.web.domain.AjaxResult;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
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.util.*;

import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;

/**
 * @author bonus
 * @data 2023/2/6 17:22
 * @description 安全参数验证
 */
@Slf4j
public class ParamSecureHandler implements AsyncHandlerInterceptor {

    private static final String [] WHITE_URL = {
            "/login", "/isAdmin", "/isLogin" ,"/register","/user/register","/operlog/addLogs","/job/edit",
            "/user","/user/resetPwd","/user/profile/updatePwd","/user/confirmPassword","/dept","/dept/edit"};
    private String rnd = null;

    public static String ur = "/";

    public static int max_length = 4;

    private static Map<String, List<Double>> requestLogMap = null;

    public boolean isFileUpload(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (StringUtils.isEmpty(contentType)) {
            return true;
        }
        if (contentType.contains(MULTIPART_FORM_DATA_VALUE)) {
            return true;
        }
        return false;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 过滤文件上传功能
        if (isFileUpload(request)) {
            return true;
        }

        XssRequestWrapper requestWrapper = new XssRequestWrapper(request);
        String requestUrl = requestWrapper.getRequestURI();
        if (isWhiteUrl(requestUrl)){
            return true;
        }
        /**
         * 白名单中不验证参数
         */
        if (!requestWrapper.isChecked()) {
            log.error("输入值非法{}", requestWrapper.getQueryString());
            returnJson(response, "输入值非法!", 500);
            return false;
        }
        System.err.println(JSON.toJSONString(request.getParameterMap()));
        /**
         * 获取所有跳转路径参数，保留传入下个界面
         */
        Map<String, String[]> map = requestWrapper.getParameterMap();
        boolean checkParameterMap = checkParameterMap(map, requestUrl);
        if (!checkParameterMap) {
            returnJson(response, "输入值非法", 500);
            return false;
        }
        /**
         * 检查数据流参数
         */
        String readerParam = requestWrapper.getReaderParam();
        // 判断是否是文件上传，是不对流参数进行验证
        String uplFile = "/upload", upImage = "/uploadFiles";
        if (!requestUrl.contains(uplFile) && !requestUrl.contains(upImage)) {
            boolean checkReader = checkReader(readerParam, requestUrl);
            if (!checkReader) {
                returnJson(response, "请求重复", 500);
                return false;
            }

        }

        return true;
    }


    private void returnJson(HttpServletResponse response, String msg, int code) {
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("applicatiopn/json;charset=utf-8");
        AjaxResult a = AjaxResult.error(code, msg);
        String res = JSON.toJSONString(a);
        try {
            writer = response.getWriter();
            writer.println(res);
            response.reset();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 检查所有页面输入的参数是否安全 request.getParameterMap()
     *
     * @param map
     * @param requestUrl
     */
    private boolean checkParameterMap(Map<String, String[]> map, String requestUrl) {
        rnd = null;
        if (map != null && map.size() > 0) {
            Iterator<Map.Entry<String, String[]>> iterator = map.entrySet().iterator();
            String value = "";
            while (iterator.hasNext()) {
                value = "";
                Map.Entry<String, String[]> entry = iterator.next();
                String pname = (String) entry.getKey();
                if (Objects.equals("token", pname)) {
                    continue;
                }
                Object objValue = entry.getValue();
                if (null == objValue) {
                    value = "";
                } else {
                    String[] values = (String[]) objValue;
                    for (String s : values) {
                        value = s + ",";
                    }
                    if (value.length() > 0) {
                        value = value.substring(0, value.length() - 1);
                    }
                }

                if ("rnd".equals(pname)) {
                    rnd = value;
                }
                if (SafeUtil.checkSafeSql(value)) {
                    log.info("请求失败,当前请求参数不安全!请求地址：\n" + requestUrl + "\n不安全参数：" + pname + ":" + value);
                    return false;
                }
                if (SafeUtil.checkSpecial(value)) {
                    log.info("请求失败,当前请求参数包含特殊字符!请求地址：\n" + requestUrl + "\n特殊字符参数：" + pname + ":" + value);
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 通过rnd随机参数判断相同url是否多次请求访问
     *
     * @param currentRequest
     * @param requestWrapper
     * @return
     */
    private boolean checkSameUrl(String currentRequest, XssRequestWrapper requestWrapper) {
        if (rnd != null && rnd.length() > 0) {
            try {
                double newRnd = Double.parseDouble(rnd);
                /**
                 * 当为空时，新增请求时间记录，第一次请求，放行，允许查询
                 */
                HttpSession session = requestWrapper.getSession();
                Object obj = session.getAttribute("requestLogMap");
                if (obj == null) {
                    requestLogMap = new HashMap<String, List<Double>>(100);
                } else {
                    requestLogMap = (Map<String, List<Double>>) obj;
                }

                List<Double> list = requestLogMap.get(currentRequest);
                if (list == null) {
                    list = new ArrayList<Double>();
                } else {
                    for (Double oldRnd : list) {
                        if (oldRnd == newRnd) {
                            log.info("请求失败,当前请求已过期!请重新登录！您的请求地址：\n" + currentRequest);
                            return false;
                        }
                    }
                }
                list.add(newRnd);
                requestLogMap.put(currentRequest, list);
                String brute = "requestLogMap";
                if (session.getAttribute(brute) != null) {
                    session.removeAttribute(brute);
                }
                session.setAttribute(brute, requestLogMap);
            } catch (NumberFormatException e) {
                log.info("rnd参数格式化错误 请检查");
                return false;
            }
        } else {
            log.info("请求失败,rnd参数不存在!请重新登录！您的请求地址：\n" + currentRequest);
            return true;
        }
        return true;
    }

    /**
     * 检查所有页面输入的参数是否安全 request.getReader()
     *
     * @param readerParam
     * @param requestUrl
     */
    private boolean checkReader(String readerParam, String requestUrl) {
        if (SafeUtil.checkScript(readerParam)) {
            log.info("请求失败,当前请求参数不安全!请求地址：\n" + requestUrl + "\n不安全参数：数据流:" + readerParam);
            return false;
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        SecurityContextHolder.remove();
    }

    private boolean isWhiteUrl(String requestUrl) {
        return Arrays.asList(WHITE_URL).contains(requestUrl);
    }
}
