using System.IO;
using System.Windows.Threading;
using TeamsISO.App.ViewModels;
using TeamsISO.Engine.Controller;

namespace TeamsISO.App.Services;

/// <summary>
/// Polls the recording drive's free space every few seconds while recording is
/// on. Surfaces a coral warning toast at the soft threshold, and at the hard
/// threshold auto-disables recording so a long unattended show doesn't fill
/// the disk and crash the host's logger and write paths.
///
/// Lifecycle is tied to the MainViewModel — instantiate after the VM is wired,
/// dispose with the host.
/// </summary>
public sealed class DiskSpaceWatcher : IDisposable
{
    /// <summary>Below this, toast a warning each tick.</summary>
    public static readonly long SoftWarnBytes = 10L * 1024 * 1024 * 1024; // 10 GB

    /// <summary>Below this, auto-disable recording to save the show.</summary>
    public static readonly long HardStopBytes = 1L * 1024 * 1024 * 1024;  // 1 GB

    private readonly IIsoController _controller;
    private readonly ToastViewModel _toast;
    private readonly DispatcherTimer _timer;
    private DateTimeOffset _lastWarnAt = DateTimeOffset.MinValue;

    public DiskSpaceWatcher(IIsoController controller, ToastViewModel toast, Dispatcher dispatcher)
    {
        _controller = controller;
        _toast = toast;
        _timer = new DispatcherTimer(DispatcherPriority.Background, dispatcher)
        {
            Interval = TimeSpan.FromSeconds(5),
        };
        _timer.Tick += OnTick;
        _timer.Start();
    }

    private void OnTick(object? sender, EventArgs e)
    {
        if (!_controller.RecordingEnabled) return;
        var dir = _controller.RecordingDirectory;
        if (string.IsNullOrEmpty(dir)) return;

        long freeBytes;
        try
        {
            // DriveInfo wants a drive letter / mount root. Walk up the path
            // until we hit a directory that exists (the recording dir might
            // not have been created yet by the first ISO).
            var probe = dir;
            while (!Directory.Exists(probe))
            {
                var parent = Path.GetDirectoryName(probe);
                if (string.IsNullOrEmpty(parent) || parent == probe) break;
                probe = parent;
            }
            if (!Directory.Exists(probe)) return;
            var drive = new DriveInfo(Path.GetPathRoot(probe) ?? probe);
            freeBytes = drive.AvailableFreeSpace;
        }
        catch
        {
            // If the drive query fails (network share dropped, weird path),
            // skip this tick rather than spam the toast with errors.
            return;
        }

        if (freeBytes < HardStopBytes)
        {
            _controller.SetRecording(false, dir);
            _toast.Warn($"Recording AUTO-STOPPED — drive has only {FormatBytes(freeBytes)} free");
            _lastWarnAt = DateTimeOffset.UtcNow;
        }
        else if (freeBytes < SoftWarnBytes)
        {
            // Throttle the soft warning so we don't toast every 5s for the
            // last hour of disk space.
            if (DateTimeOffset.UtcNow - _lastWarnAt < TimeSpan.FromMinutes(2)) return;
            _toast.Warn($"Recording drive has {FormatBytes(freeBytes)} free — winding down soon");
            _lastWarnAt = DateTimeOffset.UtcNow;
        }
    }

    private static string FormatBytes(long bytes)
    {
        if (bytes >= 1L << 30) return $"{bytes / (double)(1L << 30):F1} GB";
        if (bytes >= 1L << 20) return $"{bytes / (double)(1L << 20):F0} MB";
        return $"{bytes} bytes";
    }

    public void Dispose()
    {
        _timer.Stop();
        _timer.Tick -= OnTick;
    }
}
