using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Health;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Health;

/// <summary>
///     Tests for <see cref="HostStatusAggregator"/> — the merge + diff logic for the
///     transport entry plus per-platform probe entries that
///     <c>IHostConnectivityProbe.GetHostStatuses()</c> surfaces.
/// </summary>
public sealed class HostStatusAggregatorTests
{
    private static HostConnectivityStatus Status(string host, HostState state) =>
        new(host, state, DateTime.UtcNow);

    /// <summary>Verifies that snapshot is empty when nothing is tracked.</summary>
    [Fact]
    public void Snapshot_Empty_WhenNothingTracked()
    {
        var agg = new HostStatusAggregator();
        agg.Snapshot().ShouldBeEmpty();
    }

    /// <summary>Verifies that updating a new host fires a change event with Unknown as previous state.</summary>
    [Fact]
    public void Update_NewHost_FiresChange_PreviousIsUnknown()
    {
        var agg = new HostStatusAggregator();
        var captured = new List<HostStatusChangedEventArgs>();
        agg.OnHostStatusChanged += (_, e) => captured.Add(e);

        agg.Update(Status("PlatformA", HostState.Running));

        captured.Count.ShouldBe(1);
        captured[0].HostName.ShouldBe("PlatformA");
        captured[0].OldState.ShouldBe(HostState.Unknown);
        captured[0].NewState.ShouldBe(HostState.Running);
    }

    /// <summary>Verifies that updating to the same state does not fire a change event.</summary>
    [Fact]
    public void Update_SameState_DoesNotFire()
    {
        var agg = new HostStatusAggregator();
        agg.Update(Status("PlatformA", HostState.Running));

        var captured = new List<HostStatusChangedEventArgs>();
        agg.OnHostStatusChanged += (_, e) => captured.Add(e);

        agg.Update(Status("PlatformA", HostState.Running));

        captured.ShouldBeEmpty();
    }

    /// <summary>Verifies that state transitions fire change events with correct old and new states.</summary>
    [Fact]
    public void Update_StateTransition_FiresChangeWithCorrectPreviousAndNew()
    {
        var agg = new HostStatusAggregator();
        agg.Update(Status("PlatformA", HostState.Running));

        var captured = new List<HostStatusChangedEventArgs>();
        agg.OnHostStatusChanged += (_, e) => captured.Add(e);

        agg.Update(Status("PlatformA", HostState.Stopped));

        captured.Count.ShouldBe(1);
        captured[0].OldState.ShouldBe(HostState.Running);
        captured[0].NewState.ShouldBe(HostState.Stopped);
    }

    /// <summary>Verifies that snapshot reflects every upserted host.</summary>
    [Fact]
    public void Snapshot_ReflectsEveryUpsertedHost()
    {
        var agg = new HostStatusAggregator();
        agg.Update(Status("Transport", HostState.Running));
        agg.Update(Status("PlatformA", HostState.Running));
        agg.Update(Status("PlatformB", HostState.Stopped));

        var snap = agg.Snapshot();

        snap.Count.ShouldBe(3);
        snap.Select(s => s.HostName).OrderBy(x => x).ShouldBe(new[] { "PlatformA", "PlatformB", "Transport" });
        snap.First(s => s.HostName == "PlatformB").State.ShouldBe(HostState.Stopped);
    }

    /// <summary>Verifies that host name comparison is case-insensitive.</summary>
    [Fact]
    public void Update_HostNameComparison_IsCaseInsensitive()
    {
        var agg = new HostStatusAggregator();
        var captured = new List<HostStatusChangedEventArgs>();
        agg.OnHostStatusChanged += (_, e) => captured.Add(e);

        agg.Update(Status("PlatformA", HostState.Running));
        agg.Update(Status("platforma", HostState.Stopped)); // same host, different case

        captured.Count.ShouldBe(2);
        captured[1].OldState.ShouldBe(HostState.Running);
        captured[1].NewState.ShouldBe(HostState.Stopped);
        agg.Snapshot().Count.ShouldBe(1);
    }

    /// <summary>Verifies that removing a tracked host returns true and drops it from snapshot.</summary>
    [Fact]
    public void Remove_TrackedHost_ReturnsTrue_AndDropsFromSnapshot()
    {
        var agg = new HostStatusAggregator();
        agg.Update(Status("PlatformA", HostState.Running));
        agg.Remove("PlatformA").ShouldBeTrue();
        agg.Snapshot().ShouldBeEmpty();
    }

    /// <summary>Verifies that removing an unknown host returns false.</summary>
    [Fact]
    public void Remove_UnknownHost_ReturnsFalse()
    {
        var agg = new HostStatusAggregator();
        agg.Remove("Nope").ShouldBeFalse();
    }

    /// <summary>Verifies that concurrent updates do not corrupt the internal dictionary.</summary>
    [Fact]
    public void ConcurrentUpdates_DoNotCorruptDictionary()
    {
        var agg = new HostStatusAggregator();
        const int threadCount = 8;
        const int updatesPerThread = 250;

        var tasks = Enumerable.Range(0, threadCount).Select(t => Task.Run(() =>
        {
            for (var i = 0; i < updatesPerThread; i++)
            {
                var hostName = $"Host{(t * updatesPerThread + i) % 32}";
                var state = i % 2 == 0 ? HostState.Running : HostState.Stopped;
                agg.Update(Status(hostName, state));
            }
        })).ToArray();

        Task.WaitAll(tasks);
        agg.Snapshot().Count.ShouldBeLessThanOrEqualTo(32);
    }
}
