package com.mortals.xhx.daemon.applicationservice;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.net.Ipv4Util;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.setting.dialect.Props;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONValidator;
import com.alibaba.fastjson.TypeReference;
import com.mortals.framework.common.Rest;
import com.mortals.xhx.common.code.YesNoEnum;
import com.mortals.xhx.daemon.applicationservice.model.HcpReq;
import com.mortals.xhx.daemon.applicationservice.model.Request;
import dorkbox.notify.Notify;
import dorkbox.notify.Pos;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.pcap4j.core.BpfProgram;
import org.pcap4j.core.PcapHandle;
import org.pcap4j.core.PcapNetworkInterface;
import org.pcap4j.packet.IpV4Packet;
import org.pcap4j.packet.Packet;
import org.pcap4j.packet.TcpPacket;

import java.io.EOFException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;

/**
 * 持续获取本地网卡数据
 *
 * @author:
 * @date: 2023/1/7 11:25
 */
@AllArgsConstructor
@Slf4j
public class CaptureThread implements Runnable {

    public static final String AFFAIR_CODE = "affairCode";

    private PcapNetworkInterface device;
    private String redirectUrl;
    private String hcpGovUrl;
    private Map<String, Set<String>> filterMap;

    private String confPath;

    @Override
    public void run() {
        int snapshotLength = 65536; // in bytes
        int readTimeout = 50; // in milliseconds
        PcapHandle handle = null;
        try {
            log.info("capture device:{} desc:{} address:{}", device.getName(), device.getDescription(), device.getAddresses());
            handle = device.openLive(snapshotLength, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, readTimeout);
            String filter = "tcp";
            handle.setFilter(filter, BpfProgram.BpfCompileMode.OPTIMIZE);
            while (true) {
                try {
                    //过滤目标地址，端口
                    Packet packet = handle.getNextPacketEx();
                    //log.info("packet:{}",packet.length());
                    if (checkIpv4Packet(filterMap, packet)) continue;
                    TcpPacket tcp = packet.get(TcpPacket.class);
                    if (checkTcpPacket(filterMap, tcp)) continue;
                    if (checkHttpPacket(tcp)) continue;
                    //判断head信息
                    try {
                        String payLoad = new String(tcp.getPayload().getRawData(), "utf-8");
                        //解析httpHead协议
                        //解析http协议，获取下个包内容大小。通过内容大小判断区域内容
                        Request request = new Request();
                        // log.info("payLoad:{}",payLoad);
                        //分割协议头
                        List<String> vals = StrUtil.split(payLoad, "\r\n\r\n");
                        if (vals.size() < 2) {
                            continue;
                        }
                        List<String> split = StrUtil.split(vals.get(0), "\r\n", true, true);
                        for (String value : split) {
                            Request.parser(value, request);
                        }
                        //校验uripath
                        log.info("uri:"+request.getRequestURI());

                        if (checkRequestUri(filterMap, request)) continue;

                        if (request.getContentType() == null) {
                            //设置默认类型
                            request.setContentType("application/json");
                        }
                        if (vals.get(1).length() == 0) {
                            //无内容区
                            continue;
                        }
                        //判断是application/x-www-form-urlencoded;charset=UTF-8 还是json
                        if (request.getContentType() != null && request.getContentType().indexOf("x-www-form-urlencoded") != -1) {
                            if (vals.get(1).length() == request.getContentLength()) {
                                String[] pairs = vals.get(1).split("\\&");
                                Map<String, String> formData = new HashMap<>();
                                for (int i = 0; i < pairs.length; i++) {
                                    String[] fields = pairs[i].split("=");
                                    if (fields.length > 1) {
                                        String name = URLDecoder.decode(fields[0], "UTF-8");
                                        String value = URLDecoder.decode(fields[1], "UTF-8");
                                        formData.put(name, value);
                                    }
                                }
                                //如果有则发送请求
                                if (formData.containsKey(AFFAIR_CODE)) {
                                    //抓取到affairCode后发送到远端，并且成功后提示信息
                                    String affairCode = formData.get(AFFAIR_CODE);
                                    log.info("网卡：" + device.getName() + "affairCode:" + affairCode);
                                    sendHttpMessage(redirectUrl, affairCode);
                                }
                            } else {
                                //内容分包，获取下一个包再resamble
                            }
                            //表单形式
                        } else if (request.getContentType() != null && request.getContentType().indexOf("json") != -1) {
                            if (vals.get(1).length() == request.getContentLength()) {
                                Map<String, String> formData = new JSONObject(vals.get(1)).toBean(Map.class);
                                //如果有则发送请求
                                if (formData.containsKey(AFFAIR_CODE)) {
                                    //抓取到affairCode后发送到远端，并且成功后提示信息
                                    String affairCode = formData.get(AFFAIR_CODE);
                                    log.info("网卡：" + device.getName() + "ip:" + device.getAddresses() + "affairCode:" + affairCode);
                                    sendHttpMessage(redirectUrl, affairCode);
                                }

                            } else {
                                //内容分包，获取下一个包再resamble
                                byte[] source = vals.get(1).getBytes();
                                //读取下个tcp包，如果长度大小相同 则是body 默认最多10个包
                                int num = 0;
                                while (num < 10) {
                                    Packet nextPacket = handle.getNextPacket();
                                    TcpPacket nextTcpPacket = nextPacket.get(TcpPacket.class);
                                    if (checkTcpPacket(filterMap, nextTcpPacket)) continue;
                                    byte[] target = nextTcpPacket.getPayload().getRawData();
                                    source = doReassemble(source, target);
                                    if (source.length == request.getContentLength()) {
                                        //处理解析数据
                                        Map<String, String> formData = new JSONObject(new String(source, "utf-8")).toBean(Map.class);
                                        //如果有则发送请求
                                        if (formData.containsKey(AFFAIR_CODE)) {
                                            //抓取到affairCode后发送到远端，并且成功后提示信息
                                            String affairCode = formData.get(AFFAIR_CODE);
                                            log.info("网卡：" + device.getName() + "affairCode:" + affairCode);
                                            sendHttpMessage(redirectUrl, affairCode);
                                        }
                                        break;
                                    } else if (source.length < request.getContentLength()) {
                                        num++;
                                        continue;
                                    } else {
                                        //拼接包异常不做处理
                                        break;
                                    }
                                }
                            }
                        } else {
                            log.info("contenType error!");
                        }
                    } catch (UnsupportedEncodingException e) {
                        log.error("异常", e);
                    }
                } catch (TimeoutException e) {
                    //log.info("超时异常：device {} ip:{} isLocal:{}",device.getName(),device.getAddresses(),device.isLocal());
                    //log.error("超时异常", e.getMessage());
                } catch (EOFException e) {
                    log.error("异常EOFException", e.getMessage());
                }
            }
        } catch (Exception e) {
            log.error("异常", e);

        }
    }


    private boolean checkRequestUri(Map<String, Set<String>> filterMap, Request request) {
        return !filterMap.get("SendServerPath").contains(request.getRequestURI());
    }

    private boolean checkHttpPacket(TcpPacket tcp) throws UnsupportedEncodingException {
        String payLoad = new String(tcp.getPayload().getRawData(), "utf-8");
       // log.info("playLoad:"+payLoad);
      //  if (payLoad.indexOf("HTTP") == -1) return true;
        //System.out.println("payLoad:"+payLoad);
        return false;
    }

    private boolean checkTcpPacket(Map<String, Set<String>> filterMap, TcpPacket tcp) {
        String dstPort = tcp.getHeader().getDstPort().valueAsString();

        //log.info("destPort before:"+dstPort);
        if (!filterMap.get("SendServerPort").contains(dstPort)) {
            return true;
        }

        if (tcp.getPayload() == null) {
            return true;
        }
        //log.info("destPort:" + dstPort);
        return false;
    }


    private boolean checkIpv4Packet(Map<String, Set<String>> filterMap, Packet packet) {
        IpV4Packet ipV4Packet = packet.get(IpV4Packet.class);
        if (ipV4Packet == null) {
            return true;
        }
        String dest = ipV4Packet.getHeader().getDstAddr().getHostAddress();
      //  log.info("dest:"+dest);
        if (!filterMap.get("SendServerIp").contains(dest)) {
            return true;
        }
        //System.out.println("dest:"+dest);
        return false;
    }

    private void sendHttpMessage(String url, String affairCode) {
        if (url == null) {
            log.info("发送地址为空！");
            return;
        }

        //读取窗口id
        Integer windowId = 0;
        boolean exist = FileUtil.exist(confPath);
        if (!exist) {
            log.info("配置文件不存在,返回!");
            return;
        }

        String json = FileUtil.readString(confPath, Charset.forName("UTF-8"));

        boolean jsonBoolean = JSONValidator.from(json).validate();
        if (!jsonBoolean) {
            log.info("配置文件格式错误,返回!:{}", json);
            return;
        }
        HcpReq hcpReq = JSON.parseObject(json, HcpReq.class);

        String hcpUrl = hcpGovUrl + "?affairCode=" + affairCode;
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("url", hcpUrl);
        paramMap.put("windowid", hcpReq.getWindowId());

        log.info("req url:{},body:{}", url, JSON.toJSONString(paramMap));
        String response = HttpUtil.post(url, paramMap);
        log.info("response :" + response);
        Rest<Void> rest = JSON.parseObject(response, new TypeReference<Rest<Void>>() {
        });

        if (rest.getCode() == YesNoEnum.YES.getValue()) {
            log.info("创建提示框!");
            //创建提示框
            Notify.create()
                    .title("好差评")
                    .text("好差评成功！")
                    .hideAfter(10000)
                    .position(Pos.BOTTOM_LEFT)
                    .darkStyle()
                    .showInformation();
        }
    }

    //合并多个数据包
    private byte[] doReassemble(byte[] source, byte[] target) {
        //合并所需数组大小=原始包+新包
        byte[] buffer = new byte[source.length + target.length];
        System.arraycopy(source, 0, buffer, 0, source.length);
        System.arraycopy(target, 0, buffer, source.length, target.length);
        return buffer;
    }


    public static void main(String[] args) {
        String json = "{  }  12";
        boolean jsonBoolean = JSONValidator.from(json).validate();

        System.out.println(jsonBoolean);
    }

}
