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 CapabilityInvokerTests
{
    private static CapabilityInvoker MakeInvoker(
        DriverResiliencePipelineBuilder builder,
        DriverResilienceOptions options) =>
        new(builder, "drv-test", () => options);

    [Fact]
    public async Task Read_ReturnsValue_FromCallSite()
    {
        var invoker = MakeInvoker(new DriverResiliencePipelineBuilder(), new DriverResilienceOptions { Tier = DriverTier.A });

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

        result.ShouldBe(42);
    }

    [Fact]
    public async Task Read_Retries_OnTransientFailure()
    {
        var invoker = MakeInvoker(new DriverResiliencePipelineBuilder(), new DriverResilienceOptions { Tier = DriverTier.A });
        var attempts = 0;

        var result = await invoker.ExecuteAsync(
            DriverCapability.Read,
            "host-1",
            async _ =>
            {
                attempts++;
                if (attempts < 2) throw new InvalidOperationException("transient");
                await Task.Yield();
                return "ok";
            },
            CancellationToken.None);

        result.ShouldBe("ok");
        attempts.ShouldBe(2);
    }

    [Fact]
    public async Task Write_NonIdempotent_DoesNotRetry_EvenWhenPolicyHasRetries()
    {
        var options = new DriverResilienceOptions
        {
            Tier = DriverTier.A,
            CapabilityPolicies = new Dictionary<DriverCapability, CapabilityPolicy>
            {
                [DriverCapability.Write] = new(TimeoutSeconds: 2, RetryCount: 3, BreakerFailureThreshold: 5),
            },
        };
        var invoker = MakeInvoker(new DriverResiliencePipelineBuilder(), options);
        var attempts = 0;

        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await invoker.ExecuteWriteAsync(
                "host-1",
                isIdempotent: false,
                async _ =>
                {
                    attempts++;
                    await Task.Yield();
                    throw new InvalidOperationException("boom");
#pragma warning disable CS0162
                    return 0;
#pragma warning restore CS0162
                },
                CancellationToken.None));

        attempts.ShouldBe(1, "non-idempotent write must never replay");
    }

    [Fact]
    public async Task Write_Idempotent_Retries_WhenPolicyHasRetries()
    {
        var options = new DriverResilienceOptions
        {
            Tier = DriverTier.A,
            CapabilityPolicies = new Dictionary<DriverCapability, CapabilityPolicy>
            {
                [DriverCapability.Write] = new(TimeoutSeconds: 2, RetryCount: 3, BreakerFailureThreshold: 5),
            },
        };
        var invoker = MakeInvoker(new DriverResiliencePipelineBuilder(), options);
        var attempts = 0;

        var result = await invoker.ExecuteWriteAsync(
            "host-1",
            isIdempotent: true,
            async _ =>
            {
                attempts++;
                if (attempts < 2) throw new InvalidOperationException("transient");
                await Task.Yield();
                return "ok";
            },
            CancellationToken.None);

        result.ShouldBe("ok");
        attempts.ShouldBe(2);
    }

    [Fact]
    public async Task Write_Default_DoesNotRetry_WhenPolicyHasZeroRetries()
    {
        // Tier A Write default is RetryCount=0. Even isIdempotent=true shouldn't retry
        // because the policy says not to.
        var invoker = MakeInvoker(new DriverResiliencePipelineBuilder(), new DriverResilienceOptions { Tier = DriverTier.A });
        var attempts = 0;

        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await invoker.ExecuteWriteAsync(
                "host-1",
                isIdempotent: true,
                async _ =>
                {
                    attempts++;
                    await Task.Yield();
                    throw new InvalidOperationException("boom");
#pragma warning disable CS0162
                    return 0;
#pragma warning restore CS0162
                },
                CancellationToken.None));

        attempts.ShouldBe(1, "tier-A default for Write is RetryCount=0");
    }

    [Fact]
    public async Task Execute_HonorsDifferentHosts_Independently()
    {
        var builder = new DriverResiliencePipelineBuilder();
        var invoker = MakeInvoker(builder, new DriverResilienceOptions { Tier = DriverTier.A });

        await invoker.ExecuteAsync(DriverCapability.Read, "host-a", _ => ValueTask.FromResult(1), CancellationToken.None);
        await invoker.ExecuteAsync(DriverCapability.Read, "host-b", _ => ValueTask.FromResult(2), CancellationToken.None);

        builder.CachedPipelineCount.ShouldBe(2);
    }
}
