package main

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/shirou/gopsutil/v3/host"
)

var (
	// Package-level cache for heavy system_profiler data
	lastPowerAt   time.Time
	cachedPower   string
	powerCacheTTL = 30 * time.Second
)

func collectBatteries() (batts []BatteryStatus, err error) {
	defer func() {
		if r := recover(); r != nil {
			// Swallow panics from platform-specific battery probes to keep the UI alive.
			err = fmt.Errorf("battery collection failed: %v", r)
		}
	}()

	// macOS: pmset (fast, for real-time percentage/status)
	if runtime.GOOS == "darwin" && commandExists("pmset") {
		if out, err := runCmd(context.Background(), "pmset", "-g", "batt"); err == nil {
			// Get heavy info (health, cycles) from cached system_profiler
			health, cycles := getCachedPowerData()
			if batts := parsePMSet(out, health, cycles); len(batts) > 0 {
				return batts, nil
			}
		}
	}

	// Linux: /sys/class/power_supply
	matches, _ := filepath.Glob("/sys/class/power_supply/BAT*/capacity")
	for _, capFile := range matches {
		statusFile := filepath.Join(filepath.Dir(capFile), "status")
		capData, err := os.ReadFile(capFile)
		if err != nil {
			continue
		}
		statusData, _ := os.ReadFile(statusFile)
		percentStr := strings.TrimSpace(string(capData))
		percent, _ := strconv.ParseFloat(percentStr, 64)
		status := strings.TrimSpace(string(statusData))
		if status == "" {
			status = "Unknown"
		}
		batts = append(batts, BatteryStatus{
			Percent: percent,
			Status:  status,
		})
	}
	if len(batts) > 0 {
		return batts, nil
	}

	return nil, errors.New("no battery data found")
}

func parsePMSet(raw string, health string, cycles int) []BatteryStatus {
	lines := strings.Split(raw, "\n")
	var out []BatteryStatus
	var timeLeft string

	for _, line := range lines {
		// Check for time remaining
		if strings.Contains(line, "remaining") {
			// Extract time like "1:30 remaining"
			parts := strings.Fields(line)
			for i, p := range parts {
				if p == "remaining" && i > 0 {
					timeLeft = parts[i-1]
				}
			}
		}

		if !strings.Contains(line, "%") {
			continue
		}
		fields := strings.Fields(line)
		var (
			percent float64
			found   bool
			status  = "Unknown"
		)
		for i, f := range fields {
			if strings.Contains(f, "%") {
				value := strings.TrimSuffix(strings.TrimSuffix(f, ";"), "%")
				if p, err := strconv.ParseFloat(value, 64); err == nil {
					percent = p
					found = true
					if i+1 < len(fields) {
						status = strings.TrimSuffix(fields[i+1], ";")
					}
				}
				break
			}
		}
		if !found {
			continue
		}

		out = append(out, BatteryStatus{
			Percent:    percent,
			Status:     status,
			TimeLeft:   timeLeft,
			Health:     health,
			CycleCount: cycles,
		})
	}
	return out
}

// getCachedPowerData returns condition, cycles, and fan speed from cached system_profiler output.
func getCachedPowerData() (health string, cycles int) {
	out := getSystemPowerOutput()
	if out == "" {
		return "", 0
	}

	lines := strings.Split(out, "\n")
	for _, line := range lines {
		lower := strings.ToLower(line)
		if strings.Contains(lower, "cycle count") {
			if _, after, found := strings.Cut(line, ":"); found {
				cycles, _ = strconv.Atoi(strings.TrimSpace(after))
			}
		}
		if strings.Contains(lower, "condition") {
			if _, after, found := strings.Cut(line, ":"); found {
				health = strings.TrimSpace(after)
			}
		}
	}
	return health, cycles
}

func getSystemPowerOutput() string {
	if runtime.GOOS != "darwin" {
		return ""
	}

	now := time.Now()
	if cachedPower != "" && now.Sub(lastPowerAt) < powerCacheTTL {
		return cachedPower
	}

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	out, err := runCmd(ctx, "system_profiler", "SPPowerDataType")
	if err == nil {
		cachedPower = out
		lastPowerAt = now
	}
	return cachedPower
}

func collectThermal() ThermalStatus {
	if runtime.GOOS != "darwin" {
		return ThermalStatus{}
	}

	var thermal ThermalStatus

	// Get fan info and adapter power from cached system_profiler
	out := getSystemPowerOutput()
	if out != "" {
		lines := strings.Split(out, "\n")
		for _, line := range lines {
			lower := strings.ToLower(line)
			if strings.Contains(lower, "fan") && strings.Contains(lower, "speed") {
				if _, after, found := strings.Cut(line, ":"); found {
					// Extract number from string like "1200 RPM"
					numStr := strings.TrimSpace(after)
					numStr, _, _ = strings.Cut(numStr, " ")
					thermal.FanSpeed, _ = strconv.Atoi(numStr)
				}
			}
		}
	}

	// Get power metrics from ioreg (fast, real-time data)
	ctxPower, cancelPower := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancelPower()
	if out, err := runCmd(ctxPower, "ioreg", "-rn", "AppleSmartBattery"); err == nil {
		lines := strings.Split(out, "\n")
		for _, line := range lines {
			line = strings.TrimSpace(line)

			// Get battery temperature
			// Matches: "Temperature" = 3055 (note: space before =)
			if _, after, found := strings.Cut(line, "\"Temperature\" = "); found {
				valStr := strings.TrimSpace(after)
				if tempRaw, err := strconv.Atoi(valStr); err == nil && tempRaw > 0 {
					thermal.CPUTemp = float64(tempRaw) / 100.0
				}
			}

			// Get adapter power (Watts)
			// Read from current adapter: "AdapterDetails" = {"Watts"=140...}
			// Skip historical data: "AppleRawAdapterDetails" = ({Watts=90}, {Watts=140})
			if strings.Contains(line, "\"AdapterDetails\" = {") && !strings.Contains(line, "AppleRaw") {
				if _, after, found := strings.Cut(line, "\"Watts\"="); found {
					valStr := strings.TrimSpace(after)
					// Remove trailing characters like , or }
					valStr, _, _ = strings.Cut(valStr, ",")
					valStr, _, _ = strings.Cut(valStr, "}")
					valStr = strings.TrimSpace(valStr)
					if watts, err := strconv.ParseFloat(valStr, 64); err == nil && watts > 0 {
						thermal.AdapterPower = watts
					}
				}
			}

			// Get system power consumption (mW -> W)
			// Matches: "SystemPowerIn"=12345
			if _, after, found := strings.Cut(line, "\"SystemPowerIn\"="); found {
				valStr := strings.TrimSpace(after)
				valStr, _, _ = strings.Cut(valStr, ",")
				valStr, _, _ = strings.Cut(valStr, "}")
				valStr = strings.TrimSpace(valStr)
				if powerMW, err := strconv.ParseFloat(valStr, 64); err == nil && powerMW > 0 {
					thermal.SystemPower = powerMW / 1000.0
				}
			}

			// Get battery power (mW -> W, positive = discharging)
			// Matches: "BatteryPower"=12345
			if _, after, found := strings.Cut(line, "\"BatteryPower\"="); found {
				valStr := strings.TrimSpace(after)
				valStr, _, _ = strings.Cut(valStr, ",")
				valStr, _, _ = strings.Cut(valStr, "}")
				valStr = strings.TrimSpace(valStr)
				if powerMW, err := strconv.ParseFloat(valStr, 64); err == nil {
					thermal.BatteryPower = powerMW / 1000.0
				}
			}
		}
	}

	// Fallback: Try thermal level as a proxy if temperature not found
	if thermal.CPUTemp == 0 {
		ctx2, cancel2 := context.WithTimeout(context.Background(), 500*time.Millisecond)
		defer cancel2()
		out2, err := runCmd(ctx2, "sysctl", "-n", "machdep.xcpm.cpu_thermal_level")
		if err == nil {
			level, _ := strconv.Atoi(strings.TrimSpace(out2))
			// Estimate temp: level 0-100 roughly maps to 40-100°C
			if level >= 0 {
				thermal.CPUTemp = 45 + float64(level)*0.5
			}
		}
	}

	return thermal
}

func collectSensors() ([]SensorReading, error) {
	temps, err := host.SensorsTemperatures()
	if err != nil {
		return nil, err
	}
	var out []SensorReading
	for _, t := range temps {
		if t.Temperature <= 0 || t.Temperature > 150 {
			continue
		}
		out = append(out, SensorReading{
			Label: prettifyLabel(t.SensorKey),
			Value: t.Temperature,
			Unit:  "°C",
		})
	}
	return out, nil
}

func prettifyLabel(key string) string {
	key = strings.TrimSpace(key)
	key = strings.TrimPrefix(key, "TC")
	key = strings.ReplaceAll(key, "_", " ")
	return key
}
