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

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

/// <summary>
/// Reports how many MXAccess sessions are healthy. Each session is one worker process holding one
/// MXAccess COM instance — a live connection into a Galaxy — so this is the "how many Galaxy
/// connections are healthy" probe, expressed in the vocabulary the code actually uses.
/// </summary>
/// <remarks>
/// <para>
/// <b>Zero sessions is healthy, deliberately.</b> The gateway is a server: it opens a session when
/// a client asks and holds none otherwise, so idle-with-no-clients is the normal steady state, not
/// a fault. A count-based rule ("unhealthy below N") would sit red forever on a host nothing dials
/// yet, and a probe that is permanently red is one people learn to ignore — which costs more than
/// having no probe. The status here is therefore false only when a session exists and its worker
/// has actually failed.
/// </para>
/// <para>
/// This is tagged <c>active</c> rather than <c>ready</c> for the same reason. Readiness gates
/// whether the process should receive traffic, and a gateway with no sessions is legitimately ready
/// to serve — unlike the auth store, which every call depends on (see
/// <see cref="AuthStoreHealthCheck"/>). Failing readiness on session state would take a working
/// gateway out of rotation for a condition its own clients cause.
/// </para>
/// </remarks>
public sealed class SessionHealthCheck : IHealthCheck
{
    private readonly ISessionRegistry _sessionRegistry;

    /// <summary>Initializes a new instance of the <see cref="SessionHealthCheck"/> class.</summary>
    /// <param name="sessionRegistry">Registry holding the live sessions.</param>
    public SessionHealthCheck(ISessionRegistry sessionRegistry) =>
        _sessionRegistry = sessionRegistry ?? throw new ArgumentNullException(nameof(sessionRegistry));

    /// <summary>Buckets the live sessions by state and grades the result.</summary>
    /// <param name="context">The health check context.</param>
    /// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
    /// <returns>
    /// Healthy when nothing is faulted (including when no sessions are open), Degraded when some
    /// sessions are faulted but others are still usable, and Unhealthy when every session is
    /// faulted.
    /// </returns>
    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        int ready = 0;
        int faulted = 0;
        int starting = 0;
        int closing = 0;

        foreach (GatewaySession session in _sessionRegistry.Snapshot())
        {
            switch (session.State)
            {
                case SessionState.Ready:
                    ready++;
                    break;
                case SessionState.Faulted:
                    faulted++;
                    break;
                case SessionState.Closing:
                case SessionState.Closed:
                    // Counted but excluded from the verdict: a session on its way out is an
                    // expected lifecycle stage, not a failure, and Snapshot() still returns
                    // Closed sessions until they are removed from the registry.
                    closing++;
                    break;
                default:
                    // Creating / StartingWorker / WaitingForPipe / Handshaking /
                    // InitializingWorker — mid-startup, not yet usable but not wrong.
                    // Unspecified lands here too; it is the proto zero value and should not occur.
                    starting++;
                    break;
            }
        }

        int total = ready + faulted + starting + closing;
        int usable = ready + starting;

        Dictionary<string, object> data = new(StringComparer.Ordinal)
        {
            ["total"] = total,
            ["ready"] = ready,
            ["faulted"] = faulted,
            ["starting"] = starting,
            ["closing"] = closing,
        };

        HealthCheckResult result = (faulted, usable) switch
        {
            (0, _) => HealthCheckResult.Healthy(Describe(total, ready, faulted), data),
            (_, 0) => HealthCheckResult.Unhealthy(Describe(total, ready, faulted), data: data),
            _ => HealthCheckResult.Degraded(Describe(total, ready, faulted), data: data),
        };

        return Task.FromResult(result);
    }

    private static string Describe(int total, int ready, int faulted)
    {
        if (total == 0)
        {
            return "No MXAccess sessions are open.";
        }

        return faulted == 0
            ? $"{ready} of {total} MXAccess sessions ready."
            : $"{ready} of {total} MXAccess sessions ready, {faulted} faulted.";
    }
}
