package com.hertz.modules.monitor.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.NumberUtil;
import com.hertz.modules.monitor.dto.MonitorDto;
import com.hertz.modules.monitor.service.MonitorService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.CentralProcessor.TickType;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.FileSystem;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import oshi.util.Util;

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

@Slf4j
@Service
public class MonitorServiceImpl implements MonitorService {

    private final SystemInfo si = new SystemInfo();

    @Override
    public MonitorDto getServerInfo() {
        HardwareAbstractionLayer hal = si.getHardware();
        OperatingSystem os = si.getOperatingSystem();

        return MonitorDto.builder()
                .cpu(getCpuInfo(hal.getProcessor()))
                .mem(getMemInfo(hal.getMemory()))
                .sys(getSysInfo())
                .jvm(getJvmInfo())
                .disks(getDiskInfo(os))
                .build();
    }

    private MonitorDto.CpuInfo getCpuInfo(CentralProcessor processor) {
        long[] prevTicks = processor.getSystemCpuLoadTicks();
        Util.sleep(1000);
        long[] ticks = processor.getSystemCpuLoadTicks();
        long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()];
        long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()];
        long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()];
        long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()];
        long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()];
        long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()];
        long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()];
        long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()];
        long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;
        
        // Prevent division by zero
        if (totalCpu == 0) totalCpu = 1;

        return MonitorDto.CpuInfo.builder()
                .cpuNum(processor.getLogicalProcessorCount())
                .total(totalCpu)
                .sys(NumberUtil.round(cSys * 100.0 / totalCpu, 2).doubleValue())
                .used(NumberUtil.round(user * 100.0 / totalCpu, 2).doubleValue())
                .wait(NumberUtil.round(iowait * 100.0 / totalCpu, 2).doubleValue())
                .free(NumberUtil.round(idle * 100.0 / totalCpu, 2).doubleValue())
                .build();
    }

    private MonitorDto.MemInfo getMemInfo(GlobalMemory memory) {
        double total = NumberUtil.div(memory.getTotal(), (1024 * 1024 * 1024), 2);
        double free = NumberUtil.div(memory.getAvailable(), (1024 * 1024 * 1024), 2);
        double used = NumberUtil.sub(total, free);
        double usage = NumberUtil.mul(NumberUtil.div(used, total, 4), 100);
        
        return MonitorDto.MemInfo.builder()
                .total(total)
                .used(used)
                .free(free)
                .usage(usage)
                .build();
    }

    private MonitorDto.SysInfo getSysInfo() {
        Properties props = System.getProperties();
        return MonitorDto.SysInfo.builder()
                .computerName(NetUtil.getLocalHostName())
                .computerIp(NetUtil.getLocalhostStr())
                .osName(props.getProperty("os.name"))
                .osArch(props.getProperty("os.arch"))
                .userDir(props.getProperty("user.dir"))
                .build();
    }

    private MonitorDto.JvmInfo getJvmInfo() {
        Properties props = System.getProperties();
        RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
        long time = runtime.getStartTime();
        
        // GC Info
        long gcCount = 0;
        long gcTime = 0;
        List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean gcBean : gcBeans) {
            long count = gcBean.getCollectionCount();
            long t = gcBean.getCollectionTime();
            if (count > 0) gcCount += count;
            if (t > 0) gcTime += t;
        }
        
        double total = NumberUtil.div(Runtime.getRuntime().totalMemory(), (1024 * 1024), 2);
        double max = NumberUtil.div(Runtime.getRuntime().maxMemory(), (1024 * 1024), 2);
        double free = NumberUtil.div(Runtime.getRuntime().freeMemory(), (1024 * 1024), 2);
        double used = total - free;
        double usage = NumberUtil.mul(NumberUtil.div(used, total, 4), 100);

        return MonitorDto.JvmInfo.builder()
                .total(total)
                .max(max)
                .free(free)
                .version(props.getProperty("java.version"))
                .home(props.getProperty("java.home"))
                .name(ManagementFactory.getRuntimeMXBean().getVmName())
                .usage(usage)
                .startTime(DateUtil.formatDateTime(new Date(time)))
                .runTime(DateUtil.formatBetween(new Date(time), new Date()))
                .inputArgs(runtime.getInputArguments().toString())
                .gcCount(gcCount)
                .gcTime(gcTime)
                .build();
    }

    private List<MonitorDto.DiskInfo> getDiskInfo(OperatingSystem os) {
        List<MonitorDto.DiskInfo> list = new ArrayList<>();
        FileSystem fileSystem = os.getFileSystem();
        List<OSFileStore> fileStores = fileSystem.getFileStores();
        for (OSFileStore fs : fileStores) {
            long free = fs.getUsableSpace();
            long total = fs.getTotalSpace();
            long used = total - free;
            
            if (total == 0) continue;
            
            double usage = NumberUtil.mul(NumberUtil.div(used, total, 4), 100);
            
            MonitorDto.DiskInfo disk = MonitorDto.DiskInfo.builder()
                    .dirName(fs.getMount())
                    .sysTypeName(fs.getType())
                    .typeName(fs.getName())
                    .total(FileUtil.readableFileSize(total))
                    .free(FileUtil.readableFileSize(free))
                    .used(FileUtil.readableFileSize(used))
                    .usage(usage)
                    .build();
            list.add(disk);
        }
        return list;
    }
}
