package com.securitycontrol.common.security.interceptor;

import com.alibaba.fastjson2.JSON;
import com.github.pagehelper.util.StringUtil;
import com.securitycontrol.common.core.constant.SecurityConstants;
import com.securitycontrol.common.core.context.SecurityContextHolder;
import com.securitycontrol.common.core.utils.JwtUtils;
import com.securitycontrol.common.core.utils.ServletUtils;
import com.securitycontrol.common.core.utils.StringUtils;
import com.securitycontrol.common.core.utils.aes.DateTimeHelper;
import com.securitycontrol.common.core.utils.ip.IpUtils;
import com.securitycontrol.common.core.web.domain.AjaxResult;
import com.securitycontrol.common.security.enums.UrlEnums;
import com.securitycontrol.common.security.utils.SafeUtil;
import com.securitycontrol.common.security.utils.SecurityUtils;
import com.securitycontrol.common.security.utils.Sm3Utils;
import com.securitycontrol.common.security.utils.XssRequestWrapper;
import com.securitycontrol.system.api.RemoteLogService;
import com.securitycontrol.system.api.RemoteUserService;
import com.securitycontrol.system.api.domain.SysLog;
import com.securitycontrol.system.api.domain.decision.SysMenu;
import com.securitycontrol.system.api.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.AsyncHandlerInterceptor;

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.*;

/**
 * @author bonus
 * @data 2023/2/6 17:22
 * @description 安全参数验证
 */
@Slf4j
public class ParamSecureHandler implements AsyncHandlerInterceptor {
    @Autowired
    private final PathMatcher pathMatcher = new AntPathMatcher();


    private String rnd = null;


    private final String whiteURL = "http://127.0.0.1:18080/";

    @Autowired
    private RemoteLogService remoteLogService;

    @Autowired
    private RemoteUserService remoteUserService;

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

//      IResourceService resourceService = (IResourceService) AdapterFactory.getInstance(Constants.CLASS_RESOURCE);


    public static final String[] EXCLUDE_URLS = {"/sys/pro/addPro","/sys/pro/editPro","/sys/pro/importProData","/back/personnel/addPersonnel","/back/personnel/editPersonnel","/pageJump","/validateToken"};

    public boolean isFileUpload(HttpServletRequest request) {

        for (String excludeUrl : EXCLUDE_URLS) {
            // 忽略文件上传
            if (Objects.equals(excludeUrl, request.getRequestURI()) || request.getRequestURI().contains("file") || request.getRequestURI().contains("File")) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 过滤文件上传功能
        if(isFileUpload(request)){
            return true;
        }
        System.out.println("进入了拦截器");
        System.err.println(request.getRequestURI());
        XssRequestWrapper requestWrapper = new XssRequestWrapper(request);
        String requestUrl = requestWrapper.getRequestURI();

        /**
         * 防止refer篡改
         */
        /*String referUrl= request.getHeader("Referer");
        if(!Objects.equals(whiteURL,referUrl)){
            returnJson(response,"请求来源不正确！",500);
            return false;
        }*/
        /**
         * 白名单中不验证参数
         */
      /*  boolean doFilter = isWhiteURL(requestUrl);
        if (doFilter) {
            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 ckval = request.getHeader("ckval");
        String currentRequest = request.getRemoteAddr() + ":" + requestUrl + "?ckval=" + ckval;

        /**
         * 检查数据流参数
         */
        String readerParam = requestWrapper.getReaderParam();
        // 判断是否是文件上传，是不对流参数进行验证
        String uplFile = "uploadFile", upImage = "uploadImage";
        if (!requestUrl.contains(uplFile) && !requestUrl.contains(upImage)) {
            boolean checkReader = checkReader(readerParam, requestUrl);
            if (!checkReader) {
                returnJson(response, "请求重复", 500);
                return false;
            }

        }
        /*if (!sm3Check(request)) {
            returnJson(response,"请求参数丢失",500);
            return false;
        }*/
        if (!checkIsYq(request, requestWrapper)) {
            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);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 判断是否越权
     */
    private boolean checkIsYq(HttpServletRequest request, XssRequestWrapper requestWrapper) throws Exception {
        String requestUri = request.getRequestURI();
        String[] headUrls = requestUri.split("/");
        String url = "/" + headUrls[1] + "/" + headUrls[2];
        Boolean result = false;
//        String token = requestWrapper.getParameter("token");
        String token = SecurityUtils.getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            String userId = JwtUtils.getUserId(token);
            System.out.println("拦截器userId:" + userId);
            if (StringUtil.isNotEmpty(userId)) {
                LoginUser loginUser = SecurityUtils.getLoginUser();
                if (loginUser != null && loginUser.getSysUser() != null) {
                    if (CollectionUtils.isNotEmpty(loginUser.getSysUser().getMenus())) {
                        List<SysMenu> menuList = loginUser.getSysUser().getMenus();
                        List<SysMenu> sysMenus = treeToList(menuList);
                        for (SysMenu sysMenu : sysMenus) {
                            if (Objects.equals(sysMenu.getUrl(), url)) {
                                result = true;
                            }
                        }
                    }
                }
            }
//            result = resourceService.hasPermitURLObj(userId, "9b4483c383538275018615493e1451ea", url);
            System.out.println("==================越狱记录：========================userId:" + userId + "============是否越狱：" + result);
        }
        if (!result) {
            addExceedsAccessLog(url, token);
//            return false;
            //添加弹框
        }
        return true;
    }

    public List<SysMenu> treeToList(List<SysMenu> treeList) {
        List<SysMenu> resList = new ArrayList<>();
        Stack<List<SysMenu>> stack = new Stack<>();
        for (SysMenu sysMenu : treeList) {
            resList.add(beanCopy(sysMenu));
            if (sysMenu.getChilder() != null && !sysMenu.getChilder().isEmpty()) {
                stack.push(sysMenu.getChilder());
            }
        }
        while (!stack.isEmpty()) {
            List<SysMenu> subTree = stack.pop();
            for (SysMenu menu : subTree) {
                if (menu.getChilder() != null && !menu.getChilder().isEmpty()) {
                    stack.push(menu.getChilder());
                } else {
                    resList.add(beanCopy(menu));
                }
            }
        }
        System.out.println(JSON.toJSONString(resList));
        return resList;
    }

    private SysMenu beanCopy(SysMenu source) {
        SysMenu res = new SysMenu();
        res.setMenuId(source.getMenuId());
        res.setMenuName(source.getMenuName());
        res.setUrl(source.getUrl());
        return res;
    }

    private void addExceedsAccessLog(String url, String token) {
        SysLog sysLog = new SysLog();
        String id = UUID.randomUUID().toString().replaceAll("-", "");
        sysLog.setLogId(id);
        sysLog.setUserId(Long.valueOf(JwtUtils.getUserId(token)));
        sysLog.setOperaUserName(JwtUtils.getUserName(token));
        sysLog.setOperTime(DateTimeHelper.getNowTime());
        sysLog.setIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
        UrlEnums[] enums = UrlEnums.values();
        for (UrlEnums anEnum : enums) {
            if (url.startsWith(anEnum.getUrl())) {
                sysLog.setModel(anEnum.getInfo());
            }
        }
        sysLog.setLogType(2);
        sysLog.setOperUri(url);
        sysLog.setFailureReason("用户越权访问地址");
        sysLog.setGrade("高");
        sysLog.setErrType("越权访问");
        sysLog.setResult(1);
        remoteLogService.saveSysLog(sysLog, SecurityConstants.INNER);
    }

    /**
     * 参数校验
     *
     * @param request
     * @return
     */
    private boolean sm3Check(HttpServletRequest request) {
        Map<String, String> map = new LinkedHashMap<>();
        String tok = "token";
        request.getParameterMap().forEach((key, value) -> {
            if (!Objects.equals(key, tok)) {
                map.put(key, String.join(" ", value));
            }
        });

        String header = request.getHeader("encrypt");
        String json = JSON.toJSONString(map);
        request.getParameterMap();
        String str = Sm3Utils.encrypt(json);
        return str.equalsIgnoreCase(header);

    }

    /**
     * 检查所有页面输入的参数是否安全 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();
    }
}
