package system

import (
	"math"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"nadir/internal/oscmd"
)

type CPUInfo struct {
	Model       string `json:"model" example:"AMD Ryzen 7 7840U" doc:"CPU model name"`
	LogicalCPUs int    `json:"logical_cpus" example:"16" doc:"Number of logical CPUs (cores × threads)"`
	MinMHz      int    `json:"min_mhz" example:"400" doc:"Lowest frequency the scaling governor can select"`
	MaxMHz      int    `json:"max_mhz" example:"5137" doc:"Highest frequency (boost ceiling)"`
	CurrentMHz  int    `json:"current_mhz" example:"3157" doc:"Peak current clock across all cores (instantaneous snapshot; 0 if cpufreq unavailable)"`
}

func cpuInfo() CPUInfo {
	data, _ := os.ReadFile("/proc/cpuinfo")
	c := CPUInfo{Model: cpuModel(string(data)), LogicalCPUs: runtime.NumCPU()}
	c.MinMHz, c.MaxMHz, c.CurrentMHz = cpuFreqMHz("/sys/devices/system/cpu")
	mhz := cpuinfoMaxMHz(string(data))
	if c.Model == "" || mhz == 0 {
		model, lscpuMHz := lscpuFallback()
		if c.Model == "" {
			c.Model = model
		}
		if mhz == 0 {
			mhz = lscpuMHz
		}
	}
	if mhz > 0 {
		if c.CurrentMHz == 0 {
			c.CurrentMHz = mhz
		}
		if c.MaxMHz == 0 {
			c.MaxMHz = mhz
		}
		if c.MinMHz == 0 {
			c.MinMHz = mhz
		}
	}
	return c
}

func lscpuFallback() (model string, mhz int) {
	out, err := oscmd.Run("lscpu")
	if err != nil {
		return "", 0
	}
	for line := range strings.SplitSeq(out, "\n") {
		k, v, ok := strings.Cut(line, ":")
		if !ok {
			continue
		}
		k, v = strings.TrimSpace(k), strings.TrimSpace(v)
		switch k {
		case "Model name":
			if model == "" {
				model = v
			}
		case "BIOS Model name":
			if model == "" {
				model = v
			}
		case "CPU max MHz", "CPU MHz":
			if f, err := strconv.ParseFloat(v, 64); err == nil && int(f) > mhz {
				mhz = int(math.Round(f))
			}
		}
	}
	if mhz == 0 {
		mhz = parseGHzSuffix(model)
	}
	return model, mhz
}

func parseGHzSuffix(s string) int {
	i := strings.LastIndex(s, "@")
	if i < 0 {
		return 0
	}
	rest := strings.TrimSpace(s[i+1:])
	rest = strings.TrimSuffix(strings.TrimSuffix(rest, "GHz"), "Ghz")
	rest = strings.TrimSpace(strings.TrimSuffix(rest, "G"))
	f, err := strconv.ParseFloat(strings.TrimSpace(rest), 64)
	if err != nil {
		return 0
	}
	return int(math.Round(f * 1000))
}

func cpuinfoMaxMHz(cpuinfo string) int {
	var max float64
	for line := range strings.SplitSeq(cpuinfo, "\n") {
		k, v, ok := strings.Cut(line, ":")
		if !ok || strings.TrimSpace(k) != "cpu MHz" {
			continue
		}
		if f, err := strconv.ParseFloat(strings.TrimSpace(v), 64); err == nil && f > max {
			max = f
		}
	}
	return int(math.Round(max))
}

func cpuFreqMHz(root string) (min, max, cur int) {
	min = readKHzAsMHz(filepath.Join(root, "cpu0/cpufreq/cpuinfo_min_freq"))
	max = readKHzAsMHz(filepath.Join(root, "cpu0/cpufreq/cpuinfo_max_freq"))
	cores, _ := filepath.Glob(filepath.Join(root, "cpu[0-9]*/cpufreq/scaling_cur_freq"))
	for _, f := range cores {
		if v := readKHzAsMHz(f); v > cur {
			cur = v
		}
	}
	return min, max, cur
}

func readKHzAsMHz(path string) int {
	khz, err := strconv.Atoi(readTrim(path))
	if err != nil {
		return 0
	}
	return khz / 1000
}

func cpuModel(cpuinfo string) string {
	var fallback string
	for line := range strings.SplitSeq(cpuinfo, "\n") {
		k, v, ok := strings.Cut(line, ":")
		if !ok {
			continue
		}
		switch strings.TrimSpace(k) {
		case "model name":
			return strings.TrimSpace(v)
		case "Model":
			fallback = strings.TrimSpace(v)
		}
	}
	return fallback
}
