package collector

import (
	"bee/audit/internal/schema"
	"encoding/json"
	"log/slog"
	"os/exec"
	"sort"
	"strconv"
	"strings"
)

type sensorsDoc map[string]map[string]any

func collectSensors() *schema.HardwareSensors {
	doc, err := readSensorsJSONDoc()
	if err != nil {
		slog.Info("sensors: unavailable, skipping", "err", err)
		return nil
	}
	sensors := buildSensorsFromDoc(doc)
	if sensors == nil || (len(sensors.Fans) == 0 && len(sensors.Power) == 0 && len(sensors.Temperatures) == 0 && len(sensors.Other) == 0) {
		return nil
	}
	slog.Info("sensors: collected",
		"fans", len(sensors.Fans),
		"power", len(sensors.Power),
		"temperatures", len(sensors.Temperatures),
		"other", len(sensors.Other),
	)
	return sensors
}

func readSensorsJSONDoc() (sensorsDoc, error) {
	out, err := exec.Command("sensors", "-j").Output()
	if err != nil {
		return nil, err
	}
	var doc sensorsDoc
	if err := json.Unmarshal(out, &doc); err != nil {
		return nil, err
	}
	return doc, nil
}

func buildSensorsFromDoc(doc sensorsDoc) *schema.HardwareSensors {
	if len(doc) == 0 {
		return nil
	}
	result := &schema.HardwareSensors{}
	seen := map[string]struct{}{}

	chips := make([]string, 0, len(doc))
	for chip := range doc {
		chips = append(chips, chip)
	}
	sort.Strings(chips)

	for _, chip := range chips {
		features := doc[chip]
		location := sensorLocation(chip)

		keys := make([]string, 0, len(features))
		for key := range features {
			keys = append(keys, key)
		}
		sort.Strings(keys)

		for _, key := range keys {
			if strings.EqualFold(key, "Adapter") {
				continue
			}
			feature, ok := features[key].(map[string]any)
			if !ok {
				continue
			}
			name := strings.TrimSpace(key)
			if name == "" {
				continue
			}
			switch classifySensorFeature(feature) {
			case "fan":
				item := buildFanSensor(name, location, feature)
				if item == nil || duplicateSensor(seen, "fan", item.Name) {
					continue
				}
				result.Fans = append(result.Fans, *item)
			case "temp":
				item := buildTempSensor(name, location, feature)
				if item == nil || duplicateSensor(seen, "temp", item.Name) {
					continue
				}
				result.Temperatures = append(result.Temperatures, *item)
			case "power":
				item := buildPowerSensor(name, location, feature)
				if item == nil || duplicateSensor(seen, "power", item.Name) {
					continue
				}
				result.Power = append(result.Power, *item)
			default:
				item := buildOtherSensor(name, location, feature)
				if item == nil || duplicateSensor(seen, "other", item.Name) {
					continue
				}
				result.Other = append(result.Other, *item)
			}
		}
	}

	return result
}

func parseSensorsJSON(raw []byte) (*schema.HardwareSensors, error) {
	var doc sensorsDoc
	err := json.Unmarshal(raw, &doc)
	if err != nil {
		return nil, err
	}
	return buildSensorsFromDoc(doc), nil
}

func duplicateSensor(seen map[string]struct{}, sensorType, name string) bool {
	key := sensorType + "\x00" + name
	if _, ok := seen[key]; ok {
		return true
	}
	seen[key] = struct{}{}
	return false
}

func sensorLocation(chip string) *string {
	chip = strings.TrimSpace(chip)
	if chip == "" {
		return nil
	}
	return &chip
}

func classifySensorFeature(feature map[string]any) string {
	for key := range feature {
		switch {
		case strings.Contains(key, "fan") && strings.HasSuffix(key, "_input"):
			return "fan"
		case strings.Contains(key, "temp") && strings.HasSuffix(key, "_input"):
			return "temp"
		case strings.Contains(key, "power") && (strings.HasSuffix(key, "_input") || strings.HasSuffix(key, "_average")):
			return "power"
		case strings.Contains(key, "curr") && strings.HasSuffix(key, "_input"):
			return "power"
		case strings.HasPrefix(key, "in") && strings.HasSuffix(key, "_input"):
			return "power"
		}
	}
	return "other"
}

func buildFanSensor(name string, location *string, feature map[string]any) *schema.HardwareFanSensor {
	rpm, ok := firstFeatureInt(feature, "_input")
	if !ok {
		return nil
	}
	item := &schema.HardwareFanSensor{Name: name, Location: location, RPM: &rpm}
	if status := sensorStatusFromFeature(feature); status != nil {
		item.Status = status
	}
	return item
}

func buildTempSensor(name string, location *string, feature map[string]any) *schema.HardwareTemperatureSensor {
	celsius, ok := firstFeatureFloat(feature, "_input")
	if !ok {
		return nil
	}
	item := &schema.HardwareTemperatureSensor{Name: name, Location: location, Celsius: &celsius}
	if warning, ok := firstFeatureFloatWithSuffixes(feature, []string{"_max", "_high"}); ok {
		item.ThresholdWarningCelsius = &warning
	}
	if critical, ok := firstFeatureFloatWithSuffixes(feature, []string{"_crit", "_emergency"}); ok {
		item.ThresholdCriticalCelsius = &critical
	}
	if status := sensorStatusFromFeature(feature); status != nil {
		item.Status = status
	} else {
		item.Status = deriveTemperatureStatus(item.Celsius, item.ThresholdWarningCelsius, item.ThresholdCriticalCelsius)
	}
	return item
}

func buildPowerSensor(name string, location *string, feature map[string]any) *schema.HardwarePowerSensor {
	item := &schema.HardwarePowerSensor{Name: name, Location: location}
	if v, ok := firstFeatureFloatWithContains(feature, []string{"power"}); ok {
		item.PowerW = &v
	}
	if v, ok := firstFeatureFloatWithPrefix(feature, "curr"); ok {
		item.CurrentA = &v
	}
	if v, ok := firstFeatureFloatWithPrefix(feature, "in"); ok {
		item.VoltageV = &v
	}
	if item.PowerW == nil && item.CurrentA == nil && item.VoltageV == nil {
		return nil
	}
	if status := sensorStatusFromFeature(feature); status != nil {
		item.Status = status
	}
	return item
}

func buildOtherSensor(name string, location *string, feature map[string]any) *schema.HardwareOtherSensor {
	value, unit, ok := firstGenericSensorValue(feature)
	if !ok {
		return nil
	}
	item := &schema.HardwareOtherSensor{Name: name, Location: location, Value: &value}
	if unit != "" {
		item.Unit = &unit
	}
	if status := sensorStatusFromFeature(feature); status != nil {
		item.Status = status
	}
	return item
}

func sensorStatusFromFeature(feature map[string]any) *string {
	for key, raw := range feature {
		if !strings.HasSuffix(key, "_alarm") {
			continue
		}
		if number, ok := floatFromAny(raw); ok && number > 0 {
			status := statusWarning
			return &status
		}
	}
	return nil
}

func deriveTemperatureStatus(current, warning, critical *float64) *string {
	if current == nil {
		return nil
	}
	switch {
	case critical != nil && *current >= *critical:
		status := statusCritical
		return &status
	case warning != nil && *current >= *warning:
		status := statusWarning
		return &status
	default:
		status := statusOK
		return &status
	}
}

func firstFeatureInt(feature map[string]any, suffix string) (int, bool) {
	for key, raw := range feature {
		if strings.HasSuffix(key, suffix) {
			if value, ok := floatFromAny(raw); ok {
				return int(value), true
			}
		}
	}
	return 0, false
}

func firstFeatureFloat(feature map[string]any, suffix string) (float64, bool) {
	return firstFeatureFloatWithSuffixes(feature, []string{suffix})
}

func firstFeatureFloatWithSuffixes(feature map[string]any, suffixes []string) (float64, bool) {
	keys := sortedFeatureKeys(feature)
	for _, key := range keys {
		for _, suffix := range suffixes {
			if strings.HasSuffix(key, suffix) {
				if value, ok := floatFromAny(feature[key]); ok {
					return value, true
				}
			}
		}
	}
	return 0, false
}

func firstFeatureFloatWithContains(feature map[string]any, parts []string) (float64, bool) {
	keys := sortedFeatureKeys(feature)
	for _, key := range keys {
		matched := true
		for _, part := range parts {
			if !strings.Contains(key, part) {
				matched = false
				break
			}
		}
		if matched {
			if value, ok := floatFromAny(feature[key]); ok {
				return value, true
			}
		}
	}
	return 0, false
}

func firstFeatureFloatWithPrefix(feature map[string]any, prefix string) (float64, bool) {
	keys := sortedFeatureKeys(feature)
	for _, key := range keys {
		if strings.HasPrefix(key, prefix) && strings.HasSuffix(key, "_input") {
			if value, ok := floatFromAny(feature[key]); ok {
				return value, true
			}
		}
	}
	return 0, false
}

func firstGenericSensorValue(feature map[string]any) (float64, string, bool) {
	keys := sortedFeatureKeys(feature)
	for _, key := range keys {
		if strings.HasSuffix(key, "_alarm") {
			continue
		}
		value, ok := floatFromAny(feature[key])
		if !ok {
			continue
		}
		unit := inferSensorUnit(key)
		return value, unit, true
	}
	return 0, "", false
}

func inferSensorUnit(key string) string {
	switch {
	case strings.Contains(key, "humidity"):
		return "%"
	case strings.Contains(key, "intrusion"):
		return ""
	default:
		return ""
	}
}

func sortedFeatureKeys(feature map[string]any) []string {
	keys := make([]string, 0, len(feature))
	for key := range feature {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func floatFromAny(raw any) (float64, bool) {
	switch value := raw.(type) {
	case float64:
		return value, true
	case float32:
		return float64(value), true
	case int:
		return float64(value), true
	case int64:
		return float64(value), true
	case json.Number:
		if f, err := value.Float64(); err == nil {
			return f, true
		}
	case string:
		if value == "" {
			return 0, false
		}
		if f, err := strconv.ParseFloat(value, 64); err == nil {
			return f, true
		}
	}
	return 0, false
}
