using Microsoft.Extensions.Diagnostics.HealthChecks;
using ZB.MOM.WW.LocalDb;
using ZB.MOM.WW.LocalDb.Replication;

namespace ZB.MOM.WW.ScadaBridge.Host.Health;

/// <summary>
/// /health/ready check for the consolidated site database (LocalDb). Site nodes have no EF
/// context — central's <c>DatabaseHealthCheck&lt;ScadaBridgeDbContext&gt;</c> is central-only — so
/// the site store is probed directly through the registered <see cref="ILocalDb"/> with a trivial
/// <c>SELECT 1</c>. That is the honest readiness question for a site node: everything it persists
/// (OperationTracking, site events, store-and-forward, the replicated config tables) lives here.
/// </summary>
/// <remarks>
/// Replication state is reported as <c>data</c> ENRICHMENT ONLY and never affects the status.
/// Replication is default-OFF by design, so a node with no peer reports <c>connected=false</c> with
/// a real 0 backlog — the healthy steady state of an unreplicated node — and failing the check on
/// it would mark every correctly-configured single node unready.
/// </remarks>
public sealed class SiteLocalDbHealthCheck : IHealthCheck
{
    private readonly ILocalDb _localDb;
    private readonly ISyncStatus? _syncStatus;

    /// <summary>Initializes a new <see cref="SiteLocalDbHealthCheck"/>.</summary>
    /// <param name="localDb">The consolidated site database registered by <c>AddZbLocalDb</c>.</param>
    /// <param name="syncStatus">
    /// The replication status view, when the replication engine is registered. Optional so the check
    /// still works in a composition that wires storage without replication.
    /// </param>
    public SiteLocalDbHealthCheck(ILocalDb localDb, ISyncStatus? syncStatus = null)
    {
        _localDb = localDb ?? throw new ArgumentNullException(nameof(localDb));
        _syncStatus = syncStatus;
    }

    /// <summary>Probes the site database and reports its reachability.</summary>
    /// <param name="context">The health check context (unused; the verdict depends only on the probe).</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>Healthy when the probe query succeeds; Unhealthy with the fault otherwise.</returns>
    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            await _localDb.QueryAsync("SELECT 1;", static r => r.GetInt32(0), ct: cancellationToken)
                .ConfigureAwait(false);
        }
        catch (Exception ex) when (ex is not OperationCanceledException)
        {
            return HealthCheckResult.Unhealthy("Site LocalDb is not reachable.", ex);
        }

        return HealthCheckResult.Healthy("Site LocalDb reachable.", BuildData());
    }

    private IReadOnlyDictionary<string, object> BuildData()
    {
        var data = new Dictionary<string, object>(StringComparer.Ordinal);
        if (_syncStatus is null)
            return data;

        data["replicationConnected"] = _syncStatus.Connected;

        // Nullable on purpose upstream: null means "backlog unknown" (no provider wired or the poll
        // failed), which must not be flattened to 0 — that would render a pair which cannot read its
        // own oplog as perfectly caught up. Omit the key instead.
        if (_syncStatus.OplogBacklog is { } backlog)
            data["replicationOplogBacklog"] = backlog;

        if (_syncStatus.PeerNodeId is { } peer)
            data["replicationPeerNodeId"] = peer;

        return data;
    }
}
