using System.Diagnostics;
using System.Globalization;
using System.Management;
using System.Runtime.InteropServices;
using NexusShell.Models;

namespace NexusShell.Services;

public sealed class NexusCpuService
{
    private static readonly CultureInfo DaDk = CultureInfo.GetCultureInfo("da-DK");

    private ulong _prevIdleTicks;
    private ulong _prevKernelTicks;
    private ulong _prevUserTicks;
    private bool _hasBaseline;
    private int _lastLoadPercent;

    public NexusCpuSnapshot Capture()
    {
        var wmi = ReadWmiProcessor();
        return new NexusCpuSnapshot
        {
            Model = wmi.Model,
            Cores = wmi.Cores,
            Threads = wmi.Threads,
            LoadPercent = SampleLoadPercent(),
            FrequencyLabel = wmi.FrequencyMhz > 0
                ? $"{wmi.FrequencyMhz.ToString("0", DaDk)} MHz"
                : "—",
            TemperatureLabel = TryReadTemperature(),
        };
    }

    public int SampleLoadPercent()
    {
        if (!GetSystemTimes(out var idle, out var kernel, out var user))
        {
            return _lastLoadPercent;
        }

        var idleTicks = FileTimeToUInt64(idle);
        var kernelTicks = FileTimeToUInt64(kernel);
        var userTicks = FileTimeToUInt64(user);

        if (!_hasBaseline)
        {
            _prevIdleTicks = idleTicks;
            _prevKernelTicks = kernelTicks;
            _prevUserTicks = userTicks;
            _hasBaseline = true;
            return _lastLoadPercent;
        }

        var idleDelta = idleTicks - _prevIdleTicks;
        var totalDelta = (kernelTicks + userTicks) - (_prevKernelTicks + _prevUserTicks);
        _prevIdleTicks = idleTicks;
        _prevKernelTicks = kernelTicks;
        _prevUserTicks = userTicks;

        if (totalDelta == 0)
        {
            return _lastLoadPercent;
        }

        var used = totalDelta - idleDelta;
        _lastLoadPercent = (int)Math.Clamp(used * 100d / totalDelta, 0, 100);
        return _lastLoadPercent;
    }

    private static (string Model, int Cores, int Threads, int FrequencyMhz) ReadWmiProcessor()
    {
        try
        {
            using var searcher = new ManagementObjectSearcher(
                "SELECT Name, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed FROM Win32_Processor");
            foreach (ManagementObject obj in searcher.Get())
            {
                var name = obj["Name"]?.ToString()?.Trim();
                var cores = Convert.ToInt32(obj["NumberOfCores"] ?? 0);
                var threads = Convert.ToInt32(obj["NumberOfLogicalProcessors"] ?? 0);
                var mhz = Convert.ToInt32(obj["MaxClockSpeed"] ?? 0);
                return (
                    string.IsNullOrWhiteSpace(name) ? "Ukendt processor" : name,
                    cores,
                    threads,
                    mhz);
            }
        }
        catch
        {
            // WMI unavailable — fall through
        }

        return ("Ukendt processor", 0, 0, 0);
    }

    private static string? TryReadTemperature()
    {
        try
        {
            using var searcher = new ManagementObjectSearcher(
                "root\\WMI",
                "SELECT CurrentTemperature FROM MSAcpi_ThermalZoneTemperature");
            foreach (ManagementObject obj in searcher.Get())
            {
                if (obj["CurrentTemperature"] is null)
                {
                    continue;
                }

                var kelvinTenth = Convert.ToDouble(obj["CurrentTemperature"]);
                var celsius = (kelvinTenth / 10d) - 273.15d;
                return $"{celsius.ToString("0.#", DaDk)} °C";
            }
        }
        catch
        {
            // Temperature not available on many systems
        }

        return null;
    }

    private static ulong FileTimeToUInt64(FileTime time) =>
        ((ulong)time.dwHighDateTime << 32) | time.dwLowDateTime;

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool GetSystemTimes(out FileTime idleTime, out FileTime kernelTime, out FileTime userTime);

    [StructLayout(LayoutKind.Sequential)]
    private struct FileTime
    {
        public uint dwLowDateTime;
        public uint dwHighDateTime;
    }
}
