using Akka.Actor;
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 <see cref="ActiveNodeDecision.Evaluate"/> helper covering both
/// the role-less (ScadaBridge ActiveNode) and role-filtered (OtOpcUa AdminRoleLeader) matrices,
/// plus the startup-safety null-guards on <see cref="ActiveNodeHealthCheck"/> and
/// <see cref="AkkaActiveNodeGate"/> when no <see cref="ActorSystem"/> is registered.
/// </summary>
public sealed class ActiveNodeDecisionTests
{
    // Role-less: requiredRole == null. hasRole is irrelevant. Healthy iff (selfUp && isLeader), else Unhealthy.
    public static IEnumerable<object?[]> RoleLessCases() => new[]
    {
        new object?[] { true, true, false, (string?)null, HealthStatus.Healthy },
        new object?[] { true, false, false, (string?)null, HealthStatus.Unhealthy },
        new object?[] { false, true, false, (string?)null, HealthStatus.Unhealthy },
        new object?[] { false, false, false, (string?)null, HealthStatus.Unhealthy },
    };

    [Theory]
    [MemberData(nameof(RoleLessCases))]
    public void Evaluate_RoleLess(bool selfUp, bool isLeader, bool hasRole, string? requiredRole, HealthStatus expected)
    {
        Assert.Equal(expected, ActiveNodeDecision.Evaluate(selfUp, isLeader, hasRole, requiredRole));
    }

    // Role-filtered: requiredRole != null.
    //   lacks role               -> Healthy (probe irrelevant for this node)
    //   has role & is leader     -> Healthy (selfUp is ignored — role-filtered mode only cares about leadership)
    //   has role & not leader    -> Degraded
    public static IEnumerable<object[]> RoleFilteredCases() => new[]
    {
        // node lacks the role -> Healthy regardless of selfUp / isLeader
        new object[] { true, true, false, "admin", HealthStatus.Healthy },
        new object[] { true, false, false, "admin", HealthStatus.Healthy },
        new object[] { false, false, false, "admin", HealthStatus.Healthy },
        // node carries the role and is leader -> Healthy (selfUp=true)
        new object[] { true, true, true, "admin", HealthStatus.Healthy },
        // node carries the role and is leader -> Healthy (selfUp=false: role-filtered mode ignores selfUp)
        new object[] { false, true, true, "admin", HealthStatus.Healthy },
        // node carries the role but is not leader -> Degraded
        new object[] { true, false, true, "admin", HealthStatus.Degraded },
        new object[] { false, false, true, "admin", HealthStatus.Degraded },
    };

    [Theory]
    [MemberData(nameof(RoleFilteredCases))]
    public void Evaluate_RoleFiltered(bool selfUp, bool isLeader, bool hasRole, string? requiredRole, HealthStatus expected)
    {
        Assert.Equal(expected, ActiveNodeDecision.Evaluate(selfUp, isLeader, hasRole, requiredRole));
    }

    [Fact]
    public async Task HealthCheck_RoleLess_NoActorSystem_ReturnsDegraded()
    {
        var provider = new ServiceCollection().BuildServiceProvider();
        var check = new ActiveNodeHealthCheck(provider);

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

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

    [Fact]
    public async Task HealthCheck_RoleFiltered_NoActorSystem_ReturnsDegraded()
    {
        var provider = new ServiceCollection().BuildServiceProvider();
        var check = new ActiveNodeHealthCheck(provider, "admin");

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

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

    [Fact]
    public void Gate_NoActorSystem_IsActiveNodeFalse()
    {
        var provider = new ServiceCollection().BuildServiceProvider();
        var gate = new AkkaActiveNodeGate(provider);

        Assert.False(gate.IsActiveNode);
    }

    [Fact]
    public async Task HealthCheck_RoleLess_ClusterInaccessible_ReturnsDegraded()
    {
        // ActorSystem present but Akka.Cluster not configured → Cluster.Get throws. The check must
        // return Degraded (startup-safety rule), not let the exception escape (→ Unhealthy).
        using var system = ActorSystem.Create("plain-no-cluster-roleless");
        try
        {
            var provider = new ServiceCollection()
                .AddSingleton(system)
                .BuildServiceProvider();
            var check = new ActiveNodeHealthCheck(provider);

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

            Assert.Equal(HealthStatus.Degraded, result.Status);
        }
        finally
        {
            await system.Terminate();
        }
    }

    [Fact]
    public async Task HealthCheck_RoleFiltered_ClusterInaccessible_ReturnsDegraded()
    {
        using var system = ActorSystem.Create("plain-no-cluster-rolefiltered");
        try
        {
            var provider = new ServiceCollection()
                .AddSingleton(system)
                .BuildServiceProvider();
            var check = new ActiveNodeHealthCheck(provider, "admin");

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

            Assert.Equal(HealthStatus.Degraded, result.Status);
        }
        finally
        {
            await system.Terminate();
        }
    }

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