package collector

import (
	"bee/audit/internal/schema"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

var (
	cpuSysBaseDir = "/sys/devices/system/cpu"
	socketIndexRe = regexp.MustCompile(`(?i)(?:package id|socket|cpu)\s*([0-9]+)`)
)

func enrichCPUsWithTelemetry(cpus []schema.HardwareCPU, doc sensorsDoc) []schema.HardwareCPU {
	if len(cpus) == 0 {
		return cpus
	}

	tempBySocket := cpuTempsFromSensors(doc, len(cpus))
	powerBySocket := cpuPowerFromSensors(doc, len(cpus))
	throttleBySocket := cpuThrottleBySocket()

	for i := range cpus {
		socket := 0
		if cpus[i].Socket != nil {
			socket = *cpus[i].Socket
		}
		if value, ok := tempBySocket[socket]; ok {
			cpus[i].TemperatureC = &value
		}
		if value, ok := powerBySocket[socket]; ok {
			cpus[i].PowerW = &value
		}
		if value, ok := throttleBySocket[socket]; ok {
			cpus[i].Throttled = &value
		}
	}

	return cpus
}

func cpuTempsFromSensors(doc sensorsDoc, cpuCount int) map[int]float64 {
	out := map[int]float64{}
	if len(doc) == 0 {
		return out
	}
	var fallback []float64
	for chip, features := range doc {
		for featureName, raw := range features {
			feature, ok := raw.(map[string]any)
			if !ok {
				continue
			}
			if classifySensorFeature(feature) != "temp" {
				continue
			}
			temp, ok := firstFeatureFloat(feature, "_input")
			if !ok {
				continue
			}
			if socket, ok := detectCPUSocket(chip, featureName); ok {
				if _, exists := out[socket]; !exists {
					out[socket] = temp
				}
				continue
			}
			if isLikelyCPUTemp(chip, featureName) {
				fallback = append(fallback, temp)
			}
		}
	}
	if len(out) == 0 && cpuCount == 1 && len(fallback) > 0 {
		out[0] = fallback[0]
	}
	return out
}

func cpuPowerFromSensors(doc sensorsDoc, cpuCount int) map[int]float64 {
	out := map[int]float64{}
	if len(doc) == 0 {
		return out
	}
	var fallback []float64
	for chip, features := range doc {
		for featureName, raw := range features {
			feature, ok := raw.(map[string]any)
			if !ok {
				continue
			}
			if classifySensorFeature(feature) != "power" {
				continue
			}
			power, ok := firstFeatureFloatWithContains(feature, []string{"power"})
			if !ok {
				continue
			}
			if socket, ok := detectCPUSocket(chip, featureName); ok {
				if _, exists := out[socket]; !exists {
					out[socket] = power
				}
				continue
			}
			if isLikelyCPUPower(chip, featureName) {
				fallback = append(fallback, power)
			}
		}
	}
	if len(out) == 0 && cpuCount == 1 && len(fallback) > 0 {
		out[0] = fallback[0]
	}
	return out
}

func detectCPUSocket(parts ...string) (int, bool) {
	for _, part := range parts {
		matches := socketIndexRe.FindStringSubmatch(strings.ToLower(part))
		if len(matches) == 2 {
			value, err := strconv.Atoi(matches[1])
			if err == nil {
				return value, true
			}
		}
	}
	return 0, false
}

func isLikelyCPUTemp(chip, feature string) bool {
	value := strings.ToLower(chip + " " + feature)
	return strings.Contains(value, "coretemp") ||
		strings.Contains(value, "k10temp") ||
		strings.Contains(value, "package id") ||
		strings.Contains(value, "tdie") ||
		strings.Contains(value, "tctl") ||
		strings.Contains(value, "cpu temp")
}

func isLikelyCPUPower(chip, feature string) bool {
	value := strings.ToLower(chip + " " + feature)
	return strings.Contains(value, "intel-rapl") ||
		strings.Contains(value, "package id") ||
		strings.Contains(value, "package-") ||
		strings.Contains(value, "cpu power")
}

func cpuThrottleBySocket() map[int]bool {
	out := map[int]bool{}
	cpuDirs, err := filepath.Glob(filepath.Join(cpuSysBaseDir, "cpu[0-9]*"))
	if err != nil {
		return out
	}
	sort.Strings(cpuDirs)
	for _, cpuDir := range cpuDirs {
		socket, ok := readSocketIndex(cpuDir)
		if !ok {
			continue
		}
		if cpuPackageThrottled(cpuDir) {
			out[socket] = true
		}
	}
	return out
}

func readSocketIndex(cpuDir string) (int, bool) {
	raw, err := os.ReadFile(filepath.Join(cpuDir, "topology", "physical_package_id"))
	if err != nil {
		return 0, false
	}
	value, err := strconv.Atoi(strings.TrimSpace(string(raw)))
	if err != nil || value < 0 {
		return 0, false
	}
	return value, true
}

func cpuPackageThrottled(cpuDir string) bool {
	paths := []string{
		filepath.Join(cpuDir, "thermal_throttle", "package_throttle_count"),
		filepath.Join(cpuDir, "thermal_throttle", "core_throttle_count"),
	}
	for _, path := range paths {
		raw, err := os.ReadFile(path)
		if err != nil {
			continue
		}
		value, err := strconv.ParseInt(strings.TrimSpace(string(raw)), 10, 64)
		if err == nil && value > 0 {
			return true
		}
	}
	return false
}
