using Microsoft.Extensions.Diagnostics.HealthChecks;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Server.Diagnostics;
using ZB.MOM.WW.MxGateway.Server.Sessions;

namespace ZB.MOM.WW.MxGateway.Tests.Diagnostics;

public sealed class SessionHealthCheckTests
{
    /// <summary>
    /// An idle gateway is healthy. This is the load-bearing case: a gateway holding no sessions is
    /// the normal steady state on a host nothing dials yet, and a probe that reports red there is
    /// one operators learn to ignore.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task Healthy_WhenNoSessionsAreOpen()
    {
        var check = new SessionHealthCheck(new SessionRegistry());

        HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());

        Assert.Equal(HealthStatus.Healthy, result.Status);
        Assert.Equal(0, result.Data["total"]);
        Assert.Equal("No MXAccess sessions are open.", result.Description);
    }

    /// <summary>Every session ready reports healthy, with the counts carried as entry data.</summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task Healthy_WhenAllSessionsReady()
    {
        var check = new SessionHealthCheck(RegistryWith(SessionState.Ready, SessionState.Ready));

        HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());

        Assert.Equal(HealthStatus.Healthy, result.Status);
        Assert.Equal(2, result.Data["total"]);
        Assert.Equal(2, result.Data["ready"]);
        Assert.Equal(0, result.Data["faulted"]);
    }

    /// <summary>
    /// A faulted session alongside a usable one is degraded, not unhealthy — the gateway is still
    /// serving the sessions that work.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task Degraded_WhenSomeFaultedAndSomeReady()
    {
        var check = new SessionHealthCheck(RegistryWith(SessionState.Ready, SessionState.Faulted));

        HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());

        Assert.Equal(HealthStatus.Degraded, result.Status);
        Assert.Equal(1, result.Data["ready"]);
        Assert.Equal(1, result.Data["faulted"]);
        Assert.Contains("1 faulted", result.Description, StringComparison.Ordinal);
    }

    /// <summary>
    /// A session still starting counts as usable for grading, so a fault beside it is degraded
    /// rather than unhealthy — the startup has not failed yet.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task Degraded_WhenFaultedBesideAStartingSession()
    {
        var check = new SessionHealthCheck(
            RegistryWith(SessionState.Faulted, SessionState.StartingWorker));

        HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());

        Assert.Equal(HealthStatus.Degraded, result.Status);
        Assert.Equal(1, result.Data["starting"]);
    }

    /// <summary>Every session faulted is the genuinely bad condition, and the only unhealthy one.</summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task Unhealthy_WhenEverySessionIsFaulted()
    {
        var check = new SessionHealthCheck(RegistryWith(SessionState.Faulted, SessionState.Faulted));

        HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());

        Assert.Equal(HealthStatus.Unhealthy, result.Status);
        Assert.Equal(2, result.Data["faulted"]);
        Assert.Equal(0, result.Data["ready"]);
    }

    /// <summary>
    /// Closed sessions linger in the registry until they are removed. They are counted separately
    /// and excluded from the verdict, so a gateway whose sessions all closed cleanly is healthy —
    /// not unhealthy for having zero ready ones.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task Healthy_WhenOnlyClosedSessionsRemain()
    {
        var check = new SessionHealthCheck(RegistryWith(SessionState.Closed, SessionState.Closed));

        HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());

        Assert.Equal(HealthStatus.Healthy, result.Status);
        Assert.Equal(2, result.Data["closing"]);
        Assert.Equal(0, result.Data["ready"]);
    }

    private static SessionRegistry RegistryWith(params SessionState[] states)
    {
        var registry = new SessionRegistry();
        for (int i = 0; i < states.Length; i++)
        {
            GatewaySession session = CreateSession($"session-{i}");
            session.TransitionTo(states[i]);
            Assert.True(registry.TryAdd(session));
        }

        return registry;
    }

    private static GatewaySession CreateSession(string sessionId)
    {
        return new GatewaySession(
            sessionId,
            "mxaccess",
            $"mxaccess-gateway-1-{sessionId}",
            "nonce",
            clientIdentity: null,
            clientSessionName: "test-session",
            clientCorrelationId: "client-correlation",
            TimeSpan.FromSeconds(30),
            TimeSpan.FromSeconds(5),
            TimeSpan.FromSeconds(5),
            DateTimeOffset.UnixEpoch);
    }
}
