package com.tsi.common.utils;

import com.sun.management.OperatingSystemMXBean;
import lombok.Getter;

import java.io.File;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.time.Instant;
import java.time.ZoneId;

@Getter
public class SystemHealth {

    private String osName = System.getProperty("os.name");
    private String osVersion = System.getProperty("os.version");
    private int cpuCores = Runtime.getRuntime().availableProcessors();
    private long maxMemory = Runtime.getRuntime().maxMemory();

    private long totalMemory = 0;   // MB
    private long freeMemory = 0;    // MB
    private long usedMemory = 0;    // MB
    private long heapUsed;
    private long nonHeapUsed;

    private double memUsage = 0.;
    private double cpuUsage = 0.;
    private double loadAverage;         // CPU 부하

    private int threadCount = 0;    // 현재스레드 수
    private int peakThreadCount;    // 어플리케이션 운영중 최대 스레드 수

    private long jvmUptime;             // milliseconds, - 예: uptimeMillis = 3600000 → JVM이 1시간(60분) 동안 실행 중이라는 뜻
    private long loadedClassCount;

//    private final Map<String, Double> diskUsageMap = new HashMap<>();
    private long diskTotal;
    private long diskFree;
    private double diskUsage;

//    private final Map<String, GcStats> gcStatsMap = new HashMap<>();
    private String gcName;
    private long gcTotalCount = 0;
    private long gcTotalTime = 0;
    private long gcTotalAvgTime = 0;

    private long gcRecentCount = 0;
    private long gcRecentTime = 0;
    private long gcRecentAvgTime = 0;

    private final String jvmStartTime;

    public SystemHealth() {
        this.jvmStartTime = Instant.ofEpochMilli(
                ManagementFactory.getRuntimeMXBean().getStartTime()
        ).atZone(ZoneId.systemDefault()).toString();

        Runtime runtime = Runtime.getRuntime();
        this.maxMemory = runtime.maxMemory() / 1024 / 1024;
        this.cpuCores = runtime.availableProcessors();
        this.osName = System.getProperty("os.name");
        this.osVersion = System.getProperty("os.version");
    }

    public void checkHealth(boolean useDisk) {
        Runtime runtime = Runtime.getRuntime();
        this.totalMemory = runtime.totalMemory();   // Byte
        this.freeMemory = runtime.freeMemory();     // Byte
        this.usedMemory = this.totalMemory  - this.freeMemory;  // Byte

        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        this.heapUsed = memoryMXBean.getHeapMemoryUsage().getUsed() / 1024 / 1024;
        this.nonHeapUsed = memoryMXBean.getNonHeapMemoryUsage().getUsed() / 1024 / 1024;

        this.totalMemory = this.totalMemory / 1024 / 1024;
        this.freeMemory = this.freeMemory / 1024 / 1024;
        this.usedMemory = this.usedMemory / 1024 / 1024;

//        System.out.println("사용 중인 메모리: " + (usedMemory / 1024 / 1024) + " MB");
//        System.out.println("최대 메모리: " + (maxMemory / 1024 / 1024) + " MB");

        this.memUsage = (double) this.usedMemory / this.maxMemory * 100;
        // For Linux/Unix, Windows(Perhaps)
        OperatingSystemMXBean osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
        this.cpuUsage = osBean.getSystemCpuLoad() * 100;
        this.loadAverage = osBean.getSystemLoadAverage(); // 1분 평균

        this.threadCount = ManagementFactory.getThreadMXBean().getThreadCount();
        this.peakThreadCount = ManagementFactory.getThreadMXBean().getPeakThreadCount();

        this.jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
//        long uptimeSeconds = this.jvmUptime / 1000;
//        long uptimeMinutes = uptimeSeconds / 60;
//        long uptimeHours = uptimeMinutes / 60;
//        long uptimeDays = uptimeHours / 24;
//        System.out.println("JVM Uptime: "
//                + uptimeDays + "d "
//                + (uptimeHours % 24) + "h "
//                + (uptimeMinutes % 60) + "m "
//                + (uptimeSeconds % 60) + "s");


        this.loadedClassCount = ManagementFactory.getClassLoadingMXBean().getLoadedClassCount();

        if (useDisk) {
            File root = new File("/");
            if (root.exists() && root.canRead()) {
                this.diskTotal = root.getTotalSpace();
                this.diskFree = root.getFreeSpace();
                long used = this.diskTotal - this.diskFree;
                this.diskUsage = (double) used / this.diskTotal * 100;
            }
        }

        long totalGcCount = 0;
        long totalGcTime = 0;
        for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
            totalGcCount += gc.getCollectionCount();
            totalGcTime += gc.getCollectionTime();
//            this.gcName = gc.getName();
//            gcStatsMap.put(gc.getName(), new GcStats(gc.getCollectionCount(), gc.getCollectionTime()));
        }

        // 누적 평균
        this.gcTotalAvgTime = totalGcTime > 0 ? Math.round((double) totalGcTime / totalGcCount) : 0;

        // 최근 변화량
        this.gcRecentCount = totalGcCount - this.gcTotalCount;
        this.gcRecentTime = totalGcTime - this.gcTotalTime;
        this.gcRecentAvgTime = gcTotalCount > 0 ? Math.round((double) this.gcRecentTime  / gcTotalCount) : 0;

        this.gcTotalCount = totalGcCount;
        this.gcTotalTime = totalGcTime;

//        for (File root : File.listRoots()) {
//            if (root.exists() && root.canRead()) {
//                long total = root.getTotalSpace();
//                long free = root.getFreeSpace();
//                double usage = (double) (total - free) / total * 100;
//                diskUsageMap.put(root.getAbsolutePath(), usage);
//            }
//        }

        // GC가 전체 실행 시간의 몇 %를 차지했는지 확인 가능
//        long uptime = ManagementFactory.getRuntimeMXBean().getUptime(); // ms
//        long gcTime = this.gcTime;
//        double gcRatio = (double) gcTime / uptime * 100;
//        log.info("GC Time: {}ms, JVM Uptime: {}ms, GC 비중: {:.2f}%", gcTime, uptime, gcRatio);
    }

    public String getFormattedUptime() {
        long uptimeSeconds = this.jvmUptime / 1000;
        long uptimeMinutes = uptimeSeconds / 60;
        long uptimeHours = uptimeMinutes / 60;
        long uptimeDays = uptimeHours / 24;
        return uptimeDays + "d " + (uptimeHours % 24) + "h " + (uptimeMinutes % 60) + "m " + (uptimeSeconds % 60) + "s";
    }


}
