using Mbproxy.Admin;
using Mbproxy.Bcd;
using Mbproxy.Proxy;
using Shouldly;
using Xunit;

namespace Mbproxy.Tests.Admin;

/// <summary>
/// Unit tests for <see cref="StatusHub"/> — group joins and on-demand capture
/// arming. Uses hand-written SignalR test doubles (see <see cref="SignalRFakes"/>);
/// no SignalR host is started.
/// </summary>
[Trait("Category", "Unit")]
public sealed class StatusHubTests
{
    private static StatusHub MakeHub(
        string connectionId,
        PlcSubscriptionTracker tracker,
        TagCaptureRegistry registry,
        out FakeGroupManager groups)
    {
        groups = new FakeGroupManager();
        return new StatusHub(tracker, registry)
        {
            Context = new FakeHubCallerContext(connectionId),
            Groups  = groups,
        };
    }

    [Fact]
    public async Task SubscribeFleet_JoinsFleetGroup()
    {
        var hub = MakeHub("conn-1", new PlcSubscriptionTracker(), new TagCaptureRegistry(), out var groups);

        await hub.SubscribeFleet();

        groups.Added.ShouldContain(("conn-1", StatusHub.FleetGroup));
    }

    [Fact]
    public async Task SubscribePlc_JoinsPlcGroup_AndArmsCapture()
    {
        var registry = new TagCaptureRegistry();
        registry.GetOrCreate("plc-1", BcdTagMap.Empty);
        var hub = MakeHub("conn-1", new PlcSubscriptionTracker(), registry, out var groups);

        await hub.SubscribePlc("plc-1");

        groups.Added.ShouldContain(("conn-1", StatusHub.PlcGroup("plc-1")));
        registry.TryGet("plc-1", out var capture).ShouldBeTrue();
        capture.IsArmed.ShouldBeTrue();
    }

    [Fact]
    public async Task SecondSubscriber_FirstLeaveKeepsArmed_LastLeaveDisarms()
    {
        var tracker  = new PlcSubscriptionTracker();
        var registry = new TagCaptureRegistry();
        registry.GetOrCreate("plc-1", BcdTagMap.Empty);

        var hub1 = MakeHub("conn-1", tracker, registry, out _);
        var hub2 = MakeHub("conn-2", tracker, registry, out _);

        await hub1.SubscribePlc("plc-1");
        await hub2.SubscribePlc("plc-1");

        registry.TryGet("plc-1", out var capture).ShouldBeTrue();
        capture.IsArmed.ShouldBeTrue();

        // First viewer leaves — a second viewer remains, so capture stays armed.
        await hub1.OnDisconnectedAsync(null);
        capture.IsArmed.ShouldBeTrue("capture must stay armed while another detail page is open");

        // Last viewer leaves — capture disarms.
        await hub2.OnDisconnectedAsync(null);
        capture.IsArmed.ShouldBeFalse("capture must disarm when the last viewer leaves");
    }

    [Fact]
    public async Task SubscribePlc_UnknownPlc_DoesNotThrow_AndArmsNothing()
    {
        var registry = new TagCaptureRegistry(); // no captures registered
        var hub = MakeHub("conn-1", new PlcSubscriptionTracker(), registry, out var groups);

        await Should.NotThrowAsync(async () => await hub.SubscribePlc("ghost"));

        groups.Added.ShouldContain(("conn-1", StatusHub.PlcGroup("ghost")));
        registry.TryGet("ghost", out _).ShouldBeFalse();
    }
}
