//go:build linux

package sys

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"sync"
	"syscall"
)

var SIGUSR1 = syscall.SIGUSR1

// countConnections returns the number of entries in a /proc/net/{tcp,udp}[6]
// file. Returns 0 if the file is absent (e.g. /proc/net/tcp6 when IPv6 is
// disabled) and excludes the column header line.
func countConnections(path string) (int, error) {
	f, err := os.Open(path)
	if os.IsNotExist(err) {
		return 0, nil
	}
	if err != nil {
		return 0, err
	}
	defer f.Close()

	sc := bufio.NewScanner(f)
	n := 0
	for sc.Scan() {
		n++
	}
	if err := sc.Err(); err != nil {
		return 0, err
	}
	if n > 0 {
		n-- // first line is the column header
	}
	return n, nil
}

// GetTCPCount returns the number of active TCP connections by reading
// /proc/net/tcp and /proc/net/tcp6 when available.
func GetTCPCount() (int, error) {
	root := HostProc()
	tcp4, err := countConnections(root + "/net/tcp")
	if err != nil {
		return 0, err
	}
	tcp6, err := countConnections(root + "/net/tcp6")
	if err != nil {
		return 0, err
	}
	return tcp4 + tcp6, nil
}

// GetUDPCount returns the number of active UDP connections by reading
// /proc/net/udp and /proc/net/udp6 when available.
func GetUDPCount() (int, error) {
	root := HostProc()
	udp4, err := countConnections(root + "/net/udp")
	if err != nil {
		return 0, err
	}
	udp6, err := countConnections(root + "/net/udp6")
	if err != nil {
		return 0, err
	}
	return udp4 + udp6, nil
}

// --- CPU Utilization (Linux native) ---

var (
	cpuMu       sync.Mutex
	lastTotal   uint64
	lastIdleAll uint64
	hasLast     bool
)

// CPUPercentRaw returns instantaneous total CPU utilization by reading
// /proc/stat. First call initializes and returns 0; subsequent calls return
// busy/total * 100. Uses HostProc so HOST_PROC overrides (containers) apply.
func CPUPercentRaw() (float64, error) {
	f, err := os.Open(HostProc("stat"))
	if err != nil {
		return 0, err
	}
	defer f.Close()

	rd := bufio.NewReader(f)
	line, err := rd.ReadString('\n')
	if err != nil && !errors.Is(err, io.EOF) {
		return 0, err
	}
	// Expect: cpu  user nice system idle iowait irq softirq steal guest guest_nice
	fields := strings.Fields(line)
	if len(fields) < 5 || fields[0] != "cpu" {
		return 0, fmt.Errorf("unexpected /proc/stat format")
	}

	nums := make([]uint64, 0, len(fields)-1)
	for i := 1; i < len(fields); i++ {
		v, err := strconv.ParseUint(fields[i], 10, 64)
		if err != nil {
			break
		}
		nums = append(nums, v)
	}
	if len(nums) < 4 {
		return 0, fmt.Errorf("insufficient cpu fields")
	}
	for len(nums) < 8 {
		nums = append(nums, 0)
	}

	user, nice, system, idle := nums[0], nums[1], nums[2], nums[3]
	iowait, irq, softirq, steal := nums[4], nums[5], nums[6], nums[7]

	idleAll := idle + iowait
	nonIdle := user + nice + system + irq + softirq + steal
	total := idleAll + nonIdle

	cpuMu.Lock()
	defer cpuMu.Unlock()

	if !hasLast {
		lastTotal = total
		lastIdleAll = idleAll
		hasLast = true
		return 0, nil
	}

	totald := total - lastTotal
	idled := idleAll - lastIdleAll
	lastTotal = total
	lastIdleAll = idleAll

	if totald == 0 {
		return 0, nil
	}
	busy := totald - idled
	pct := float64(busy) / float64(totald) * 100.0
	if pct > 100 {
		pct = 100
	}
	return pct, nil
}
