using Akka.Actor;
using Akka.Cluster;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using ZB.MOM.WW.Health.Akka;

namespace ZB.MOM.WW.Health.Akka.Tests;

/// <summary>
/// Table-driven tests for the pure status-mapping function inside <see cref="AkkaClusterStatusPolicy"/>.
/// The two presets (<see cref="AkkaClusterStatusPolicy.Default"/> and
/// <see cref="AkkaClusterStatusPolicy.OtOpcUaCompat"/>) are the convergence targets for ScadaBridge
/// and OtOpcUa respectively; every <see cref="MemberStatus"/> is exercised so a drift in either
/// preset fails loudly. Also covers the startup-safety null-guard on <see cref="AkkaClusterHealthCheck"/>.
/// </summary>
public sealed class AkkaClusterStatusPolicyTests
{
    public static IEnumerable<object[]> DefaultCases() => new[]
    {
        new object[] { MemberStatus.Up, HealthStatus.Healthy },
        new object[] { MemberStatus.Joining, HealthStatus.Healthy },
        new object[] { MemberStatus.Leaving, HealthStatus.Degraded },
        new object[] { MemberStatus.Exiting, HealthStatus.Degraded },
        new object[] { MemberStatus.WeaklyUp, HealthStatus.Unhealthy },
        new object[] { MemberStatus.Down, HealthStatus.Unhealthy },
        new object[] { MemberStatus.Removed, HealthStatus.Unhealthy },
        new object[] { (MemberStatus)99, HealthStatus.Unhealthy },   // unknown / future status
    };

    [Theory]
    [MemberData(nameof(DefaultCases))]
    public void Default_MapsEveryStatus(MemberStatus status, HealthStatus expected)
    {
        Assert.Equal(expected, AkkaClusterStatusPolicy.Default.Evaluate(status));
    }

    public static IEnumerable<object[]> OtOpcUaCompatCases() => new[]
    {
        new object[] { MemberStatus.Up, HealthStatus.Healthy },
        new object[] { MemberStatus.Joining, HealthStatus.Degraded },
        new object[] { MemberStatus.Leaving, HealthStatus.Degraded },
        new object[] { MemberStatus.Exiting, HealthStatus.Degraded },
        new object[] { MemberStatus.WeaklyUp, HealthStatus.Degraded },
        new object[] { MemberStatus.Down, HealthStatus.Degraded },
        new object[] { MemberStatus.Removed, HealthStatus.Degraded },
        new object[] { (MemberStatus)99, HealthStatus.Degraded },    // unknown / future status
    };

    [Theory]
    [MemberData(nameof(OtOpcUaCompatCases))]
    public void OtOpcUaCompat_OnlyUpIsHealthy(MemberStatus status, HealthStatus expected)
    {
        Assert.Equal(expected, AkkaClusterStatusPolicy.OtOpcUaCompat.Evaluate(status));
    }

    [Fact]
    public void CustomPolicy_UsesSuppliedFunc()
    {
        var policy = new AkkaClusterStatusPolicy(_ => HealthStatus.Unhealthy);
        Assert.Equal(HealthStatus.Unhealthy, policy.Evaluate(MemberStatus.Up));
    }

    [Fact]
    public async Task HealthCheck_NoActorSystem_ReturnsDegraded()
    {
        var provider = new ServiceCollection().BuildServiceProvider();
        var check = new AkkaClusterHealthCheck(provider, AkkaClusterStatusPolicy.Default);

        var result = await check.CheckHealthAsync(NewContext(check));

        Assert.Equal(HealthStatus.Degraded, result.Status);
    }

    [Fact]
    public async Task HealthCheck_ActorSystemPresentButClusterInaccessible_ReturnsDegraded()
    {
        // A plain (non-clustered) ActorSystem exists in DI, but Akka.Cluster is not configured,
        // so Cluster.Get(system) throws a ConfigurationException — the startup race the spec calls
        // out. The check must return Degraded, not let the exception escape (→ Unhealthy via the host).
        using var system = ActorSystem.Create("plain-no-cluster");
        try
        {
            var provider = new ServiceCollection()
                .AddSingleton(system)
                .BuildServiceProvider();
            var check = new AkkaClusterHealthCheck(provider, AkkaClusterStatusPolicy.Default);

            var result = await check.CheckHealthAsync(NewContext(check));

            Assert.Equal(HealthStatus.Degraded, result.Status);
            // No cluster to describe → no data, so the emitted payload stays the pre-0.2.0 shape.
            Assert.Empty(result.Data);
        }
        finally
        {
            await system.Terminate();
        }
    }

    private static HealthCheckContext NewContext(IHealthCheck check) => new()
    {
        Registration = new HealthCheckRegistration("akka-cluster", check, HealthStatus.Unhealthy, tags: null),
    };
}
