using HanaTui.System;

namespace HanaTui.System;

/// <summary>
/// A snapshot of all system metrics at a point in time.
/// </summary>
public sealed class SystemSnapshot
{
    // CPU: index 0 = total ("cpu"), 1+ = per-core ("cpu0", "cpu1", ...)
    public double[] CpuPercents { get; init; } = [];
    public string[] CpuLabels { get; init; } = [];

    public long MemTotalKb { get; init; }
    public long MemAvailableKb { get; init; }
    public long MemUsedKb => MemTotalKb - MemAvailableKb;

    public long SwapTotalKb { get; init; }
    public long SwapFreeKb { get; init; }
    public long SwapUsedKb => SwapTotalKb - SwapFreeKb;

    public double MemUsedPercent =>
        MemTotalKb > 0 ? MemUsedKb * 100.0 / MemTotalKb : 0;

    public double SwapUsedPercent =>
        SwapTotalKb > 0 ? SwapUsedKb * 100.0 / SwapTotalKb : 0;

    public static string FormatGb(long kb) =>
        $"{kb / 1024.0 / 1024.0:F1} GB";
}

/// <summary>
/// Polls /proc/stat and /proc/meminfo on a background timer.
/// Thread-safe snapshot available via CurrentSnapshot.
/// </summary>
public sealed class SystemStats : IDisposable
{
    private const int RefreshMs = 800;

    private List<CpuSample> _prevSamples = [];
    private SystemSnapshot _snapshot = new();
    private readonly object _lock = new();
    private readonly Timer _timer;
    private bool _disposed;

    public SystemStats()
    {
        // Take initial sample immediately (no % yet, need two samples)
        _prevSamples = ReadCpuSamples();
        _timer = new Timer(Tick, null, RefreshMs, RefreshMs);
    }

    /// <summary>
    /// Returns the most recent system snapshot. Safe to call from any thread.
    /// </summary>
    public SystemSnapshot CurrentSnapshot
    {
        get { lock (_lock) { return _snapshot; } }
    }

    private void Tick(object? _)
    {
        try
        {
            var currSamples = ReadCpuSamples();
            var mem = ReadMemInfo();

            // Compute CPU % by diffing prev vs curr
            var percents = new double[currSamples.Count];
            var labels = new string[currSamples.Count];

            for (int i = 0; i < currSamples.Count; i++)
            {
                labels[i] = currSamples[i].Label;
                // Find matching prev sample by label
                var prev = _prevSamples.Find(s => s.Label == currSamples[i].Label);
                percents[i] = CpuSample.ComputePercent(prev, currSamples[i]);
            }

            var snap = new SystemSnapshot
            {
                CpuPercents = percents,
                CpuLabels = labels,
                MemTotalKb = mem.MemTotal,
                MemAvailableKb = mem.MemAvailable,
                SwapTotalKb = mem.SwapTotal,
                SwapFreeKb = mem.SwapFree,
            };

            lock (_lock)
            {
                _prevSamples = currSamples;
                _snapshot = snap;
            }
        }
        catch
        {
            // Silently swallow errors in background stats thread
        }
    }

    private static List<CpuSample> ReadCpuSamples()
    {
        try
        {
            var content = File.ReadAllText("/proc/stat");
            return CpuSample.ParseProcStat(content);
        }
        catch
        {
            return [];
        }
    }

    private static (long MemTotal, long MemAvailable, long SwapTotal, long SwapFree) ReadMemInfo()
    {
        long memTotal = 0, memAvailable = 0, swapTotal = 0, swapFree = 0;
        try
        {
            foreach (var line in File.ReadLines("/proc/meminfo"))
            {
                if (line.StartsWith("MemTotal:", StringComparison.Ordinal))
                    memTotal = ParseKb(line);
                else if (line.StartsWith("MemAvailable:", StringComparison.Ordinal))
                    memAvailable = ParseKb(line);
                else if (line.StartsWith("SwapTotal:", StringComparison.Ordinal))
                    swapTotal = ParseKb(line);
                else if (line.StartsWith("SwapFree:", StringComparison.Ordinal))
                    swapFree = ParseKb(line);
            }
        }
        catch { /* no /proc/meminfo — probably not Linux */ }

        return (memTotal, memAvailable, swapTotal, swapFree);
    }

    private static long ParseKb(string line)
    {
        // Format: "MemTotal:       32768 kB"
        var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
        return parts.Length >= 2 && long.TryParse(parts[1], out var v) ? v : 0;
    }

    public void Dispose()
    {
        if (_disposed) return;
        _disposed = true;
        _timer.Dispose();
    }
}
