using System.Collections.Frozen;
using Mbproxy.Bcd;
using Mbproxy.Proxy;
using Shouldly;
using Xunit;

namespace Mbproxy.Tests.Proxy;

/// <summary>
/// Unit tests for <see cref="TagCaptureRegistry"/> — the shared seam that arms and
/// disarms per-PLC <see cref="TagValueCapture"/> instances.
/// </summary>
[Trait("Category", "Unit")]
public sealed class TagCaptureRegistryTests
{
    private static BcdTagMap Map(params (ushort addr, byte width)[] tags)
    {
        if (tags.Length == 0)
            return BcdTagMap.Empty;
        var frozen = tags
            .Select(t => BcdTag.Create(t.addr, t.width))
            .ToDictionary(t => t.Address)
            .ToFrozenDictionary();
        return new BcdTagMap(frozen);
    }

    [Fact]
    public void GetOrCreate_ReturnsSameInstance_OnRepeatCall_WhenTagSetUnchanged()
    {
        var registry = new TagCaptureRegistry();
        var first  = registry.GetOrCreate("plc-1", Map((100, 16)));
        var second = registry.GetOrCreate("plc-1", Map((100, 16)));

        // AddOrUpdate's update path rebuilds; both must be live and consistent.
        second.TagCount.ShouldBe(1);
        registry.TryGet("plc-1", out var current).ShouldBeTrue();
        current.ShouldBeSameAs(second);
    }

    [Fact]
    public void GetOrCreate_Rebuild_PreservesArmedFlag()
    {
        var registry = new TagCaptureRegistry();
        var capture = registry.GetOrCreate("plc-1", Map((100, 16)));
        capture.Arm();

        // Hot-reload reseat: same PLC, changed tag set.
        var rebuilt = registry.GetOrCreate("plc-1", Map((100, 16), (200, 32)));

        rebuilt.ShouldNotBeSameAs(capture);
        rebuilt.IsArmed.ShouldBeTrue("a rebuilt capture must keep capturing for an open detail page");
        rebuilt.TagCount.ShouldBe(2);
    }

    [Fact]
    public void Arm_And_Disarm_ReachTheRightCapture()
    {
        var registry = new TagCaptureRegistry();
        registry.GetOrCreate("plc-1", Map((100, 16)));
        registry.GetOrCreate("plc-2", Map((100, 16)));

        registry.Arm("plc-1");

        registry.TryGet("plc-1", out var c1).ShouldBeTrue();
        registry.TryGet("plc-2", out var c2).ShouldBeTrue();
        c1.IsArmed.ShouldBeTrue();
        c2.IsArmed.ShouldBeFalse();

        registry.Disarm("plc-1");
        c1.IsArmed.ShouldBeFalse();
    }

    [Fact]
    public void DisarmAll_DisarmsEveryCapture()
    {
        var registry = new TagCaptureRegistry();
        registry.GetOrCreate("plc-1", Map((100, 16)));
        registry.GetOrCreate("plc-2", Map((100, 16)));
        registry.Arm("plc-1");
        registry.Arm("plc-2");

        registry.DisarmAll();

        registry.TryGet("plc-1", out var c1).ShouldBeTrue();
        registry.TryGet("plc-2", out var c2).ShouldBeTrue();
        c1.IsArmed.ShouldBeFalse();
        c2.IsArmed.ShouldBeFalse();
    }

    [Fact]
    public void UnknownPlc_Operations_AreSafeNoOps()
    {
        var registry = new TagCaptureRegistry();

        Should.NotThrow(() => registry.Arm("ghost"));
        Should.NotThrow(() => registry.Disarm("ghost"));
        Should.NotThrow(() => registry.Remove("ghost"));
        registry.TryGet("ghost", out _).ShouldBeFalse();
    }

    [Fact]
    public void Remove_DropsTheCapture()
    {
        var registry = new TagCaptureRegistry();
        registry.GetOrCreate("plc-1", Map((100, 16)));
        registry.TryGet("plc-1", out _).ShouldBeTrue();

        registry.Remove("plc-1");

        registry.TryGet("plc-1", out _).ShouldBeFalse();
    }
}
