using Microsoft.Extensions.Configuration;
using ZB.MOM.WW.Configuration;
using ZB.MOM.WW.OtOpcUa.Runtime.Historian;

namespace ZB.MOM.WW.OtOpcUa.Host.Configuration;

/// <summary>
///     Fail-fast startup validator for <see cref="ServerHistorianOptions"/> (archreview 06/S-11),
///     built on the shared <c>ZB.MOM.WW.Configuration</c> <see cref="OptionsValidatorBase{TOptions}"/>.
///     Wired via <c>AddValidatedOptions</c> so a provably-crashing historian config fails host startup
///     with a named, aggregated <c>OptionsValidationException</c> instead of the raw
///     <see cref="System.UriFormatException"/> the gateway client factory's <c>new Uri(...)</c> would
///     otherwise throw at first resolution (a crash-loop under a service/docker restart policy).
/// </summary>
/// <remarks>
///     <para>
///         <b>Consumer-gated, not section-gated.</b> The load-bearing question is not "is
///         <c>ServerHistorian</c> enabled?" but "is its gateway connection <em>consumed</em> by anything
///         enabled?". The alarm-history sink (<c>AlarmHistorian:Enabled=true</c>) sources its
///         endpoint/key/TLS from the <c>ServerHistorian</c> section independently of
///         <see cref="ServerHistorianOptions.Enabled"/>, so it must also gate this check — otherwise the
///         <c>AlarmHistorian:Enabled=true</c> / <c>ServerHistorian:Enabled=false</c> corner crashes with
///         zero warning. Continuous historization is already co-gated on
///         <see cref="ServerHistorianOptions.Enabled"/> in the Host, so <c>Enabled</c> covers it.
///     </para>
///     <para>
///         <b>Fail tier = provably-crashing configs only.</b> Three settings qualify, and they all fail
///         the same way — <c>HistorianGatewayClientOptions.Validate()</c> throws inside the client
///         factory before any call is attempted, so the host dies at startup:
///         an empty / non-absolute / non-http(s) <c>Endpoint</c>; an empty <c>ApiKey</c> ("The gateway
///         API key must not be empty"); and a <c>UseTls</c> that disagrees with the endpoint scheme
///         ("UseTls requires an https gateway endpoint" / "An https gateway endpoint requires UseTls").
///         A non-positive <c>MaxTieClusterOverfetch</c> genuinely degrades rather than crashes (the node
///         manager surfaces a Bad read), so it stays an operator warning in
///         <see cref="ServerHistorianOptions.Validate"/>. The <c>Endpoint</c> value is not a secret and
///         is echoed to make each error actionable; the <c>ApiKey</c> is never surfaced.
///     </para>
///     <para>
///         <b>The <c>ApiKey</c> and <c>UseTls</c> checks were added after two consecutive live-rig
///         crash-loops</b> (LocalDb Phase 2 gate). Both had been classified as degrading, on the
///         assumption that a bad client configuration would connect and be rejected by the gateway — but
///         the client validates its own options at construction, so the process dies during Akka startup
///         instead, which is precisely the failure this validator exists to convert into a named,
///         aggregated error. The lesson generalises: "degrades" is only true of settings the client
///         does not itself validate.
///     </para>
/// </remarks>
public sealed class ServerHistorianOptionsValidator : OptionsValidatorBase<ServerHistorianOptions>
{
    private readonly IConfiguration _configuration;

    /// <summary>Creates the validator over the app configuration (read to resolve the consumer gate).</summary>
    /// <param name="configuration">The app configuration; used to read <c>AlarmHistorian:Enabled</c>.</param>
    public ServerHistorianOptionsValidator(IConfiguration configuration) =>
        _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));

    /// <inheritdoc />
    protected override void Validate(ValidationBuilder builder, ServerHistorianOptions options)
    {
        var alarmHistorianEnabled = _configuration.GetValue<bool>("AlarmHistorian:Enabled");
        var consumed = options.Enabled || alarmHistorianEnabled;

        // A section nobody consumes may legitimately be empty (the docker-dev default) — no checks.
        if (!consumed) return;

        var endpointValid = Uri.TryCreate(options.Endpoint, UriKind.Absolute, out var uri)
            && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);

        // Name the DRIVING section(s) so the operator knows why an empty/malformed endpoint fails —
        // especially in the alarm-only corner where ServerHistorian itself is disabled.
        var reason = options.Enabled
            ? "required because ServerHistorian:Enabled=true"
            : "required because AlarmHistorian:Enabled=true sources its gateway connection (endpoint/key/TLS) from the ServerHistorian section";

        builder.RequireThat(
            endpointValid,
            $"ServerHistorian:Endpoint is empty or not an absolute http(s) URI ('{options.Endpoint}') — {reason}.");

        // Deliberately not echoed: unlike the endpoint, the key is a secret.
        builder.RequireThat(
            !string.IsNullOrWhiteSpace(options.ApiKey),
            $"ServerHistorian:ApiKey is empty — {reason}. The gateway client rejects a keyless "
            + "configuration at construction, so the host would crash on startup rather than degrade. "
            + "Supply it via the environment variable ServerHistorian__ApiKey.");

        // The flag and the scheme must agree in BOTH directions — the client throws either way
        // ("UseTls requires an https gateway endpoint" / "An https gateway endpoint requires UseTls").
        // Only meaningful once the endpoint parsed; a malformed one already failed above.
        if (endpointValid)
        {
            var https = uri!.Scheme == Uri.UriSchemeHttps;
            builder.RequireThat(
                options.UseTls == https,
                $"ServerHistorian:UseTls is {options.UseTls.ToString().ToLowerInvariant()} but the "
                + $"endpoint is '{options.Endpoint}' — {reason}. The flag must match the scheme "
                + "(https ⇒ UseTls=true, http ⇒ UseTls=false); the gateway client rejects a mismatch "
                + "at construction, crashing the host on startup.");
        }
    }
}
