using System.Collections.Concurrent;

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

/// <summary>
///     Process-singleton tracker of live resilience counters per
///     <c>(DriverInstanceId, HostName)</c>. Populated by the CapabilityInvoker (call
///     start/complete + success reset), the DriverResiliencePipelineBuilder's Polly
///     telemetry (retry failure, breaker open/close), and the MemoryTracking layer
///     (footprint). Read by <c>DriverResilienceStatusPublisherService</c> (Host, driver
///     nodes) which periodically publishes each snapshot to the
///     <c>driver-resilience-status</c> DistributedPubSub topic for the AdminUI
///     resilience panel.
/// </summary>
/// <remarks>
///     Pure in-memory (no EF Core / SQL Server dependency) so tests can exercise it directly.
///     The operator-facing reader is the DPS publisher named above, mirroring the driver-health
///     flow — not the DB-table/HostedService shape originally sketched for Phase 6.1 Stream E
///     (R2-10 built the DPS reader instead).
/// </remarks>
public sealed class DriverResilienceStatusTracker
{
    private readonly ConcurrentDictionary<StatusKey, ResilienceStatusSnapshot> _status = new();

    /// <summary>Record a Polly pipeline failure for <paramref name="hostName"/>.</summary>
    /// <param name="driverInstanceId">The driver instance identifier.</param>
    /// <param name="hostName">The host name.</param>
    /// <param name="utcNow">The UTC timestamp of the failure event.</param>
    public void RecordFailure(string driverInstanceId, string hostName, DateTime utcNow)
    {
        var key = new StatusKey(driverInstanceId, hostName);
        _status.AddOrUpdate(key,
            _ => new ResilienceStatusSnapshot { ConsecutiveFailures = 1, LastSampledUtc = utcNow },
            (_, existing) => existing with
            {
                ConsecutiveFailures = existing.ConsecutiveFailures + 1,
                LastSampledUtc = utcNow,
            });
    }

    /// <summary>Reset the consecutive-failure count on a successful pipeline execution.</summary>
    /// <param name="driverInstanceId">The driver instance identifier.</param>
    /// <param name="hostName">The host name.</param>
    /// <param name="utcNow">The UTC timestamp of the success event.</param>
    public void RecordSuccess(string driverInstanceId, string hostName, DateTime utcNow)
    {
        var key = new StatusKey(driverInstanceId, hostName);
        _status.AddOrUpdate(key,
            _ => new ResilienceStatusSnapshot { ConsecutiveFailures = 0, LastSampledUtc = utcNow },
            (_, existing) => existing with
            {
                ConsecutiveFailures = 0,
                LastSampledUtc = utcNow,
            });
    }

    /// <summary>Record a circuit-breaker open event.</summary>
    /// <param name="driverInstanceId">The driver instance identifier.</param>
    /// <param name="hostName">The host name.</param>
    /// <param name="utcNow">The UTC timestamp of the breaker open event.</param>
    public void RecordBreakerOpen(string driverInstanceId, string hostName, DateTime utcNow)
    {
        var key = new StatusKey(driverInstanceId, hostName);
        _status.AddOrUpdate(key,
            _ => new ResilienceStatusSnapshot { LastBreakerOpenUtc = utcNow, LastSampledUtc = utcNow },
            (_, existing) => existing with { LastBreakerOpenUtc = utcNow, LastSampledUtc = utcNow });
    }

    /// <summary>
    ///     Record a circuit-breaker close (recovery) event — the paired counterpart to
    ///     <see cref="RecordBreakerOpen"/>. Stamps <see cref="ResilienceStatusSnapshot.LastBreakerClosedUtc"/>
    ///     so <see cref="ResilienceStatusSnapshot.IsBreakerOpen"/> can report whether the breaker
    ///     is currently open. Called from the pipeline builder's <c>OnClosed</c> hook.
    /// </summary>
    /// <param name="driverInstanceId">The driver instance identifier.</param>
    /// <param name="hostName">The host name.</param>
    /// <param name="utcNow">The UTC timestamp of the breaker close event.</param>
    public void RecordBreakerClose(string driverInstanceId, string hostName, DateTime utcNow)
    {
        var key = new StatusKey(driverInstanceId, hostName);
        _status.AddOrUpdate(key,
            _ => new ResilienceStatusSnapshot { LastBreakerClosedUtc = utcNow, LastSampledUtc = utcNow },
            (_, existing) => existing with { LastBreakerClosedUtc = utcNow, LastSampledUtc = utcNow });
    }

    /// <summary>Record a process recycle event (Tier C only).</summary>
    /// <param name="driverInstanceId">The driver instance identifier.</param>
    /// <param name="hostName">The host name.</param>
    /// <param name="utcNow">The UTC timestamp of the recycle event.</param>
    public void RecordRecycle(string driverInstanceId, string hostName, DateTime utcNow)
    {
        var key = new StatusKey(driverInstanceId, hostName);
        _status.AddOrUpdate(key,
            _ => new ResilienceStatusSnapshot { LastRecycleUtc = utcNow, LastSampledUtc = utcNow },
            (_, existing) => existing with { LastRecycleUtc = utcNow, LastSampledUtc = utcNow });
    }

    /// <summary>Capture / update the MemoryTracking-supplied baseline + current footprint.</summary>
    /// <param name="driverInstanceId">The driver instance identifier.</param>
    /// <param name="hostName">The host name.</param>
    /// <param name="baselineBytes">The baseline footprint in bytes.</param>
    /// <param name="currentBytes">The current footprint in bytes.</param>
    /// <param name="utcNow">The UTC timestamp of the footprint update.</param>
    public void RecordFootprint(string driverInstanceId, string hostName, long baselineBytes, long currentBytes, DateTime utcNow)
    {
        var key = new StatusKey(driverInstanceId, hostName);
        _status.AddOrUpdate(key,
            _ => new ResilienceStatusSnapshot
            {
                BaselineFootprintBytes = baselineBytes,
                CurrentFootprintBytes = currentBytes,
                LastSampledUtc = utcNow,
            },
            (_, existing) => existing with
            {
                BaselineFootprintBytes = baselineBytes,
                CurrentFootprintBytes = currentBytes,
                LastSampledUtc = utcNow,
            });
    }

    /// <summary>
    ///     Record the entry of a capability call for this (instance, host). Increments the
    ///     in-flight counter used as the <see cref="ResilienceStatusSnapshot.CurrentInFlight"/>
    ///     surface (in-flight capability-call depth). Paired with
    ///     <see cref="RecordCallComplete"/>; callers use try/finally.
    /// </summary>
    /// <param name="driverInstanceId">The driver instance identifier.</param>
    /// <param name="hostName">The host name.</param>
    public void RecordCallStart(string driverInstanceId, string hostName)
    {
        var key = new StatusKey(driverInstanceId, hostName);
        _status.AddOrUpdate(key,
            _ => new ResilienceStatusSnapshot { CurrentInFlight = 1 },
            (_, existing) => existing with { CurrentInFlight = existing.CurrentInFlight + 1 });
    }

    /// <summary>Paired with <see cref="RecordCallStart"/> — decrements the in-flight counter.</summary>
    /// <param name="driverInstanceId">The driver instance identifier.</param>
    /// <param name="hostName">The host name.</param>
    public void RecordCallComplete(string driverInstanceId, string hostName)
    {
        var key = new StatusKey(driverInstanceId, hostName);
        _status.AddOrUpdate(key,
            _ => new ResilienceStatusSnapshot { CurrentInFlight = 0 }, // start-without-complete shouldn't happen; clamp to 0
            (_, existing) => existing with { CurrentInFlight = Math.Max(0, existing.CurrentInFlight - 1) });
    }

    /// <summary>Snapshot of a specific (instance, host) pair; null if no counters recorded yet.</summary>
    /// <param name="driverInstanceId">The driver instance identifier.</param>
    /// <param name="hostName">The host name.</param>
    /// <returns>The current resilience status snapshot, or null if no counters have been recorded for this pair.</returns>
    public ResilienceStatusSnapshot? TryGet(string driverInstanceId, string hostName) =>
        _status.TryGetValue(new StatusKey(driverInstanceId, hostName), out var snapshot) ? snapshot : null;

    /// <summary>Copy of every currently-tracked (instance, host, snapshot) triple. Safe under concurrent writes.</summary>
    /// <returns>A snapshot list of all tracked (driver instance, host, resilience snapshot) triples.</returns>
    public IReadOnlyList<(string DriverInstanceId, string HostName, ResilienceStatusSnapshot Snapshot)> Snapshot() =>
        _status.Select(kvp => (kvp.Key.DriverInstanceId, kvp.Key.HostName, kvp.Value)).ToList();

    private readonly record struct StatusKey(string DriverInstanceId, string HostName);
}

/// <summary>Snapshot of the resilience counters for one <c>(DriverInstanceId, HostName)</c> pair.</summary>
public sealed record ResilienceStatusSnapshot
{
    /// <summary>Gets the number of consecutive pipeline failures.</summary>
    public int ConsecutiveFailures { get; init; }
    /// <summary>Gets the UTC timestamp of the last circuit-breaker open event, if any.</summary>
    public DateTime? LastBreakerOpenUtc { get; init; }
    /// <summary>Gets the UTC timestamp of the last circuit-breaker close (recovery) event, if any.</summary>
    public DateTime? LastBreakerClosedUtc { get; init; }
    /// <summary>Gets the UTC timestamp of the last recycle event, if any.</summary>
    public DateTime? LastRecycleUtc { get; init; }

    /// <summary>
    ///     Gets whether the circuit breaker is currently open for this (instance, host):
    ///     an open event has been recorded and no later close event has superseded it.
    ///     Note: Polly half-opens after <c>BreakDuration</c> without firing <c>OnClosed</c>
    ///     (which fires only on a successful probe), so an idle driver can read "open" until
    ///     the next call self-corrects it.
    /// </summary>
    public bool IsBreakerOpen =>
        LastBreakerOpenUtc is { } open &&
        (LastBreakerClosedUtc is not { } closed || open > closed);
    /// <summary>Gets the baseline process footprint in bytes.</summary>
    public long BaselineFootprintBytes { get; init; }
    /// <summary>Gets the current process footprint in bytes.</summary>
    public long CurrentFootprintBytes { get; init; }
    /// <summary>Gets the UTC timestamp when the counters were last updated.</summary>
    public DateTime LastSampledUtc { get; init; }

    /// <summary>
    ///     In-flight capability calls against this (instance, host). Bumped on call entry +
    ///     decremented on completion. Feeds <c>DriverInstanceResilienceStatus.CurrentBulkheadDepth</c>
    ///     for Admin <c>/hosts</c> — in-flight capability-call depth until the full
    ///     telemetry observer lands.
    /// </summary>
    public int CurrentInFlight { get; init; }
}
