using System.Diagnostics.Metrics;

namespace ZB.MOM.WW.ScadaBridge.Commons.Observability;

/// <summary>
/// Central <see cref="Meter"/> + instrument definitions for ScadaBridge's application
/// telemetry, modelled on OtOpcUa's <c>OtOpcUaTelemetry</c>. Modules emit through these
/// pre-created instruments so a single OpenTelemetry / Prometheus binding in
/// <c>Host</c> (registered via <c>AddZbTelemetry</c> with this meter named in
/// <c>ZbTelemetryOptions.Meters</c>) catches everything. No exporter is required —
/// instruments are no-op until a listener attaches, so tests and dev hosts pay nothing
/// for instrumentation that nobody scrapes.
///
/// Instrument names follow the OpenTelemetry semantic convention pattern
/// <c>scadabridge.&lt;subsystem&gt;.&lt;event&gt;</c>. This task defines the instruments and
/// their emit helpers; four later tasks wire the actual emit points. Until those land the
/// helpers are dormant but inert — calling them is safe and simply records against a meter
/// that nothing observes.
/// </summary>
public static class ScadaBridgeTelemetry
{
    /// <summary>The meter name registered with OTel via <c>ZbTelemetryOptions.Meters</c>.</summary>
    public const string MeterName = "ZB.MOM.WW.ScadaBridge";

    /// <summary>Singleton <see cref="Meter"/> all instruments hang off.</summary>
    private static readonly Meter Meter = new(MeterName);

    // ---------------- Counters ----------------

    /// <summary>Incremented each time a deployment is successfully applied.</summary>
    private static readonly Counter<long> _deploymentsApplied =
        Meter.CreateCounter<long>("scadabridge.deployments.applied", unit: "1",
            description: "Deployments applied.");

    /// <summary>Incremented for each inbound API request, tagged with the API method.</summary>
    private static readonly Counter<long> _inboundApiRequests =
        Meter.CreateCounter<long>("scadabridge.inbound_api.requests", unit: "1",
            description: "Inbound API requests, tagged by method.");

    // ---------------- Observable gauges ----------------

    /// <summary>Current count of open site connections, mutated via <see cref="Interlocked"/>.</summary>
    private static long _siteConnectionsUp;

    /// <summary>Provider that yields the live StoreAndForward queue depth; set by a later task.</summary>
    private static Func<long>? _queueDepthProvider;

#pragma warning disable IDE0052 // Held to keep the observable gauges alive for the meter's lifetime.
    /// <summary>Gauge reporting the number of currently open site connections.</summary>
    private static readonly ObservableGauge<long> _siteConnectionUp =
        Meter.CreateObservableGauge<long>("scadabridge.site.connection.up",
            () => Interlocked.Read(ref _siteConnectionsUp),
            description: "Number of currently open site connections.");

    /// <summary>Gauge reporting the current StoreAndForward queue depth via the registered provider.</summary>
    private static readonly ObservableGauge<long> _storeAndForwardQueueDepth =
        Meter.CreateObservableGauge<long>("scadabridge.store_and_forward.queue.depth",
            () => Volatile.Read(ref _queueDepthProvider) is { } p ? p() : 0L,
            unit: "items",
            description: "Current StoreAndForward queue depth.");
#pragma warning restore IDE0052

    // ---------------- Emit helpers ----------------

    /// <summary>Records that a deployment was applied.</summary>
    public static void RecordDeploymentApplied() => _deploymentsApplied.Add(1);

    /// <summary>Records an inbound API request for the given <paramref name="method"/>.</summary>
    /// <param name="method">The API method the request targeted.</param>
    public static void RecordInboundApiRequest(string method) =>
        _inboundApiRequests.Add(1, new KeyValuePair<string, object?>("method", method));

    /// <summary>Records that a site connection opened (increments the up-count gauge).</summary>
    public static void SiteConnectionOpened() => Interlocked.Increment(ref _siteConnectionsUp);

    /// <summary>Records that a site connection closed (decrements the up-count gauge).</summary>
    public static void SiteConnectionClosed() => Interlocked.Decrement(ref _siteConnectionsUp);

    /// <summary>
    /// Registers the provider the StoreAndForward queue-depth gauge reads on each observation.
    /// A later task supplies a provider that reads the real StoreAndForward depth. A null
    /// provider is ignored so the gauge falls back to reporting 0.
    /// </summary>
    /// <param name="provider">A callback returning the current queue depth.</param>
    public static void SetQueueDepthProvider(Func<long> provider)
    {
        if (provider is null)
        {
            return;
        }

        Volatile.Write(ref _queueDepthProvider, provider);
    }
}
