using Mbproxy.Proxy;
using Microsoft.AspNetCore.SignalR;

namespace Mbproxy.Admin;

/// <summary>
/// SignalR hub backing the live admin dashboard. Two subscription scopes:
/// <list type="bullet">
///   <item><see cref="SubscribeFleet"/> — the fleet dashboard (<c>GET /</c>) joins the
///         <see cref="FleetGroup"/> group and receives a <c>"fleet"</c> message every
///         push tick.</item>
///   <item><see cref="SubscribePlc"/> — a connection-detail page (<c>GET /plc/{name}</c>)
///         joins <see cref="PlcGroup"/> and receives a <c>"plc"</c> message. The first
///         subscriber to a PLC arms that PLC's tag-value capture; the last to leave
///         disarms it (on-demand capture).</item>
/// </list>
///
/// <para>The hub itself is transient (one instance per call). Cross-call state — the
/// subscriber counts that drive capture arming — lives in the singleton
/// <see cref="PlcSubscriptionTracker"/>. The actual pushes are issued by
/// <see cref="StatusBroadcaster"/>, not the hub.</para>
/// </summary>
internal sealed class StatusHub : Hub
{
    /// <summary>SignalR group name for fleet-dashboard subscribers.</summary>
    public const string FleetGroup = "fleet";

    /// <summary>SignalR group name for a single PLC's detail-page subscribers.</summary>
    public static string PlcGroup(string plcName) => "plc:" + plcName;

    private readonly PlcSubscriptionTracker _tracker;
    private readonly TagCaptureRegistry _captureRegistry;

    public StatusHub(PlcSubscriptionTracker tracker, TagCaptureRegistry captureRegistry)
    {
        _tracker         = tracker;
        _captureRegistry = captureRegistry;
    }

    /// <summary>Subscribes the calling connection to fleet-wide status pushes.</summary>
    public Task SubscribeFleet()
        => Groups.AddToGroupAsync(Context.ConnectionId, FleetGroup);

    /// <summary>
    /// Subscribes the calling connection to one PLC's detail pushes and arms that PLC's
    /// tag-value capture if this is its first viewer.
    /// </summary>
    public async Task SubscribePlc(string plcName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, PlcGroup(plcName)).ConfigureAwait(false);

        if (_tracker.Add(Context.ConnectionId, plcName))
            _captureRegistry.Arm(plcName); // no-op for an unknown PLC name
    }

    /// <summary>
    /// On disconnect, drops every subscription the connection held and disarms the
    /// capture of any PLC whose last viewer just left.
    /// </summary>
    public override Task OnDisconnectedAsync(Exception? exception)
    {
        foreach (var plcName in _tracker.RemoveConnection(Context.ConnectionId))
            _captureRegistry.Disarm(plcName);

        return base.OnDisconnectedAsync(exception);
    }
}
