//go:build linux

package system

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"strings"
	"sync"
	"time"
)

// CPUCollector samples CPU usage in the background by reading /proc/stat.
type CPUCollector struct {
	mu         sync.RWMutex
	cpuPercent float64
	sampleRate time.Duration
	cancel     context.CancelFunc
}

// NewCPUCollector creates a new CPU collector with the given sample rate.
func NewCPUCollector(sampleRate time.Duration) *CPUCollector {
	return &CPUCollector{
		sampleRate: sampleRate,
	}
}

// Start begins background CPU sampling.
func (c *CPUCollector) Start(ctx context.Context) {
	ctx, c.cancel = context.WithCancel(ctx)
	debugf("[DEBUG] [system] CPUCollector.Start: sampleRate=%s", c.sampleRate)
	go c.loop(ctx)
}

// Stop stops the background CPU sampling.
func (c *CPUCollector) Stop() {
	if c.cancel != nil {
		c.cancel()
	}
}

// CPUPercent returns the latest CPU usage percentage (0-100).
func (c *CPUCollector) CPUPercent() float64 {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.cpuPercent
}

func (c *CPUCollector) loop(ctx context.Context) {
	firstSample := true
	for {
		// Read first sample
		idle1, total1, err := readCPUStat()
		if err != nil {
			debugf("[DEBUG] [system] CPUCollector: readCPUStat error: %v", err)
			select {
			case <-ctx.Done():
				return
			case <-time.After(c.sampleRate):
				continue
			}
		}

		// Wait for sample interval
		select {
		case <-ctx.Done():
			return
		case <-time.After(c.sampleRate):
		}

		// Read second sample
		idle2, total2, err := readCPUStat()
		if err != nil {
			continue
		}

		totalDelta := total2 - total1
		idleDelta := idle2 - idle1

		if totalDelta > 0 {
			busyDelta := totalDelta - idleDelta
			percent := float64(busyDelta) / float64(totalDelta) * 100
			c.mu.Lock()
			c.cpuPercent = percent
			c.mu.Unlock()

			if firstSample {
				debugf("[DEBUG] [system] CPUCollector: first sample — cpu=%.1f%% (idle=%d total=%d)", percent, idleDelta, totalDelta)
				firstSample = false
			}
		}
	}
}

// readCPUStat reads /proc/stat and returns idle and total CPU jiffies.
// First line format: cpu <user> <nice> <system> <idle> <iowait> <irq> <softirq> <steal>
func readCPUStat() (idle, total uint64, err error) {
	f, err := os.Open("/proc/stat")
	if err != nil {
		return 0, 0, err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	if !scanner.Scan() {
		return 0, 0, fmt.Errorf("empty /proc/stat")
	}

	line := scanner.Text()
	if !strings.HasPrefix(line, "cpu ") {
		return 0, 0, fmt.Errorf("unexpected /proc/stat first line: %s", line)
	}

	fields := strings.Fields(line)
	if len(fields) < 9 {
		return 0, 0, fmt.Errorf("/proc/stat has too few fields: %d", len(fields))
	}

	// Fields: cpu user(1) nice(2) system(3) idle(4) iowait(5) irq(6) softirq(7) steal(8)
	var values [8]uint64
	for i := 0; i < 8; i++ {
		var v uint64
		for _, c := range fields[i+1] {
			if c >= '0' && c <= '9' {
				v = v*10 + uint64(c-'0')
			}
		}
		values[i] = v
	}

	// idle_total = idle + iowait
	idleTotal := values[3] + values[4]
	// total = sum of all
	var totalVal uint64
	for _, v := range values {
		totalVal += v
	}

	return idleTotal, totalVal, nil
}
