using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace ZB.MOM.WW.Health;

/// <summary>
/// Maps the canonical ZB.MOM.WW three-tier health endpoints in one call.
/// </summary>
public static class ZbHealthEndpointExtensions
{
    /// <summary>
    /// Maps the three health tiers:
    /// <list type="bullet">
    ///   <item><description><c>/health/ready</c> — runs only checks tagged <see cref="ZbHealthTags.Ready"/>.</description></item>
    ///   <item><description><c>/health/active</c> — runs only checks tagged <see cref="ZbHealthTags.Active"/>.</description></item>
    ///   <item><description><c>/healthz</c> — bare process liveness; runs no checks (always 200 while the process is up).</description></item>
    /// </list>
    /// All three are anonymous. Status mapping is the ASP.NET Core default:
    /// Healthy/Degraded → 200, Unhealthy → 503.
    /// </summary>
    /// <remarks>
    /// Does NOT call <c>services.AddHealthChecks()</c> — the caller registers probes and their tags.
    /// The readiness and active tiers use the canonical JSON writer
    /// (<see cref="ZbHealthWriter.WriteJsonAsync"/>) unless overridden via
    /// <see cref="ZbHealthEndpointOptions.ResponseWriter"/>. The liveness tier runs no checks and
    /// emits a minimal <c>200 OK</c> body.
    /// </remarks>
    /// <returns>
    /// A composite <see cref="IEndpointConventionBuilder"/> that fans every chained convention out to
    /// <em>all three</em> health endpoints (readiness, active, and liveness). For example,
    /// <c>endpoints.MapZbHealth().RequireHost("…")</c> gates all three endpoints, as a caller expects.
    /// </returns>
    public static IEndpointConventionBuilder MapZbHealth(
        this IEndpointRouteBuilder endpoints,
        ZbHealthEndpointOptions? options = null)
    {
        ArgumentNullException.ThrowIfNull(endpoints);
        options ??= new ZbHealthEndpointOptions();

        var responseWriter = options.ResponseWriter ?? ZbHealthWriter.WriteJsonAsync;

        var ready = endpoints.MapHealthChecks(options.ReadyPath, new HealthCheckOptions
        {
            Predicate = static c => c.Tags.Contains(ZbHealthTags.Ready),
            ResponseWriter = responseWriter,
        }).AllowAnonymous();

        var active = endpoints.MapHealthChecks(options.ActivePath, new HealthCheckOptions
        {
            Predicate = static c => c.Tags.Contains(ZbHealthTags.Active),
            ResponseWriter = responseWriter,
        }).AllowAnonymous();

        // Liveness: run no checks. The endpoint returns 200 as long as the process can respond.
        // No JSON writer — the empty report would carry no useful data, so the framework default
        // (a minimal plain-text body) is sufficient.
        var live = endpoints.MapHealthChecks(options.LivePath, new HealthCheckOptions
        {
            Predicate = static _ => false,
        }).AllowAnonymous();

        return new CompositeEndpointConventionBuilder(ready, active, live);
    }

    /// <summary>
    /// An <see cref="IEndpointConventionBuilder"/> that forwards each convention to several
    /// underlying builders, so conventions chained onto the result of
    /// <see cref="MapZbHealth(IEndpointRouteBuilder, ZbHealthEndpointOptions?)"/> apply to all three
    /// health endpoints rather than just one.
    /// </summary>
    private sealed class CompositeEndpointConventionBuilder : IEndpointConventionBuilder
    {
        private readonly IEndpointConventionBuilder[] _builders;

        public CompositeEndpointConventionBuilder(params IEndpointConventionBuilder[] builders) =>
            _builders = builders;

        public void Add(Action<EndpointBuilder> convention)
        {
            foreach (var builder in _builders)
                builder.Add(convention);
        }

        public void Finally(Action<EndpointBuilder> finalConvention)
        {
            foreach (var builder in _builders)
                builder.Finally(finalConvention);
        }
    }

    /// <summary>
    /// Maps the three health tiers, configuring options inline. See the other
    /// <see cref="MapZbHealth(IEndpointRouteBuilder, ZbHealthEndpointOptions?)"/> overload for tier semantics.
    /// </summary>
    /// <param name="endpoints">The endpoint route builder to map onto.</param>
    /// <param name="configure">Callback that mutates a fresh <see cref="ZbHealthEndpointOptions"/>.</param>
    /// <returns>
    /// A composite <see cref="IEndpointConventionBuilder"/> that fans chained conventions out to all
    /// three health endpoints.
    /// </returns>
    public static IEndpointConventionBuilder MapZbHealth(
        this IEndpointRouteBuilder endpoints,
        Action<ZbHealthEndpointOptions> configure)
    {
        ArgumentNullException.ThrowIfNull(endpoints);
        ArgumentNullException.ThrowIfNull(configure);

        var options = new ZbHealthEndpointOptions();
        configure(options);
        return endpoints.MapZbHealth(options);
    }
}
