package com.utic.center.utic.ptis.server.scheduler;

import com.utic.center.common.utils.SystemHealth;
import com.utic.center.common.utils.TimeUtils;
import com.utic.center.utic.ptis.server.config.ApplicationConfig;
import com.utic.center.utic.ptis.server.controller.UticPtisServerController;
import com.utic.center.utic.ptis.server.repository.ApplicationRepository;
import com.utic.center.utic.ptis.server.service.ProcessStateService;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.text.DecimalFormat;
import java.util.EnumMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
@RequiredArgsConstructor
@EnableScheduling
@Component
public class UticPtisServerScheduler {

    private final HikariDataSource dataSource;
    private final ApplicationConfig config;
    private final UticPtisServerController controller;
    private final ProcessStateService processStateService;

    private final AtomicBoolean isScheduleRunning = new AtomicBoolean(false);

    private final Map<Thread.State, Integer> stateCountMap = new EnumMap<>(Thread.State.class);
    private final SystemHealth systemHealth = new SystemHealth();
    private final DecimalFormat df = new DecimalFormat("#.##");
    private String scheduleTime;

    @Scheduled(cron = "0 * * * * *")
    public void scheduleProcess() {
        if (!this.config.isStartSchedule()) {
            return;
        }

        try {
            this.systemHealth.checkHealth(false);
            loggingHealthCheck();

            double cpuUsage = this.systemHealth.getCpuUsage();
            if (cpuUsage > this.config.getCpuLimits()) {
                ApplicationRepository.processState.setErrDesc("CPU 사용율이 너무 높음: " + String.format("%.2f", cpuUsage));
                log.warn("[SKIP] UticPtisServerScheduler.scheduleProcess: High CPU Usage, Limit({} %), Current({} %), Schedule Job SKIP...",
                        this.config.getCpuLimits(), String.format("%.2f", cpuUsage));
                loggingThreads();
                return;
            }
        } catch (Exception e) {
            log.error("UticPtisServerScheduler.scheduleProcess: System Health Check Exception {}", e.getMessage());
        }

        // 이전 작업이 아직 실행 중이면 스킵(비동기 처리로 변경할 경우 대비-보혐)
        if (!this.isScheduleRunning.compareAndSet(false, true)) {
            log.warn("UticPtisServerScheduler.scheduleProcess: Previous Schedule Running. {}, {}", this.scheduleTime, TimeUtils.getCurrentTimeString());
            return;
        }

        this.scheduleTime = TimeUtils.getCurrentTimeString();

        try {
            this.controller.run();
            this.processStateService.processRunning();
        } catch (Exception e) {
            log.error("UticPtisServerScheduler.scheduleProcess: Exception {}", e.getMessage());
        }
        finally {
            this.isScheduleRunning.set(false); // 작업 완료 후 플래그 초기화
        }
    }

    private void loggingHealthCheck() {
        log.info("----------------------------------------------------------------------------------------------------------");
        loggingSystemHealth();
        logSessionStatus();
        loggingThreadState();
        log.info("----------------------------------------------------------------------------------------------------------");
    }

    private void loggingSystemHealth() {
        double loadAvg = this.systemHealth.getLoadAverage();
        String loadAvgStr = (loadAvg < 0) ? "N/A" : this.df.format(loadAvg);
        log.info("SYSTEM HEALTH: CPU {} Cores[{} %, LoadAvg({})], Memory[{} %, Used({} MB), Max({} MB)], Threads[{}, Peak({})], GC[{}, {} ms]",
                this.systemHealth.getCpuCores(),
                this.df.format(this.systemHealth.getCpuUsage()),
                loadAvgStr,
                this.df.format(this.systemHealth.getMemUsage()),
                this.systemHealth.getUsedMemory(),
                this.systemHealth.getMaxMemory(),
                this.systemHealth.getThreadCount(),
                this.systemHealth.getPeakThreadCount(),
                this.systemHealth.getGcCount(),
                this.systemHealth.getGcTime());
    }

    private void logSessionStatus() {
        HikariPoolMXBean poolStats = this.dataSource.getHikariPoolMXBean();
        int totalConnections = poolStats.getTotalConnections();
        int activeConnections = poolStats.getActiveConnections();
        int idleConnections = poolStats.getIdleConnections();
        int threadsAwaiting = poolStats.getThreadsAwaitingConnection();
        log.info("   DB SESSION: Total: {}, Active: {}, Idle: {}, Waiting: {}",
                totalConnections, activeConnections, idleConnections, threadsAwaiting);
    }

    private void loggingThreadState() {
        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
        this.stateCountMap.clear();
        for (ThreadInfo info : infos) {
            Thread.State state = info.getThreadState();
            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
        }
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<Thread.State, Integer> entry : stateCountMap.entrySet()) {
            sb.append(String.format("%s(%d), ", entry.getKey().name(), entry.getValue()));
        }
        // 마지막 쉼표 제거
        if (sb.length() > 0) {
            sb.setLength(sb.length() - 2);
        }
        log.info(" THREAD STATE: {}", sb.toString());
    }

    private void loggingThreads() {
        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
        this.stateCountMap.clear();
        for (ThreadInfo info : infos) {
            Thread.State state = info.getThreadState();
            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
            log.info("         [" + info.getThreadId() + "] " + info.getThreadName() + " - " + info.getThreadState());
        }
    }

}
