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 DriverResilienceOptionsParserTests
{
    [Fact]
    public void NullJson_ReturnsPureTierDefaults()
    {
        var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, null, out var diag);

        diag.ShouldBeNull();
        options.Tier.ShouldBe(DriverTier.A);
        options.Resolve(DriverCapability.Read).ShouldBe(
            DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Read]);
    }

    [Fact]
    public void WhitespaceJson_ReturnsDefaults()
    {
        DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.B, "   ", out var diag);
        diag.ShouldBeNull();
    }

    [Fact]
    public void MalformedJson_FallsBack_WithDiagnostic()
    {
        var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, "{not json", out var diag);

        diag.ShouldNotBeNull();
        diag.ShouldContain("malformed");
        options.Tier.ShouldBe(DriverTier.A);
        options.Resolve(DriverCapability.Read).ShouldBe(
            DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Read]);
    }

    [Fact]
    public void EmptyObject_ReturnsDefaults()
    {
        var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, "{}", out var diag);

        diag.ShouldBeNull();
        options.Resolve(DriverCapability.Write).ShouldBe(
            DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Write]);
    }

    [Fact]
    public void ReadOverride_MergedIntoTierDefaults()
    {
        var json = """
            {
              "capabilityPolicies": {
                "Read": { "timeoutSeconds": 5, "retryCount": 7, "breakerFailureThreshold": 2 }
              }
            }
            """;

        var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out var diag);

        diag.ShouldBeNull();
        var read = options.Resolve(DriverCapability.Read);
        read.TimeoutSeconds.ShouldBe(5);
        read.RetryCount.ShouldBe(7);
        read.BreakerFailureThreshold.ShouldBe(2);

        // Other capabilities untouched
        options.Resolve(DriverCapability.Write).ShouldBe(
            DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Write]);
    }

    [Fact]
    public void PartialPolicy_FillsMissingFieldsFromTierDefault()
    {
        var json = """
            {
              "capabilityPolicies": {
                "Read": { "retryCount": 10 }
              }
            }
            """;

        var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out _);

        var read = options.Resolve(DriverCapability.Read);
        var tierDefault = DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Read];
        read.RetryCount.ShouldBe(10);
        read.TimeoutSeconds.ShouldBe(tierDefault.TimeoutSeconds, "partial override; timeout falls back to tier default");
        read.BreakerFailureThreshold.ShouldBe(tierDefault.BreakerFailureThreshold);
    }

    [Fact]
    public void BulkheadOverrides_AreHonored()
    {
        var json = """
            { "bulkheadMaxConcurrent": 100, "bulkheadMaxQueue": 500 }
            """;

        var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.B, json, out _);

        options.BulkheadMaxConcurrent.ShouldBe(100);
        options.BulkheadMaxQueue.ShouldBe(500);
    }

    [Fact]
    public void UnknownCapability_Surfaces_InDiagnostic_ButDoesNotFail()
    {
        var json = """
            {
              "capabilityPolicies": {
                "InventedCapability": { "timeoutSeconds": 99 }
              }
            }
            """;

        var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out var diag);

        diag.ShouldNotBeNull();
        diag.ShouldContain("InventedCapability");
        // Known capabilities untouched.
        options.Resolve(DriverCapability.Read).ShouldBe(
            DriverResilienceOptions.GetTierDefaults(DriverTier.A)[DriverCapability.Read]);
    }

    [Fact]
    public void PropertyNames_AreCaseInsensitive()
    {
        var json = """
            { "BULKHEADMAXCONCURRENT": 42 }
            """;

        var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out _);

        options.BulkheadMaxConcurrent.ShouldBe(42);
    }

    [Fact]
    public void CapabilityName_IsCaseInsensitive()
    {
        var json = """
            { "capabilityPolicies": { "read": { "retryCount": 99 } } }
            """;

        var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.A, json, out var diag);

        diag.ShouldBeNull();
        options.Resolve(DriverCapability.Read).RetryCount.ShouldBe(99);
    }

    [Theory]
    [InlineData(DriverTier.A)]
    [InlineData(DriverTier.B)]
    [InlineData(DriverTier.C)]
    public void EveryTier_WithEmptyJson_RoundTrips_Its_Defaults(DriverTier tier)
    {
        var options = DriverResilienceOptionsParser.ParseOrDefaults(tier, "{}", out var diag);

        diag.ShouldBeNull();
        options.Tier.ShouldBe(tier);
        foreach (var cap in Enum.GetValues<DriverCapability>())
            options.Resolve(cap).ShouldBe(DriverResilienceOptions.GetTierDefaults(tier)[cap]);
    }
}
