using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace YMhut.Box.Core.System;

public sealed record SystemMetricsSnapshot(
    string MachineName,
    string OsDescription,
    int ProcessorCount,
    long WorkingSetBytes,
    long ManagedMemoryBytes,
    double? CpuUsagePercent,
    ulong? TotalMemoryBytes,
    ulong? AvailableMemoryBytes,
    ulong? UsedMemoryBytes,
    double? MemoryUsagePercent,
    double? ProcessCpuUsagePercent,
    TimeSpan ProcessUptime,
    int ProcessThreadCount,
    int ProcessHandleCount,
    ulong? SystemDriveTotalBytes,
    ulong? SystemDriveAvailableBytes,
    ulong? SystemDriveUsedBytes,
    double? SystemDriveUsagePercent,
    bool Is64BitProcess,
    string RuntimeDescription,
    DateTimeOffset CapturedAt);

public interface ISystemMetricsService
{
    SystemMetricsSnapshot Capture();
}

public sealed class SystemMetricsService : ISystemMetricsService
{
    private readonly object _cpuLock = new();
    private CpuSample? _lastCpuSample;
    private readonly object _processCpuLock = new();
    private ProcessCpuSample? _lastProcessCpuSample;

    public SystemMetricsSnapshot Capture()
    {
        using var process = Process.GetCurrentProcess();
        var capturedAt = DateTimeOffset.Now;
        var cpuUsage = CaptureCpuUsage();
        var processCpuUsage = CaptureProcessCpuUsage(process, capturedAt);
        var memory = CaptureMemoryStatus();
        var drive = CaptureSystemDriveStatus();
        var processUptime = SafeProcessUptime(process, capturedAt);
        return new SystemMetricsSnapshot(
            Environment.MachineName,
            global::System.Runtime.InteropServices.RuntimeInformation.OSDescription,
            Environment.ProcessorCount,
            Environment.WorkingSet,
            GC.GetTotalMemory(false),
            cpuUsage,
            memory.Total,
            memory.Available,
            memory.Used,
            memory.UsagePercent,
            processCpuUsage,
            processUptime,
            SafeThreadCount(process),
            SafeHandleCount(process),
            drive.Total,
            drive.Available,
            drive.Used,
            drive.UsagePercent,
            Environment.Is64BitProcess,
            global::System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription,
            capturedAt);
    }

    private double? CaptureCpuUsage()
    {
        if (!GetSystemTimes(out var idleTime, out var kernelTime, out var userTime))
        {
            return null;
        }

        var sample = new CpuSample(ToUInt64(idleTime), ToUInt64(kernelTime), ToUInt64(userTime));
        lock (_cpuLock)
        {
            if (_lastCpuSample is not { } previous)
            {
                _lastCpuSample = sample;
                return null;
            }

            _lastCpuSample = sample;
            var idleDelta = sample.Idle - previous.Idle;
            var kernelDelta = sample.Kernel - previous.Kernel;
            var userDelta = sample.User - previous.User;
            var totalDelta = kernelDelta + userDelta;
            if (totalDelta == 0 || idleDelta > totalDelta)
            {
                return null;
            }

            return Math.Clamp((double)(totalDelta - idleDelta) / totalDelta * 100, 0, 100);
        }
    }

    private static MemoryStatus CaptureMemoryStatus()
    {
        var status = new MemoryStatusEx();
        status.dwLength = (uint)Marshal.SizeOf<MemoryStatusEx>();
        if (!GlobalMemoryStatusEx(ref status) || status.ullTotalPhys == 0)
        {
            return new MemoryStatus(null, null, null, null);
        }

        var total = status.ullTotalPhys;
        var available = Math.Min(status.ullAvailPhys, total);
        var used = total - available;
        var percent = Math.Clamp((double)used / total * 100, 0, 100);
        return new MemoryStatus(total, available, used, percent);
    }

    private double? CaptureProcessCpuUsage(Process process, DateTimeOffset capturedAt)
    {
        TimeSpan totalProcessorTime;
        try
        {
            totalProcessorTime = process.TotalProcessorTime;
        }
        catch
        {
            return null;
        }

        var sample = new ProcessCpuSample(totalProcessorTime, capturedAt);
        lock (_processCpuLock)
        {
            if (_lastProcessCpuSample is not { } previous)
            {
                _lastProcessCpuSample = sample;
                return null;
            }

            _lastProcessCpuSample = sample;
            var elapsed = sample.CapturedAt - previous.CapturedAt;
            var cpuDelta = sample.TotalProcessorTime - previous.TotalProcessorTime;
            if (elapsed.TotalMilliseconds <= 0 || cpuDelta.TotalMilliseconds < 0)
            {
                return null;
            }

            return Math.Clamp(cpuDelta.TotalMilliseconds / (elapsed.TotalMilliseconds * Math.Max(1, Environment.ProcessorCount)) * 100, 0, 100);
        }
    }

    private static DriveStatus CaptureSystemDriveStatus()
    {
        try
        {
            var root = Path.GetPathRoot(Environment.SystemDirectory);
            if (string.IsNullOrWhiteSpace(root))
            {
                return new DriveStatus(null, null, null, null);
            }

            var drive = new DriveInfo(root);
            if (!drive.IsReady || drive.TotalSize <= 0)
            {
                return new DriveStatus(null, null, null, null);
            }

            var total = (ulong)drive.TotalSize;
            var available = (ulong)Math.Clamp(drive.AvailableFreeSpace, 0, drive.TotalSize);
            var used = total - available;
            var percent = Math.Clamp((double)used / total * 100, 0, 100);
            return new DriveStatus(total, available, used, percent);
        }
        catch
        {
            return new DriveStatus(null, null, null, null);
        }
    }

    private static TimeSpan SafeProcessUptime(Process process, DateTimeOffset capturedAt)
    {
        try
        {
            return capturedAt - new DateTimeOffset(process.StartTime);
        }
        catch
        {
            return TimeSpan.Zero;
        }
    }

    private static int SafeThreadCount(Process process)
    {
        try
        {
            return process.Threads.Count;
        }
        catch
        {
            return 0;
        }
    }

    private static int SafeHandleCount(Process process)
    {
        try
        {
            return process.HandleCount;
        }
        catch
        {
            return 0;
        }
    }

    private static ulong ToUInt64(FileTime value)
    {
        return ((ulong)(uint)value.HighDateTime << 32) | (uint)value.LowDateTime;
    }

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

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool GlobalMemoryStatusEx(ref MemoryStatusEx buffer);

    [StructLayout(LayoutKind.Sequential)]
    private struct FileTime
    {
        public int LowDateTime;
        public int HighDateTime;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct MemoryStatusEx
    {
        public uint dwLength;
        public uint dwMemoryLoad;
        public ulong ullTotalPhys;
        public ulong ullAvailPhys;
        public ulong ullTotalPageFile;
        public ulong ullAvailPageFile;
        public ulong ullTotalVirtual;
        public ulong ullAvailVirtual;
        public ulong ullAvailExtendedVirtual;
    }

    private sealed record CpuSample(ulong Idle, ulong Kernel, ulong User);

    private sealed record MemoryStatus(ulong? Total, ulong? Available, ulong? Used, double? UsagePercent);

    private sealed record ProcessCpuSample(TimeSpan TotalProcessorTime, DateTimeOffset CapturedAt);

    private sealed record DriveStatus(ulong? Total, ulong? Available, ulong? Used, double? UsagePercent);
}
