// Package collector는 호스트 시스템 자원과 서비스 인스턴스 지표를 수집합니다.
package collector

import (
	"regexp"
	"time"

	"host-agent/internal/payload"

	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/disk"
	"github.com/shirou/gopsutil/v3/host"
	"github.com/shirou/gopsutil/v3/load"
	"github.com/shirou/gopsutil/v3/mem"
	psnet "github.com/shirou/gopsutil/v3/net"
	"github.com/shirou/gopsutil/v3/process"
)

// rateBps는 누적 카운터 두 시점의 델타를 초당 속도(B/s)로 환산합니다.
// 카운터 리셋(음수 델타)이나 경과 시간 0은 0으로 처리합니다.
func rateBps(cur, prev int64, seconds float64) int64 {
	if seconds <= 0 || cur <= prev {
		return 0
	}
	return int64(float64(cur-prev) / seconds)
}

// physicalDiskPattern은 파티션(sda1, nvme0n1p1)과 가상 디바이스(loop, ram, sr)를 제외한
// 물리 블록 디바이스명에 매칭됩니다. disk0은 macOS(개발 환경)용.
var physicalDiskPattern = regexp.MustCompile(`^(?:[shvx][v]?d[a-z]+|nvme\d+n\d+|mmcblk\d+|md\d+|dm-\d+|disk\d+)$`)

// isPhysicalDisk는 디스크 I/O 합산 시 파티션 이중 집계를 막기 위한 필터입니다.
func isPhysicalDisk(name string) bool {
	return physicalDiskPattern.MatchString(name)
}

// sumDiskCounters는 물리 디스크들의 누적 read/write 바이트를 합산합니다.
// 물리 디스크 판정에 하나도 걸리지 않으면(이름 체계가 다른 환경) 전체 합산으로 폴백합니다.
func sumDiskCounters() (read, write int64) {
	counters, err := disk.IOCounters()
	if err != nil {
		return 0, 0
	}
	var allR, allW int64
	matched := false
	for name, c := range counters {
		allR += int64(c.ReadBytes)
		allW += int64(c.WriteBytes)
		if isPhysicalDisk(name) {
			read += int64(c.ReadBytes)
			write += int64(c.WriteBytes)
			matched = true
		}
	}
	if !matched {
		return allR, allW
	}
	return read, write
}

// procCounts는 전체/실행중 프로세스 수를 반환합니다.
// 리눅스는 /proc 기반 load.Misc()로 실행(R) 상태까지 구하고,
// 그 외 환경은 PID 목록 개수로 전체 수만 폴백 집계합니다(running=0).
func procCounts() (total, running int64) {
	if m, err := load.Misc(); err == nil && m.ProcsTotal > 0 {
		return int64(m.ProcsTotal), int64(m.ProcsRunning)
	}
	if pids, err := process.Pids(); err == nil {
		total = int64(len(pids))
	}
	return total, running
}

// SystemCollector는 호스트 시스템 메트릭을 수집하며, 네트워크/디스크 속도(B/s) 계산을 위해
// 직전 누적 카운터와 시각을 보관합니다.
type SystemCollector struct {
	prevSent      int64
	prevRecv      int64
	prevDiskRead  int64
	prevDiskWrite int64
	prevTime      time.Time
}

func NewSystem() *SystemCollector {
	c := &SystemCollector{prevTime: time.Now()}
	// cpu.Percent는 직전 호출과의 델타로 계산되므로 최초 1회 프라이밍합니다.
	_, _ = cpu.Percent(0, false)
	if io, err := psnet.IOCounters(false); err == nil && len(io) > 0 {
		c.prevSent = int64(io[0].BytesSent)
		c.prevRecv = int64(io[0].BytesRecv)
	}
	c.prevDiskRead, c.prevDiskWrite = sumDiskCounters()
	return c
}

func (c *SystemCollector) Collect() payload.SystemMetrics {
	m := payload.SystemMetrics{}

	if p, err := cpu.Percent(0, false); err == nil && len(p) > 0 {
		m.CpuUsage = p[0]
	}
	if v, err := mem.VirtualMemory(); err == nil {
		m.MemoryUsage = v.UsedPercent
		m.MemTotal = int64(v.Total)
		m.MemUsed = int64(v.Used)
	}
	if d, err := disk.Usage("/"); err == nil {
		m.DiskUsage = d.UsedPercent
		m.DiskTotal = int64(d.Total)
		m.DiskUsed = int64(d.Used)
	}
	if l, err := load.Avg(); err == nil {
		l1, l5, l15 := l.Load1, l.Load5, l.Load15
		m.Load1m, m.Load5m, m.Load15m = &l1, &l5, &l15
	}
	if up, err := host.Uptime(); err == nil {
		m.UptimeSec = int64(up)
	}
	m.ProcsTotal, m.ProcsRunning = procCounts()

	now := time.Now()
	dur := now.Sub(c.prevTime).Seconds()
	if io, err := psnet.IOCounters(false); err == nil && len(io) > 0 {
		cs, cr := int64(io[0].BytesSent), int64(io[0].BytesRecv)
		m.BytesSent = rateBps(cs, c.prevSent, dur)
		m.BytesRecv = rateBps(cr, c.prevRecv, dur)
		c.prevSent, c.prevRecv = cs, cr
	}
	dr, dw := sumDiskCounters()
	m.DiskReadBps = rateBps(dr, c.prevDiskRead, dur)
	m.DiskWriteBps = rateBps(dw, c.prevDiskWrite, dur)
	c.prevDiskRead, c.prevDiskWrite = dr, dw
	c.prevTime = now

	return m
}
