using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Core.Observability;

/// <summary>
///     Domain-layer health aggregation for Phase 6.1 Stream C. Pure functions over the driver
///     fleet — given each driver's <see cref="DriverState"/>, produce a <see cref="ReadinessVerdict"/>
///     that maps to HTTP status codes at the endpoint layer.
/// </summary>
/// <remarks>
///     State matrix per <c>docs/v2/implementation/phase-6-1-resilience-and-observability.md</c>
///     §Stream C.1:
///     <list type="bullet">
///         <item><see cref="DriverState.Unknown"/> / <see cref="DriverState.Initializing"/>
///             → /readyz 503 (not yet ready).</item>
///         <item><see cref="DriverState.Healthy"/> → /readyz 200.</item>
///         <item><see cref="DriverState.Degraded"/> → /readyz 200 with flagged driver IDs.</item>
///         <item><see cref="DriverState.Reconnecting"/> → /readyz 200 with flagged driver IDs
///             (driver alive but not serving live data; same verdict as Degraded).</item>
///         <item><see cref="DriverState.Faulted"/> → /readyz 503.</item>
///     </list>
///     The overall verdict is computed across the fleet: any Faulted → Faulted; any
///     Unknown/Initializing → NotReady; any Degraded or Reconnecting → Degraded; else
///     Healthy. An empty fleet is Healthy (nothing to degrade).
/// </remarks>
public static class DriverHealthReport
{
    /// <summary>Compute the fleet-wide readiness verdict from per-driver states.</summary>
    /// <param name="drivers">The list of per-driver health snapshots to aggregate.</param>
    /// <returns>The fleet-wide readiness verdict.</returns>
    public static ReadinessVerdict Aggregate(IReadOnlyList<DriverHealthSnapshot> drivers)
    {
        ArgumentNullException.ThrowIfNull(drivers);
        if (drivers.Count == 0) return ReadinessVerdict.Healthy;

        var anyFaulted = drivers.Any(d => d.State == DriverState.Faulted);
        if (anyFaulted) return ReadinessVerdict.Faulted;

        var anyInitializing = drivers.Any(d =>
            d.State == DriverState.Unknown || d.State == DriverState.Initializing);
        if (anyInitializing) return ReadinessVerdict.NotReady;

        // Reconnecting = driver alive but not serving live data; report as Degraded so /readyz
        // stays 200 (the fleet can still serve cached / last-good data) while operators see the
        // affected driver in the body.
        var anyDegraded = drivers.Any(d =>
            d.State == DriverState.Degraded || d.State == DriverState.Reconnecting);
        if (anyDegraded) return ReadinessVerdict.Degraded;

        return ReadinessVerdict.Healthy;
    }

    /// <summary>
    ///     Map a <see cref="ReadinessVerdict"/> to the HTTP status the /readyz endpoint should
    ///     return per the Stream C.1 state matrix.
    /// </summary>
    /// <param name="verdict">The readiness verdict to map to HTTP status.</param>
    /// <returns>The HTTP status code the /readyz endpoint should return.</returns>
    public static int HttpStatus(ReadinessVerdict verdict) => verdict switch
    {
        ReadinessVerdict.Healthy => 200,
        ReadinessVerdict.Degraded => 200,
        ReadinessVerdict.NotReady => 503,
        ReadinessVerdict.Faulted => 503,
        _ => 500,
    };
}

/// <summary>Per-driver snapshot fed into <see cref="DriverHealthReport.Aggregate"/>.</summary>
/// <param name="DriverInstanceId">Driver instance identifier (from <c>IDriver.DriverInstanceId</c>).</param>
/// <param name="State">Current <see cref="DriverState"/> from <c>IDriver.GetHealth</c>.</param>
/// <param name="DetailMessage">Optional driver-supplied detail (e.g. "primary PLC unreachable").</param>
public sealed record DriverHealthSnapshot(
    string DriverInstanceId,
    DriverState State,
    string? DetailMessage = null);

/// <summary>Overall fleet readiness — derived from driver states by <see cref="DriverHealthReport.Aggregate"/>.</summary>
public enum ReadinessVerdict
{
    /// <summary>All drivers Healthy (or fleet is empty).</summary>
    Healthy,

    /// <summary>At least one driver Degraded; none Faulted / NotReady.</summary>
    Degraded,

    /// <summary>At least one driver Unknown / Initializing; none Faulted.</summary>
    NotReady,

    /// <summary>At least one driver Faulted.</summary>
    Faulted,
}
