package com.changfa.utils;

import lombok.SneakyThrows;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.snmp4j.*;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.*;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.DefaultPDUFactory;
import org.snmp4j.util.TableEvent;
import org.snmp4j.util.TableUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Vector;

public class SnmpUtil {
    /**
     * get cpu描述信息
     */
    public static final String SNMPGET_CPUDESC = ".1.3.6.1.2.1.1.1.0.255.1";

    /**
     * walk 网络接口描述
     */
    public static final String SNMPWALK_IFDESCR = ".1.3.6.1.2.1.2.2.1.2";

    /**
     * walk 接口物理地址
     */
    public static final String SNMPWALK_IFPHYSADDRESS = ".1.3.6.1.2.1.2.2.1.6";

    /**
     * get IO负载
     */
    public static final String SNMPGET_IOLOAD = ".1.3.6.1.2.1.1.1.0.255.0";

    /**
     * 硬盘大小
     */
    public static final String SNMPGET_DSKTOTAL = ".1.3.6.1.4.1.2021.9.1.6";

    /**
     * walk cpu 负载
     */
    public static final String SNMPWALK_HRPROCESSLOAD = ".1.3.6.1.2.1.25.3.3.1.2";

    /**
     * walk cpu 空闲率
     */
    public static final String SNMPWALK_SSCPUIDLE = ".1.3.6.1.4.1.2021.11.11.0";

    /**
     * walk 存储设备描述
     */
    public static final String SNMPWALK_HRSTORAGEDESCR = ".1.3.6.1.2.1.25.2.3.1.3";
    /**
     * walk 存储设备描述
     */
    public static final String SNMPWALK_AHU = ".1.3.6.1.2.1.25.2.3.1.4";
    /**
     * walk 存储设备描述
     */
    public static final String SNMPWALK_AUR = ".1.3.6.1.2.1.25.2.3.1.5";

    /**
     * walk 存储设备使用大小
     * 内存使用多少，跟总容量相除就是占用率
     */
    public static final String SNMPWALK_HRSTORAGEUSED = ".1.3.6.1.2.1.25.2.3.1.6";
    /**
     * walk 内存占用
     * 各个进程占用的内存
     */
    public static final String SNMPWALK_MEMORY_WIN = ".1.3.6.1.2.1.25.5.1.1.2";

    /**
     * walk 存储设备使用率
     */
    public static final String SNMPWALK_DSKPERCENT = ".1.3.6.1.4.1.2021.9.1.9";

    /**
     * get 获取内存大小
     */
    public static final String SNMPGET_HRMEMORYSIZE = ".1.3.6.1.2.1.25.2.2.0";

    /**
     * 获取机器名
     */
    public static final String SNMPWALK_SYSNAME = ".1.3.6.1.2.1.1.5";

    /**
     * 性能参数ID-cpu使用率
     */
    public static final Integer PERFORMANCE_PARAM_CPUUSAGE = 1;

    /**
     * 性能参数ID-内存使用率
     */
    public static final Integer PERFORMANCE_PARAM_MEMORYUSAGE = 2;

    /**
     * 性能参数ID-io带宽占用率
     */
    public static final Integer PERFORMANCE_PARAM_IOUSAGE = 3;

    /**
     * decimal 类型1024,用于除法计算
     */
    public static final BigDecimal DEVIDE_NUM = new BigDecimal("1024");

    /**
     * get方式获取内存大小
     */
    public static final String GET_MEMORY = "1.3.6.1.2.1.25.2.2.0";

    /**
     * 获取系统描述
     */
    public static final String SYS_DSC = "1.3.6.1.2.1.1.1.0";

    /**
     * 获取网络接口数量
     */
    public static final String IF_NUM = "1.3.6.1.2.1.2.1.0";

    /**
     * 获取cpu核数
     */
    public static final String CPU_NUM = "1.3.6.1.2.1.25.3.3.1.2";

    private static Logger logger = LogManager.getLogger(SnmpUtil.class);
    private static Snmp snmp = null;
    private static UserTarget target = new UserTarget();;

    @SuppressWarnings("squid:S3010")
    public SnmpUtil(String intranetDeviceIp, Integer snmpPort) throws IOException {
        if (snmp == null) {
            snmp = new Snmp(new DefaultUdpTransportMapping());
            USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
            SecurityModels.getInstance().addSecurityModel(usm);
            snmp.listen();
        }
        // Add User
        UsmUser user = new UsmUser(
                new OctetString("user001"),
                AuthSHA.ID, new OctetString("Authe@1234"),
                PrivDES.ID, new OctetString("Priva@1234"));
        //If the specified SNMP engine id is specified, this user can only be used with the specified engine ID
        //So if it's not correct, will get an error that can't find a user from the user table.
        //snmp.getUSM().addUser(new OctetString("nmsAdmin"), new OctetString("0002651100"), user);
        snmp.getUSM().addUser(new OctetString("user001"), user);
        //初始化CommunityTarget
        target.setVersion(SnmpConstants.version3);
        target.setAddress(new UdpAddress(intranetDeviceIp+"/"+snmpPort));
        target.setSecurityLevel(SecurityLevel.AUTH_PRIV);
        target.setSecurityName(new OctetString("user001"));
        target.setTimeout(5000);    //3s
        target.setRetries(0);
    }

    private ResponseEvent snmpGet(String oid) {
        ScopedPDU pdu = new ScopedPDU();
        pdu.addOID(new VariableBinding(new OID(oid)));
        ResponseEvent re = null;
        try {
            re = snmp.get(pdu, target);
        } catch (Exception e) {
            logger.info("snmpGet 异常" + e.getMessage());
        }
        return re;
    }

    private List<TableEvent> snmpWalk(String oid) {
        TableUtils utils = new TableUtils(snmp, new DefaultPDUFactory(PDU.GETBULK));
        OID[] columnOid = new OID[]{new OID(oid)};

        return utils.getTable(target, columnOid, null, null);
    }

    /**
     * 获取cpu负载
     *
     * @return
     */
    public BigDecimal getCpuUsage() {
        List<TableEvent> list = snmpWalk(SNMPWALK_HRPROCESSLOAD);
        BigDecimal usage = new BigDecimal("0");
        for (TableEvent tableEvent : list) {
            try {
                String s = tableEvent.toString().split("=")[3].split("]")[0].trim();
                if (!"-4,exception".equals(s)) {
                    usage = usage.add(new BigDecimal(tableEvent.toString().split("=")[3].split("]")[0].trim()));
                } else {
                    return new BigDecimal("-1");
                }
            } catch (Exception e) {
                logger.info("获取cpu负载失败" + e.getMessage());
                return new BigDecimal("-1");
            }
        }
        usage = usage.divide(new BigDecimal(list.size()), 2, BigDecimal.ROUND_HALF_UP);
        return usage;
    }

    /**
     * 获取cpu使用率
     *
     * @return
     */
    public BigDecimal getCpuUseRatio() {
        BigDecimal totalSize;
        ResponseEvent responseEvent = snmpGet(SNMPWALK_SSCPUIDLE);
        if (responseEvent != null && responseEvent.getResponse() != null) {
            totalSize = new BigDecimal(responseEvent.getResponse().toString().split("=")[4].split("]")[0].trim());
        } else {
            return new BigDecimal("-1");
        }
        return new BigDecimal("100").subtract(totalSize);
    }

    /**
     * 获取磁盘占用率
     *
     * @return
     */
    public BigDecimal getDiskUsageForWindows() {
        BigDecimal multiply = new BigDecimal("-1");
        try {
            BigDecimal diskSize = getDiskSize();
            BigDecimal diskUsed = getDiskUsed();
            if (BigDecimal.ZERO.compareTo(diskSize) < 0 && BigDecimal.ZERO.compareTo(diskUsed) < 0) {
                multiply = getDiskUsed().divide(getDiskSize(), 3, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100));
            }
        } catch (Exception e) {
            logger.info("获取windows磁盘占用异常" + e.getMessage());
        }

        return multiply;
    }

    /**
     * 获取磁盘占用率
     *
     * @return
     */
    public BigDecimal getDiskUsage() {
        BigDecimal totalSize = null;
        //org.snmp4j.util.TableEvent[index=null,vbs=null,status=-1,exception=null,report=null]
        List<TableEvent> tableEvents = snmpWalk(SNMPWALK_DSKPERCENT);
        if (tableEvents.isEmpty()) {
            totalSize = getDiskUsageForWindows();
            return totalSize;
        }
        for (int i = 0; i < tableEvents.size(); i++) {
            if ("-1,exception".equals(tableEvents.get(i).toString().split("=")[3].split("]")[0].trim())) {
                return new BigDecimal("-1");
            }
            String s = tableEvents.get(i).toString().split("=")[3].split("]")[0].trim();
            if (!"-4,exception".equals(s)) {
                totalSize = new BigDecimal(tableEvents.get(i).toString().split("=")[3].split("]")[0].trim());
            } else {
                return new BigDecimal("-1");
            }
        }

        return totalSize;
    }

    /**
     * 获取内存使用率
     *
     * @return
     */
    public BigDecimal getMemoryUsage() {
        BigDecimal usage;
        BigDecimal totalSize;
        try {
            ResponseEvent event = snmpGet(SNMPGET_HRMEMORYSIZE);
            if (event != null && event.getResponse() != null) {
                totalSize = new BigDecimal(event.getResponse().toString().split("=")[4].split("]")[0].trim());
                usage = getMemoryUsed();
                return usage.multiply(new BigDecimal("100")).divide(totalSize, 0, BigDecimal.ROUND_HALF_UP);
            } else {
                return new BigDecimal("-1");
            }
        } catch (Exception e) {
            logger.info("获取内存使用率失败" + e.getMessage());
            return new BigDecimal("-1");
        }
    }


    /**
     * 获取内存已使用
     *
     * @return
     */
    public BigDecimal getMemoryUsed() {
        List<TableEvent> list = snmpWalk(SNMPWALK_HRSTORAGEDESCR);
        int index = 0;
        BigDecimal usedMemory = new BigDecimal("0");
        for (int i = 0; i < list.size(); i++) {
            String trim = list.get(i).toString().split("=")[3].split("]")[0].trim();
            if (trim.contains("Physical memory")) {
                //linux系统
                index = i;
                List<TableEvent> usedList = snmpWalk(SNMPWALK_HRSTORAGEUSED);
                usedMemory = new BigDecimal(usedList.get(index).toString().split("=")[3].split("]")[0].trim());
            } else if (trim.contains("Physical Memory")) {
                //windows系统
                List<TableEvent> list1 = snmpWalk(SNMPWALK_MEMORY_WIN);
                BigDecimal re = new BigDecimal("0");
                for (int j = 0; j < list1.size(); j++) {
                    String trim1 = list1.get(j).toString().split("=")[3].split("]")[0].trim();
                    BigDecimal b = new BigDecimal(trim1);
                    re = re.add(b);
                }
                return re;
            } else if (trim.contains("Memory buffers")) {
                //linux系统
                index = i;
                List<TableEvent> usedList = snmpWalk(SNMPWALK_HRSTORAGEUSED);
                usedMemory = usedMemory.subtract(new BigDecimal(usedList.get(index).toString().split("=")[3].split("]")[0].trim()));
            } else if (trim.contains("Cached memory")) {
                //linux系统
                index = i;
                List<TableEvent> usedList = snmpWalk(SNMPWALK_HRSTORAGEUSED);
                usedMemory = usedMemory.subtract(new BigDecimal(usedList.get(index).toString().split("=")[3].split("]")[0].trim()));
            }
        }
        return usedMemory;

    }

    /**
     * 获取磁盘
     *
     * @return
     */
    public BigDecimal getDiskSize() {
        List<TableEvent> list = snmpWalk(SNMPWALK_HRSTORAGEDESCR);
        BigDecimal diskSize = new BigDecimal(-1);
        if (!list.isEmpty()) {
            diskSize = new BigDecimal("0");
            List<TableEvent> usedList1 = snmpWalk(SNMPWALK_AHU);
            List<TableEvent> usedList2 = snmpWalk(SNMPWALK_AUR);
            for (int i = 0; i < list.size(); i++) {
                boolean re = list.get(i).toString().split("=")[3].split("]")[0].trim().contains(":") || list.get(i).toString().split("=")[3].split("]")[0].trim().contains("/");
                if (re) {
                    diskSize = diskSize.add(new BigDecimal(usedList1.get(i).toString().split("=")[3].split("]")[0].trim()).multiply(new BigDecimal(usedList2.get(i).toString().split("=")[3].split("]")[0].trim())).divide(new BigDecimal("1073741824"), 2, BigDecimal.ROUND_HALF_UP));
                }
            }
        }
        return diskSize;
    }

    /**
     * 获取磁盘已使用
     *
     * @return
     */
    public BigDecimal getDiskUsed() {
        List<TableEvent> list = snmpWalk(SNMPWALK_HRSTORAGEDESCR);
        BigDecimal diskSize = new BigDecimal(-1);
        if (!list.isEmpty()) {
            diskSize = new BigDecimal("0");
            List<TableEvent> usedList1 = snmpWalk(SNMPWALK_AHU);
            List<TableEvent> usedList2 = snmpWalk(SNMPWALK_HRSTORAGEUSED);
            for (int i = 0; i < list.size(); i++) {
                boolean re = list.get(i).toString().split("=")[3].split("]")[0].trim().contains(":") || list.get(i).toString().split("=")[3].split("]")[0].trim().contains("/");
                if (re) {
                    diskSize = diskSize.add(new BigDecimal(usedList1.get(i).toString().split("=")[3].split("]")[0].trim()).multiply(new BigDecimal(usedList2.get(i).toString().split("=")[3].split("]")[0].trim())).divide(new BigDecimal("1073741824"), 2, BigDecimal.ROUND_HALF_UP));
                }
            }
        }
        return diskSize;
    }

    /**
     * 获取IO负载
     *
     * @return
     */
    public Float getIOUsage() {
        ResponseEvent event = snmpGet(SNMPGET_IOLOAD);
        if (event != null && event.getResponse() != null) {
            float usage = Float.parseFloat(event.getResponse().toString().split("=")[4].split("]")[0].trim());
            if (usage > 1) {
                return usage;
            } else {
                return usage * 100;
            }
        }
        return null;
    }

    /**
     * 获取性某性能参数数据
     *
     * @param parameterId
     * @return
     */
    public Object getData(Integer parameterId) {
        Object data = null;
        try {
            if (parameterId.equals(PERFORMANCE_PARAM_CPUUSAGE)) {
                data = getCpuUsage();
            } else if (parameterId.equals(PERFORMANCE_PARAM_MEMORYUSAGE)) {
                data = getMemoryUsage();
            } else if (parameterId.equals(PERFORMANCE_PARAM_IOUSAGE)) {
                data = getIOUsage();
            }
        } catch (Exception e) {
            logger.info("获取性能参数异常" + e.getMessage());
        }
        return data;
    }

    /**
     * 获取设备物理地址
     *
     * @return
     */
    public String getMacAddress() {
        try {
            List<TableEvent> list = snmpWalk(SNMPWALK_IFDESCR);
            int index = 0;
            for (int i = 0; i < list.size(); i++) {
                if (list.get(i).toString().split("=")[3].split("]")[0].trim().contains("eth")) {
                    index = i;
                }
            }
            List<TableEvent> ifAddressList = snmpWalk(SNMPWALK_IFPHYSADDRESS);
            return ifAddressList.get(index).toString().split("=")[3].split("]")[0].trim();
        } catch (Exception e) {
            logger.info("获取物理地址失败" + e.getMessage());
            return "failure";
        }
    }

    /**
     * 获取设备内存大小  单位为GB
     *
     * @return
     */
    public String getMemoryDesc() {
        ResponseEvent event = snmpGet(SNMPGET_HRMEMORYSIZE);
        Long bytes = Long.parseLong(event == null ? "0" : event.getResponse().toString().split("=")[4].split("]")[0].trim());
        float gb = bytes / 1024.0f / 1024.0f;
        DecimalFormat decimalFormat = new DecimalFormat(".00");
        return decimalFormat.format(gb) + "GB";
    }

    /**
     * @return
     * @throws IOException 获取cpu描述信息
     */
    public String getCpuDesc() throws IOException {
        ResponseEvent event = snmpGet(SNMPGET_CPUDESC);
        return event == null ? "" : event.getResponse().toString().split("=")[4].split("]")[0].trim().split(":")[1].trim();
    }

    /**
     * 获取存储设备大小 (空盘空间不足百分百)
     *
     * @param deviceCode
     * @return
     */
    public BigDecimal getDiskSize(String deviceCode) {

        List<TableEvent> list = snmpWalk(SNMPGET_DSKTOTAL);
        BigDecimal diskSize = new BigDecimal(-1);
        if (list != null) {
            try {
                for (TableEvent tableEvent : list) {
                    diskSize = new BigDecimal(tableEvent.toString().split("=")[3].split("]")[0].trim()).divide(DEVIDE_NUM).divide(DEVIDE_NUM, 2, RoundingMode.HALF_UP);
                }
            } catch (Exception e) {
                logger.info("获取磁盘大小失败" + e.getMessage());
            }
        }
        if (BigDecimal.ZERO.compareTo(diskSize) > 0) {
            diskSize = getDiskSize();
        }
        return diskSize;
    }


    /**
     * 获取磁盘描述
     *
     * @param deviceCode
     * @return
     * @throws IOException
     */
    public BigDecimal getDiskDesc(String deviceCode) {
        return getDiskSize(deviceCode).divide(DEVIDE_NUM, 2, RoundingMode.HALF_UP).divide(DEVIDE_NUM, 2, RoundingMode.HALF_UP);
    }

    /**
     * snmp协议检测
     */
    public boolean snmpCheck() {
        ResponseEvent re = snmpGet(".1.3.6.1.4.1.2021.255.1");
        return re == null || re.getResponse() != null;
    }

    /**
     * 获取机器名
     *
     * @return
     */
    public String getSysName() {
        String name = null;
        try {
            List<TableEvent> list = snmpWalk(SNMPWALK_SYSNAME);
            if (list != null) {
                for (TableEvent tableEvent : list) {
                    name = (tableEvent.toString().split("=")[3].split("]")[0].trim());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("获取机器名失败" + e.getMessage());
        }
        return name;
    }

    /**
     * 获取内存总大小(实际值)
     *
     * @return
     */
    public BigDecimal getMemoryTotalSize() {
        try {
            ResponseEvent event = snmpGet(GET_MEMORY);
            if (event != null && event.getResponse() != null) {
                String trim = event.getResponse().toString().split("=")[4].split("]")[0].trim();
                return new BigDecimal(trim).divide(DEVIDE_NUM, 2, RoundingMode.HALF_UP).divide(DEVIDE_NUM, 2, RoundingMode.HALF_UP);
            } else {
                throw new RuntimeException("获取内存信息失败");
            }
        } catch (Exception e) {
            logger.info("获取内存总大小失败" + e.getMessage());
        }
        return new BigDecimal("-1");
    }

    /**
     * 获取系统描述
     *
     * @return
     */
    public String getSysDsc() {
        try {
            ResponseEvent event = snmpGet(SYS_DSC);
            if (event != null && event.getResponse() != null) {
                return event.getResponse().toString().split("=")[4].split("]")[0].trim();
            } else {
                throw new RuntimeException("获取系统描述信息失败");
            }
        } catch (Exception e) {
            logger.info("获取系统描述信息失败" + e.getMessage());
        }
        return "failure";
    }

    /**
     * 获取接口数
     *
     * @return
     */
    public Integer getIfNumber() {
        try {
            ResponseEvent event = snmpGet(IF_NUM);
            if (event != null && event.getResponse() != null) {
                String trim = event.getResponse().toString().split("=")[4].split("]")[0].trim();
                return Integer.parseInt(trim);
            } else {
                throw new RuntimeException("获取接口信息失败");
            }
        } catch (Exception e) {
            logger.info("获取接口数量失败" + e.getMessage());
        }
        return -1;
    }

    /**
     * 获取cpu核数(n个cpu有n条数据)
     *
     * @return
     */
    public Integer getCpuNum() {
        List<TableEvent> list = snmpWalk(CPU_NUM);
        Integer num = -1;
        if (list != null) {
            num = list.size();
        }
        return num;
    }


    @SneakyThrows
    public static void getAll(Integer port){
        String snmpIp = "127.0.0.1";
        Integer snmpPort = port;
        SnmpUtil snmpUtil = new SnmpUtil(snmpIp, snmpPort);

        logger.info("机器名称" + snmpUtil.getSysName());
        logger.info("磁盘大小 " + snmpUtil.getDiskSize("devide"));
        logger.info("物理地址" + snmpUtil.getMacAddress());
        logger.info("cpu " + snmpUtil.getCpuUsage());
        logger.info("内存 " + snmpUtil.getMemoryUsage());
        logger.info("磁盘 " + snmpUtil.getDiskUsage());
        logger.info("系统基本描述信息" + snmpUtil.getSysDsc());
        logger.info("网络接口数量 " + snmpUtil.getIfNumber());
        logger.info("cpu核数 " + snmpUtil.getCpuNum());
        logger.info("cpu使用率 " + snmpUtil.getData(PERFORMANCE_PARAM_CPUUSAGE));
        logger.info("内存使用率 " + snmpUtil.getData(PERFORMANCE_PARAM_MEMORYUSAGE));
        logger.info("io带宽占用率 " + snmpUtil.getData(PERFORMANCE_PARAM_IOUSAGE));
    }

    private static PDU createGetPdu(String oid) {
        OctetString contextEngineId = new OctetString("0002651100[02]");
        ScopedPDU pdu = new ScopedPDU();
        pdu.setType(PDU.GET);
        pdu.setContextEngineID(contextEngineId);    //if not set, will be SNMP engine id
        //pdu.setContextName(contextName);  //must be same as SNMP agent

        pdu.add(new VariableBinding(new OID(oid))); //sysUpTime
        return pdu;
    }

    private static int sendRequest(Snmp snmp, PDU pdu, UserTarget target)
            throws IOException {
        ResponseEvent responseEvent = snmp.send(pdu, target);
        PDU response = responseEvent.getResponse();

        if (response == null) {
            System.out.println("TimeOut...");
        } else {
            if (response.getErrorStatus() == PDU.noError) {
                if (response != null ) {
                    return responseEvent.getResponse().get(0).getVariable().toInt();
                }
                System.out.println(response.toString());
                Vector<? extends VariableBinding> vbs = response.getVariableBindings();
                for (VariableBinding vb : vbs) {
                    System.out.println(vb + " ," + vb.getVariable().getSyntaxString());
                }
            } else {
                System.out.println("Error:" + response.getErrorStatusText());
            }
        }
        return 0;
    }

    public static void main(String[] args) throws IOException {
        String snmpIp = "26.74.251.47";
        Integer snmpPort = 161;
        SnmpUtil snmpUtil = new SnmpUtil(snmpIp, snmpPort);
        sendRequest(snmp, createGetPdu(SNMPWALK_SYSNAME), target);
        logger.info("机器名称" + snmpUtil.getSysName());
        logger.info("磁盘大小 " + snmpUtil.getDiskSize("devide"));
        logger.info("物理地址" + snmpUtil.getMacAddress());
        logger.info("cpu " + snmpUtil.getCpuUsage());
        logger.info("内存 " + snmpUtil.getMemoryUsage());
        logger.info("磁盘 " + snmpUtil.getDiskUsage());
        logger.info("系统基本描述信息" + snmpUtil.getSysDsc());
        logger.info("网络接口数量 " + snmpUtil.getIfNumber());
        logger.info("cpu核数 " + snmpUtil.getCpuNum());
        logger.info("cpu使用率 " + snmpUtil.getData(PERFORMANCE_PARAM_CPUUSAGE));
        logger.info("内存使用率 " + snmpUtil.getData(PERFORMANCE_PARAM_MEMORYUSAGE));
        logger.info("io带宽占用率 " + snmpUtil.getData(PERFORMANCE_PARAM_IOUSAGE));

    }
}
