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

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

/// <summary>
///     Tier-agnostic memory-footprint tracker. Captures the post-initialize <b>baseline</b>
///     from the first samples after <c>IDriver.InitializeAsync</c>, then classifies each
///     subsequent sample against a hybrid soft/hard threshold per
///     <c>docs/v2/plan.md</c> — <c>soft = max(multiplier × baseline, baseline + floor)</c>,
///     <c>hard = 2 × soft</c>.
/// </summary>
/// <remarks>
///     <para>This tracker <b>never kills a process</b>. Soft and hard breaches
///     log + surface to the Admin UI via <c>DriverInstanceResilienceStatus</c>. The matching
///     process-level recycle protection lives in a separate <c>MemoryRecycle</c> that activates
///     for Tier C drivers only (where the driver runs out-of-process behind a supervisor that
///     can safely restart it without tearing down the OPC UA session or co-hosted in-proc
///     drivers).</para>
///
///     <para>Baseline capture: the tracker starts in <see cref="TrackingPhase.WarmingUp"/> for
///     <see cref="BaselineWindow"/> (default 5 min). During that window samples are collected;
///     the baseline is computed as the median once the window elapses. Before that point every
///     classification returns <see cref="MemoryTrackingAction.Warming"/>.</para>
/// </remarks>
public sealed class MemoryTracking
{
    private readonly DriverTier _tier;
    private readonly TimeSpan _baselineWindow;
    private readonly List<long> _warmupSamples = [];
    private long _baselineBytes;
    private TrackingPhase _phase = TrackingPhase.WarmingUp;
    private DateTime? _warmupStartUtc;

    /// <summary>Tier-default multiplier/floor constants.</summary>
    /// <param name="tier">The driver tier.</param>
    /// <returns>The multiplier and floor-bytes constants for the tier.</returns>
    public static (int Multiplier, long FloorBytes) GetTierConstants(DriverTier tier) => tier switch
    {
        DriverTier.A => (Multiplier: 3, FloorBytes: 50L * 1024 * 1024),
        DriverTier.B => (Multiplier: 3, FloorBytes: 100L * 1024 * 1024),
        DriverTier.C => (Multiplier: 2, FloorBytes: 500L * 1024 * 1024),
        _ => throw new ArgumentOutOfRangeException(nameof(tier), tier, $"No memory-tracking constants defined for tier {tier}."),
    };

    /// <summary>Window over which post-init samples are collected to compute the baseline.</summary>
    public TimeSpan BaselineWindow => _baselineWindow;

    /// <summary>Current phase: <see cref="TrackingPhase.WarmingUp"/> or <see cref="TrackingPhase.Steady"/>.</summary>
    public TrackingPhase Phase => _phase;

    /// <summary>Captured baseline; 0 until warmup completes.</summary>
    public long BaselineBytes => _baselineBytes;

    /// <summary>Effective soft threshold (zero while warming up).</summary>
    public long SoftThresholdBytes => _baselineBytes == 0 ? 0 : ComputeSoft(_tier, _baselineBytes);

    /// <summary>Effective hard threshold = 2 × soft (zero while warming up).</summary>
    public long HardThresholdBytes => _baselineBytes == 0 ? 0 : ComputeSoft(_tier, _baselineBytes) * 2;

    /// <summary>Initializes a new instance of the <see cref="MemoryTracking"/> class.</summary>
    /// <param name="tier">The driver tier for threshold constants.</param>
    /// <param name="baselineWindow">Optional custom baseline window duration (default 5 minutes).</param>
    public MemoryTracking(DriverTier tier, TimeSpan? baselineWindow = null)
    {
        _tier = tier;
        _baselineWindow = baselineWindow ?? TimeSpan.FromMinutes(5);
    }

    /// <summary>
    ///     Submit a memory-footprint sample. Returns the action the caller should surface.
    ///     During warmup, always returns <see cref="MemoryTrackingAction.Warming"/> and accumulates
    ///     samples; once the window elapses the first steady-phase sample triggers baseline capture
    ///     (median of warmup samples).
    /// </summary>
    /// <param name="footprintBytes">The current memory footprint in bytes.</param>
    /// <param name="utcNow">The current UTC time.</param>
    /// <returns>The tracking action the caller should surface for this sample.</returns>
    public MemoryTrackingAction Sample(long footprintBytes, DateTime utcNow)
    {
        if (_phase == TrackingPhase.WarmingUp)
        {
            _warmupStartUtc ??= utcNow;
            _warmupSamples.Add(footprintBytes);
            if (utcNow - _warmupStartUtc.Value >= _baselineWindow && _warmupSamples.Count > 0)
            {
                _baselineBytes = ComputeMedian(_warmupSamples);
                _phase = TrackingPhase.Steady;
            }
            else
            {
                return MemoryTrackingAction.Warming;
            }
        }

        if (footprintBytes >= HardThresholdBytes) return MemoryTrackingAction.HardBreach;
        if (footprintBytes >= SoftThresholdBytes) return MemoryTrackingAction.SoftBreach;
        return MemoryTrackingAction.None;
    }

    private static long ComputeSoft(DriverTier tier, long baseline)
    {
        var (multiplier, floor) = GetTierConstants(tier);
        return Math.Max(multiplier * baseline, baseline + floor);
    }

    private static long ComputeMedian(List<long> samples)
    {
        var sorted = samples.Order().ToArray();
        var mid = sorted.Length / 2;
        return sorted.Length % 2 == 1
            ? sorted[mid]
            : (sorted[mid - 1] + sorted[mid]) / 2;
    }
}

/// <summary>Phase of a <see cref="MemoryTracking"/> lifecycle.</summary>
public enum TrackingPhase
{
    /// <summary>Collecting post-init samples; baseline not yet computed.</summary>
    WarmingUp,

    /// <summary>Baseline captured; every sample classified against soft/hard thresholds.</summary>
    Steady,
}

/// <summary>Classification the tracker returns per sample.</summary>
public enum MemoryTrackingAction
{
    /// <summary>Baseline not yet captured; sample collected, no threshold check.</summary>
    Warming,

    /// <summary>Below soft threshold.</summary>
    None,

    /// <summary>Between soft and hard thresholds — log + surface, no action.</summary>
    SoftBreach,

    /// <summary>
    ///     ≥ hard threshold. Log + surface + (Tier C only, via <c>MemoryRecycle</c>) request
    ///     process recycle via the driver supervisor. Tier A/B breach never invokes any
    ///     kill path.
    /// </summary>
    HardBreach,
}
