package com.kamco.cd.training.common.service;

import com.kamco.cd.training.common.dto.MonitorDto;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Log4j2
public class SystemMonitorService {

  // =========================
  // CPU 이전값 (delta 계산용)
  // - /proc/stat은 누적값이기 때문에
  // - 이전 값과 비교해서 사용률 계산
  // =========================
  private long prevIdle = 0;
  private long prevTotal = 0;

  // =========================
  // 최근 30초 히스토리
  // - CPU: 30개 (1초 * 30)
  // - GPU: GPU별 30개
  // =========================
  private final Deque<Double> cpuHistory = new ArrayDeque<>();

  // key: GPU index
  // value: 최근 30개 사용률
  private final Map<Integer, Deque<Integer>> gpuHistory = new ConcurrentHashMap<>();

  // =========================
  // GPU 데이터 제공 (dmon reader)
  // =========================
  private final GpuDmonReader gpuReader;

  // =========================
  // 캐시 (API 응답용)
  // - 매 요청마다 계산하지 않기 위해 사용
  // - volatile → 멀티스레드 안전하게 최신값 유지
  // =========================
  private volatile MonitorDto cached = new MonitorDto();

  // =========================
  // 1초마다 수집
  // =========================
  @Scheduled(fixedRate = 1000)
  public void collect() {
    try {

      // =====================
      // 1. CPU 수집
      // =====================
      double cpu = readCpu();

      cpuHistory.add(cpu);

      // 30개 유지 (rolling window)
      if (cpuHistory.size() > 30) cpuHistory.poll();

      // =====================
      // 2. GPU 수집
      // =====================
      Map<Integer, Integer> gpuMap = gpuReader.getGpuUtilMap();

      for (Map.Entry<Integer, Integer> entry : gpuMap.entrySet()) {

        int index = entry.getKey();
        int util = entry.getValue();

        // GPU별 히스토리 생성 및 추가
        gpuHistory.computeIfAbsent(index, k -> new ArrayDeque<>()).add(util);

        // 30개 유지
        Deque<Integer> q = gpuHistory.get(index);
        if (q.size() > 30) q.poll();
      }

      // =====================
      // 3. 캐시 업데이트
      // =====================
      updateCache();

    } catch (Exception e) {
      log.error("collect error", e);
    }
  }

  // =========================
  // CPU 사용률 계산
  // - /proc/stat 사용
  // - 이전값과의 차이로 계산 (delta 방식)
  // =========================
  private double readCpu() throws Exception {

    if (!isLinux()) return 0;

    try (BufferedReader br = new BufferedReader(new FileReader("/proc/stat"))) {

      String[] p = br.readLine().split("\\s+");

      long user = Long.parseLong(p[1]);
      long nice = Long.parseLong(p[2]);
      long system = Long.parseLong(p[3]);
      long idle = Long.parseLong(p[4]);
      long iowait = Long.parseLong(p[5]);
      long irq = Long.parseLong(p[6]);
      long softirq = Long.parseLong(p[7]);

      long total = user + nice + system + idle + iowait + irq + softirq;
      long idleAll = idle + iowait;

      // 최초 실행 시 기준값만 저장
      if (prevTotal == 0) {
        prevTotal = total;
        prevIdle = idleAll;
        return 0;
      }

      long totalDiff = total - prevTotal;
      long idleDiff = idleAll - prevIdle;

      prevTotal = total;
      prevIdle = idleAll;

      if (totalDiff == 0) return 0;

      // CPU 사용률 (%)
      return (1.0 - (double) idleDiff / totalDiff) * 100;
    }
  }

  // =========================
  // Linux 환경 체크
  // =========================
  private boolean isLinux() {
    return System.getProperty("os.name").toLowerCase().contains("linux");
  }

  // =========================
  // Memory 조회 (/proc/meminfo)
  // - OS 값 그대로 사용 (kB)
  // - [사용량, 전체]
  // =========================
  private long[] readMemory() throws Exception {

    if (!isLinux()) return new long[] {0, 0};

    try (BufferedReader br = new BufferedReader(new FileReader("/proc/meminfo"))) {

      long total = 0;
      long available = 0;

      String line;
      while ((line = br.readLine()) != null) {
        if (line.startsWith("MemTotal")) {
          total = Long.parseLong(line.replaceAll("\\D+", ""));
        } else if (line.startsWith("MemAvailable")) {
          available = Long.parseLong(line.replaceAll("\\D+", ""));
        }
      }

      long used = total - available;

      return new long[] {used, total};
    }
  }

  // =========================
  // 캐시 업데이트
  // - CPU: 30초 평균
  // - GPU: 전체 샘플 평균
  // - Memory: 현재값
  // =========================
  private void updateCache() throws Exception {

    MonitorDto dto = new MonitorDto();

    // =====================
    // CPU 평균 (30초)
    // =====================
    dto.cpu = (int) cpuHistory.stream().mapToDouble(Double::doubleValue).average().orElse(0);

    // =====================
    // Memory (kB 그대로)
    // =====================
    dto.memory = readMemory();

    // =====================
    // GPU 평균 (🔥 전체 샘플 기준)
    // =====================
    int sum = 0;
    int count = 0;

    for (Deque<Integer> q : gpuHistory.values()) {
      for (int v : q) {
        sum += v;
        count++;
      }
    }

    dto.gpu = (count == 0) ? 0 : sum / count;

    // =====================
    // 캐시 교체 (atomic)
    // =====================
    this.cached = dto;
  }

  // =========================
  // 외부 조회
  // - Controller에서 호출
  // =========================
  public MonitorDto get() {
    return cached;
  }
}
