using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Resilience;

namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Resilience;

[Trait("Category", "Unit")]
public sealed class InFlightCounterTests
{
    /// <summary>Verifies that starting and completing a call nets to zero.</summary>
    [Fact]
    public void StartThenComplete_NetsToZero()
    {
        var tracker = new DriverResilienceStatusTracker();
        tracker.RecordCallStart("drv", "host-a");
        tracker.RecordCallComplete("drv", "host-a");

        tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(0);
    }

    /// <summary>Verifies that nested starts sum the depth.</summary>
    [Fact]
    public void NestedStarts_SumDepth()
    {
        var tracker = new DriverResilienceStatusTracker();
        tracker.RecordCallStart("drv", "host-a");
        tracker.RecordCallStart("drv", "host-a");
        tracker.RecordCallStart("drv", "host-a");

        tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(3);

        tracker.RecordCallComplete("drv", "host-a");
        tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(2);
    }

    /// <summary>Verifies that completing before start is clamped to zero.</summary>
    [Fact]
    public void CompleteBeforeStart_ClampedToZero()
    {
        var tracker = new DriverResilienceStatusTracker();
        tracker.RecordCallComplete("drv", "host-a");

        // A stray Complete without a matching Start shouldn't drive the counter negative.
        tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(0);
    }

    /// <summary>Verifies that different hosts track independently.</summary>
    [Fact]
    public void DifferentHosts_TrackIndependently()
    {
        var tracker = new DriverResilienceStatusTracker();
        tracker.RecordCallStart("drv", "host-a");
        tracker.RecordCallStart("drv", "host-a");
        tracker.RecordCallStart("drv", "host-b");

        tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(2);
        tracker.TryGet("drv", "host-b")!.CurrentInFlight.ShouldBe(1);
    }

    /// <summary>Verifies that concurrent starts do not lose count.</summary>
    [Fact]
    public void ConcurrentStarts_DoNotLose_Count()
    {
        var tracker = new DriverResilienceStatusTracker();
        Parallel.For(0, 500, _ => tracker.RecordCallStart("drv", "host-a"));

        tracker.TryGet("drv", "host-a")!.CurrentInFlight.ShouldBe(500);
    }

    /// <summary>Verifies that CapabilityInvoker increments the tracker during execution.</summary>
    [Fact]
    public async Task CapabilityInvoker_IncrementsTracker_DuringExecution()
    {
        var tracker = new DriverResilienceStatusTracker();
        var invoker = new CapabilityInvoker(
            new DriverResiliencePipelineBuilder(),
            "drv-live",
            () => new DriverResilienceOptions { Tier = DriverTier.A },
            driverType: "Modbus",
            statusTracker: tracker);

        var observedMidCall = -1;
        await invoker.ExecuteAsync(
            DriverCapability.Read,
            "plc-1",
            async _ =>
            {
                observedMidCall = tracker.TryGet("drv-live", "plc-1")?.CurrentInFlight ?? -1;
                await Task.Yield();
                return 42;
            },
            CancellationToken.None);

        observedMidCall.ShouldBe(1, "during call, in-flight == 1");
        tracker.TryGet("drv-live", "plc-1")!.CurrentInFlight.ShouldBe(0, "post-call, counter decremented");
    }

    /// <summary>Verifies that CapabilityInvoker decrements the counter on exception.</summary>
    [Fact]
    public async Task CapabilityInvoker_ExceptionPath_DecrementsCounter()
    {
        var tracker = new DriverResilienceStatusTracker();
        var invoker = new CapabilityInvoker(
            new DriverResiliencePipelineBuilder(),
            "drv-live",
            () => new DriverResilienceOptions { Tier = DriverTier.A },
            statusTracker: tracker);

        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await invoker.ExecuteAsync<int>(
                DriverCapability.Write,
                "plc-1",
                _ => throw new InvalidOperationException("boom"),
                CancellationToken.None));

        tracker.TryGet("drv-live", "plc-1")!.CurrentInFlight.ShouldBe(0,
            "finally-block must decrement even when call-site throws");
    }

    /// <summary>Verifies that CapabilityInvoker without a tracker does not throw.</summary>
    [Fact]
    public async Task CapabilityInvoker_WithoutTracker_DoesNotThrow()
    {
        var invoker = new CapabilityInvoker(
            new DriverResiliencePipelineBuilder(),
            "drv-live",
            () => new DriverResilienceOptions { Tier = DriverTier.A },
            statusTracker: null);

        var result = await invoker.ExecuteAsync(
            DriverCapability.Read, "host-1",
            _ => ValueTask.FromResult(7),
            CancellationToken.None);

        result.ShouldBe(7);
    }
}
