using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace ZB.MOM.WW.Health;

/// <summary>
/// Canonical JSON response writer for the ZB.MOM.WW health endpoints.
/// </summary>
/// <remarks>
/// Self-contained — it has no runtime dependency on <c>AspNetCore.HealthChecks.UI.Client</c>;
/// the JSON shape is modelled after that library's <c>UIResponseWriter</c> output but written here
/// with <see cref="System.Text.Json"/>. The body shape is:
/// <code>
/// {
///   "status": "Healthy|Degraded|Unhealthy",
///   "totalDurationMs": 12.34,
///   "entries": {
///     "&lt;name&gt;": { "status": "...", "description": "...", "durationMs": 1.23, "data": { ... } }
///   }
/// }
/// </code>
/// The <c>description</c> key is always present; when a check supplies no description it is emitted
/// as JSON <c>null</c> (not omitted), matching the spec example and the <c>HealthChecks.UI.Client</c>
/// shape. The <c>data</c> key is the opposite: it is emitted only when the check supplied a non-empty
/// <see cref="HealthCheckResult.Data"/> dictionary, so payloads from checks that publish no data stay
/// byte-identical to the pre-0.2.0 shape. Its keys are written verbatim (the camelCase naming policy
/// applies to the envelope properties, not to dictionary keys). The HTTP status code is left to the
/// ASP.NET Core health-checks middleware (Healthy/Degraded → 200, Unhealthy → 503); this writer only
/// renders the body and sets <c>Content-Type: application/json</c>.
/// </remarks>
public static class ZbHealthWriter
{
    // Null properties are emitted (not omitted) so a null `description` renders as
    // "description": null — matching the SPEC §3 example and the HealthChecks.UI.Client shape this
    // writer mirrors. Consumers can then read entries.<name>.description without handling a missing
    // property. (Do not set DefaultIgnoreCondition = WhenWritingNull here.)
    private static readonly JsonSerializerOptions SerializerOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    };

    /// <summary>
    /// Writes <paramref name="report"/> to the response as canonical ZB.MOM.WW health JSON.
    /// </summary>
    /// <param name="context">The current HTTP context. Its <see cref="HttpResponse"/> is written to.</param>
    /// <param name="report">The aggregated health report for the tier that ran.</param>
    public static async Task WriteJsonAsync(HttpContext context, HealthReport report)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(report);

        context.Response.ContentType = "application/json; charset=utf-8";

        var payload = new HealthReportDto
        {
            Status = report.Status.ToString(),
            TotalDurationMs = report.TotalDuration.TotalMilliseconds,
            Entries = report.Entries.ToDictionary(
                static e => e.Key,
                static e => new HealthEntryDto
                {
                    Status = e.Value.Status.ToString(),
                    Description = e.Value.Description,
                    DurationMs = e.Value.Duration.TotalMilliseconds,
                    // HealthReportEntry.Data is never null (empty when the check set none); mapping
                    // empty → null is what drops the key entirely, keeping data-less payloads
                    // byte-identical to the pre-0.2.0 shape.
                    Data = e.Value.Data.Count > 0 ? e.Value.Data : null,
                }),
        };

        await JsonSerializer.SerializeAsync(context.Response.Body, payload, SerializerOptions, context.RequestAborted).ConfigureAwait(false);
    }

    private sealed class HealthReportDto
    {
        public string Status { get; init; } = string.Empty;
        public double TotalDurationMs { get; init; }
        public Dictionary<string, HealthEntryDto> Entries { get; init; } = new();
    }

    private sealed class HealthEntryDto
    {
        public string Status { get; init; } = string.Empty;
        public string? Description { get; init; }
        public double DurationMs { get; init; }

        // Per-property ignore, deliberately NOT a global DefaultIgnoreCondition: the writer must keep
        // emitting "description": null (see SerializerOptions above) while omitting "data" entirely
        // when a check publishes none.
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        public IReadOnlyDictionary<string, object>? Data { get; init; }
    }
}
