/*
 * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
 * <p>
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl.html
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package top.continew.starter.license.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.CollUtils;
import top.continew.starter.license.exception.LicenseException;
import top.continew.starter.license.model.LicenseExtraModel;

import java.io.*;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Set;

/**
 * 服务器信息工具类
 *
 * @author Rong.Jia
 * @since 2.12.0
 */
public class ServerInfoUtils {

    private static final Logger log = LoggerFactory.getLogger(ServerInfoUtils.class);

    private static class ServerInfosContainer {
        private static Set<String> ipAddress = null;
        private static Set<String> macAddress = null;
        private static String cpuSerial = null;
        private static String mainBoardSerial = null;
    }

    private ServerInfoUtils() {
    }

    /**
     * 组装需要额外校验的License参数
     *
     * @return {@link LicenseExtraModel }
     */
    public static LicenseExtraModel getServerInfos() {
        LicenseExtraModel result = new LicenseExtraModel();
        try {
            initServerInfos();
            result.setIpAddress(ServerInfosContainer.ipAddress);
            result.setMacAddress(ServerInfosContainer.macAddress);
            result.setCpuSerial(ServerInfosContainer.cpuSerial);
            result.setMainBoardSerial(ServerInfosContainer.mainBoardSerial);
        } catch (Exception e) {
            log.error("获取服务器硬件信息异常", e);
            throw new LicenseException(String.format("获取服务器硬件信息异常, %s", e.getMessage()));
        }
        return result;
    }

    /**
     * 初始化服务器硬件信息，并将信息缓存到内存
     *
     * @throws Exception 例外
     */
    private static void initServerInfos() throws Exception {
        if (ServerInfosContainer.ipAddress == null) {
            ServerInfosContainer.ipAddress = getIpAddress();
        }
        if (ServerInfosContainer.macAddress == null) {
            ServerInfosContainer.macAddress = getMacAddress();
        }
        if (ServerInfosContainer.cpuSerial == null) {
            ServerInfosContainer.cpuSerial = getCpuSerial();
        }
        if (ServerInfosContainer.mainBoardSerial == null) {
            ServerInfosContainer.mainBoardSerial = getMainBoardSerial();
        }
    }

    /**
     * 获取服务器临时磁盘位置
     *
     * @return {@link String}
     */
    public static String getServerTempPath() {
        return System.getProperty("user.dir");
    }

    /**
     * 获取CPU序列号
     *
     * @return String CPU 序列号
     */
    public static String getCpuSerial() {
        return FileUtil.isWindows() ? getWindowCpuSerial() : getLinuxCpuSerial();
    }

    /**
     * 获取主板序列号
     *
     * @return String 主板序列号
     */
    public static String getMainBoardSerial() {
        return FileUtil.isWindows() ? getWindowMainBoardSerial() : getLinuxMainBoardSerial();
    }

    /**
     * 获取linux cpu 序列号
     *
     * @return {@link String}
     */
    private static String getLinuxCpuSerial() {
        String result = StringConstants.EMPTY;
        String cpuIdCmd = "dmidecode";
        BufferedReader bufferedReader = null;
        try {
            // 管道
            Process p = Runtime.getRuntime().exec(new String[] {"sh", "-c", cpuIdCmd});
            bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = null;
            int index = -1;
            while ((line = bufferedReader.readLine()) != null) {
                // 寻找标示字符串[hwaddr]
                index = line.toLowerCase().indexOf("uuid");
                if (index >= 0) {
                    // 取出mac地址并去除2边空格
                    result = line.substring(index + "uuid".length() + 1).trim();
                    break;
                }
            }
        } catch (IOException e) {
            log.error("获取Linux cpu信息错误 {}", e.getMessage());
        } finally {
            IoUtil.close(bufferedReader);
        }
        return result.trim();
    }

    /**
     * 获取Window cpu 序列号
     *
     * @return {@link String}
     */
    private static String getWindowCpuSerial() {
        StringBuilder result = new StringBuilder(StringConstants.EMPTY);
        File file = null;
        BufferedReader input = null;
        try {
            file = File.createTempFile("tmp", ".vbs");
            file.deleteOnExit();
            FileWriter fw = new FileWriter(file);
            String vbs = """
                Set objWMIService = GetObject("winmgmts:\\\\.\\root\\cimv2")
                Set colItems = objWMIService.ExecQuery("Select * from Win32_Processor")

                For Each objItem In colItems
                    WScript.Echo objItem.ProcessorId
                    Exit For ' do the first cpu only!
                Next
                """;
            fw.write(vbs);
            fw.close();

            Process p = Runtime.getRuntime().exec("cscript //NoLogo " + file.getPath());
            input = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line;
            while ((line = input.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            log.error("获取window cpu信息错误, {}", e.getMessage());
        } finally {
            IoUtil.close(input);
            FileUtil.del(file);
        }
        return result.toString().trim();
    }

    /**
     * 获取Linux主板序列号
     *
     * @return {@link String}
     */
    private static String getLinuxMainBoardSerial() {
        String command = "dmidecode | grep 'Serial Number' | awk '{print $3}' | tail -1";
        try {
            Process process = new ProcessBuilder("sh", "-c", command).start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                return reader.lines().findFirst().orElse(StringConstants.EMPTY);
            }
        } catch (IOException e) {
            log.error("获取 Linux 主板序列号失败: {}", e.getMessage());
            return StringConstants.EMPTY;
        }
    }

    /**
     * 获取window主板序列号
     *
     * @return {@link String}
     */
    private static String getWindowMainBoardSerial() {
        StringBuilder result = new StringBuilder(StringConstants.EMPTY);
        File file = null;
        BufferedReader input = null;
        try {
            file = File.createTempFile("realhowto", ".vbs");
            file.deleteOnExit();
            FileWriter fw = new FileWriter(file);

            String vbs = """
                Set objWMIService = GetObject("winmgmts:\\\\.\\root\\cimv2")
                Set colItems = objWMIService.ExecQuery _
                   ("Select * from Win32_BaseBoard")
                For Each objItem in colItems
                    Wscript.Echo objItem.SerialNumber
                    exit for  ' do the first cpu only!
                Next
                """;

            fw.write(vbs);
            fw.close();
            Process p = Runtime.getRuntime().exec("cscript //NoLogo " + file.getPath());
            input = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line;
            while ((line = input.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            log.error("获取Window主板信息错误 {}", e.getMessage());
        } finally {
            IoUtil.close(input);
            FileUtil.del(file);
        }
        return result.toString().trim();
    }

    /**
     * <p>获取Mac地址</p>
     *
     * @return List<String> Mac地址
     * @throws Exception 默认异常
     */
    public static Set<String> getMacAddress() throws Exception {
        // 获取所有网络接口
        Set<InetAddress> inetAddresses = getLocalAllInetAddress();
        if (CollectionUtil.isNotEmpty(inetAddresses)) {
            return CollUtils.mapToSet(inetAddresses, ServerInfoUtils::getMacByInetAddress);
        }
        return Collections.emptySet();
    }

    /**
     * <p>获取IP地址</p>
     *
     * @return List<String> IP地址
     * @throws Exception 默认异常
     */
    public static Set<String> getIpAddress() throws Exception {
        // 获取所有网络接口
        Set<InetAddress> inetAddresses = getLocalAllInetAddress();
        if (CollectionUtil.isNotEmpty(inetAddresses)) {
            return CollUtils.mapToSet(inetAddresses, InetAddress::getHostAddress);
        }
        return Collections.emptySet();
    }

    /**
     * <p>获取某个网络地址对应的Mac地址</p>
     *
     * @param inetAddr 网络地址
     * @return String Mac地址
     */
    private static String getMacByInetAddress(InetAddress inetAddr) {
        try {
            byte[] mac = NetworkInterface.getByInetAddress(inetAddr).getHardwareAddress();
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < mac.length; i++) {
                if (i != 0) {
                    stringBuilder.append("-");
                }
                // 将十六进制byte转化为字符串
                String temp = Integer.toHexString(mac[i] & 0xff);
                if (temp.length() == 1) {
                    stringBuilder.append("0").append(temp);
                } else {
                    stringBuilder.append(temp);
                }
            }
            return stringBuilder.toString().toUpperCase();
        } catch (SocketException e) {
            log.error("getMacByInetAddress {}", e.getMessage());
        }
        return null;
    }

    /**
     * <p>获取当前服务器所有符合条件的网络地址</p>
     *
     * @return List<InetAddress> 网络地址列表
     * @throws Exception 默认异常
     */
    private static Set<InetAddress> getLocalAllInetAddress() throws Exception {
        Set<InetAddress> result = CollUtil.newHashSet();
        // 遍历所有的网络接口
        for (Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            networkInterfaces.hasMoreElements();) {
            NetworkInterface ni = networkInterfaces.nextElement();
            // 在所有的接口下再遍历IP
            for (Enumeration<InetAddress> addresses = ni.getInetAddresses(); addresses.hasMoreElements();) {
                InetAddress address = addresses.nextElement();
                //排除LoopbackAddress、SiteLocalAddress、LinkLocalAddress、MulticastAddress类型的IP地址
                /*&& !inetAddr.isSiteLocalAddress()*/
                if (!address.isLoopbackAddress() && !address.isLinkLocalAddress() && !address.isMulticastAddress()) {
                    result.add(address);
                }
            }
        }
        return result;
    }
}
