module Admin
  class HostMetrics
    RECORDINGS_PATH = ENV.fetch("RECORDINGS_PATH", "/recordings")
    ROOT_PATH = ENV.fetch("ADMIN_DISK_ROOT_PATH", "/")

    def sample!
      cpu = read_cpu_percent
      memory = read_memory
      disk = read_disk
      network = read_network

      sample = {
        at: Time.current.iso8601,
        cpu: cpu,
        mem: memory[:used_percent]
      }
      MetricsStore.push_sample(sample)

      {
        cpu: cpu,
        memory: memory,
        disk: disk,
        network: network,
        history: MetricsStore.history,
        load_avg: read_load_avg
      }
    end

    private

    def read_cpu_percent
      a = cpu_jiffies
      sleep 0.08
      b = cpu_jiffies
      return 0.0 unless a && b

      idle_delta = b[:idle] - a[:idle]
      total_delta = b[:total] - a[:total]
      return 0.0 if total_delta <= 0

      ((1.0 - (idle_delta.to_f / total_delta)) * 100).round(1)
    rescue StandardError
      0.0
    end

    def cpu_jiffies
      line = File.read("/proc/stat").each_line.find { |l| l.start_with?("cpu ") }
      return nil unless line

      parts = line.split[1..-1].map(&:to_i)
      idle = parts[3] + (parts[4] || 0)
      total = parts.sum
      { idle: idle, total: total }
    rescue StandardError
      nil
    end

    def read_memory
      info = {}
      File.read("/proc/meminfo").each_line do |line|
        key, value = line.split(":", 2)
        info[key.strip] = value.to_i
      end
      total = info["MemTotal"] * 1024
      available = (info["MemAvailable"] || info["MemFree"]) * 1024
      used = [total - available, 0].max
      {
        total_bytes: total,
        used_bytes: used,
        available_bytes: available,
        used_percent: total.positive? ? ((used.to_f / total) * 100).round(1) : 0.0
      }
    rescue StandardError
      { total_bytes: 0, used_bytes: 0, available_bytes: 0, used_percent: 0.0 }
    end

    def read_disk
      {
        root: disk_usage_for(ROOT_PATH),
        recordings: disk_usage_for(RECORDINGS_PATH)
      }
    end

    def disk_usage_for(path)
      return unavailable_disk unless path.present? && (Dir.exist?(path) || File.exist?(path))

      stat = File.statvfs(path)
      total = stat.blocks * stat.frsize
      free = stat.bavail * stat.frsize
      used = total - free
      {
        path: path,
        total_bytes: total,
        used_bytes: used,
        free_bytes: free,
        used_percent: total.positive? ? ((used.to_f / total) * 100).round(1) : 0.0,
        dir_size_bytes: recordings_dir_size(path)
      }
    rescue StandardError
      unavailable_disk
    end

    def recordings_dir_size(path)
      return nil unless path == RECORDINGS_PATH && Dir.exist?(path)

      `du -sb #{Shellwords.escape(path)} 2>/dev/null`.split.first.to_i
    rescue StandardError
      nil
    end

    def unavailable_disk
      { path: nil, total_bytes: nil, used_bytes: nil, free_bytes: nil, used_percent: nil, dir_size_bytes: nil }
    end

    def read_network
      rx, tx = parse_net_bytes
      prev = MetricsStore.net_totals
      MetricsStore.save_net_totals(rx: rx, tx: tx)

      delta_rx = prev[:rx] ? [rx - prev[:rx], 0].max : 0
      delta_tx = prev[:tx] ? [tx - prev[:tx], 0].max : 0

      {
        rx_bytes_total: rx,
        tx_bytes_total: tx,
        total_bytes: rx + tx
      }
    rescue StandardError
      { rx_bytes_total: 0, tx_bytes_total: 0, total_bytes: 0 }
    end

    def parse_net_bytes
      rx = 0
      tx = 0
      File.read("/proc/net/dev").each_line.drop(2).each do |line|
        iface = line.split(":").first.strip
        next if iface == "lo"

        cols = line.split(":").last.split.map(&:to_i)
        rx += cols[0]
        tx += cols[8]
      end
      [rx, tx]
    end

    def read_load_avg
      loads = File.read("/proc/loadavg").split[0..2].map(&:to_f)
      { "1m" => loads[0], "5m" => loads[1], "15m" => loads[2] }
    rescue StandardError
      { "1m" => 0.0, "5m" => 0.0, "15m" => 0.0 }
    end
  end
end
