using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

/// <summary>
///     Tier C opt-in periodic-recycle driver.
///     A tick method advanced by the caller (fed by a background timer in prod; by test clock
///     in unit tests) decides whether the configured interval has elapsed and, if so, drives the
///     supplied <see cref="IDriverSupervisor"/> to recycle the Host.
/// </summary>
/// <remarks>
///     Tier A/B drivers MUST NOT use this class — scheduled recycle for in-process drivers would
///     kill every OPC UA session and every co-hosted driver. The ctor throws when constructed
///     with any tier other than C to make the misuse structurally impossible.
///
///     <para>Keeps no background thread of its own — callers invoke <see cref="TickAsync"/> on
///     their ambient scheduler tick (Phase 6.1 Stream C's health-endpoint host runs one). That
///     decouples the unit under test from wall-clock time and thread-pool scheduling.</para>
/// </remarks>
public sealed class ScheduledRecycleScheduler
{
    private readonly TimeSpan _recycleInterval;
    private readonly IDriverSupervisor _supervisor;
    private readonly ILogger<ScheduledRecycleScheduler> _logger;
    private DateTime _nextRecycleUtc;

    /// <summary>
    ///     Construct the scheduler for a Tier C driver. Throws if <paramref name="tier"/> isn't C.
    /// </summary>
    /// <param name="tier">Driver tier; must be <see cref="DriverTier.C"/>.</param>
    /// <param name="recycleInterval">Interval between recycles (e.g. 7 days).</param>
    /// <param name="startUtc">Anchor time; next recycle fires at <paramref name="startUtc"/> + <paramref name="recycleInterval"/>.</param>
    /// <param name="supervisor">Supervisor that performs the actual recycle.</param>
    /// <param name="logger">Diagnostic sink.</param>
    public ScheduledRecycleScheduler(
        DriverTier tier,
        TimeSpan recycleInterval,
        DateTime startUtc,
        IDriverSupervisor supervisor,
        ILogger<ScheduledRecycleScheduler> logger)
    {
        if (tier != DriverTier.C)
            throw new ArgumentException(
                $"ScheduledRecycleScheduler is Tier C only (got {tier}). " +
                "In-process drivers must not use scheduled recycle; see decisions #74 and #145.",
                nameof(tier));

        if (recycleInterval <= TimeSpan.Zero)
            throw new ArgumentException("RecycleInterval must be positive.", nameof(recycleInterval));

        _recycleInterval = recycleInterval;
        _supervisor = supervisor;
        _logger = logger;
        _nextRecycleUtc = startUtc + recycleInterval;
    }

    /// <summary>Next scheduled recycle UTC. Advances by <see cref="RecycleInterval"/> on each fire.</summary>
    public DateTime NextRecycleUtc => _nextRecycleUtc;

    /// <summary>Recycle interval this scheduler was constructed with.</summary>
    public TimeSpan RecycleInterval => _recycleInterval;

    /// <summary>
    ///     Tick the scheduler forward. If <paramref name="utcNow"/> is past
    ///     <see cref="NextRecycleUtc"/>, requests a recycle from the supervisor and advances
    ///     <see cref="NextRecycleUtc"/> by exactly one interval. Returns true when a recycle fired.
    /// </summary>
    /// <param name="utcNow">The current UTC time.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>True if a recycle was triggered; false otherwise.</returns>
    public async Task<bool> TickAsync(DateTime utcNow, CancellationToken cancellationToken)
    {
        if (utcNow < _nextRecycleUtc)
            return false;

        _logger.LogInformation(
            "Scheduled recycle due for Tier C driver {DriverId} at {Now:o}; advancing next to {Next:o}.",
            _supervisor.DriverInstanceId, utcNow, _nextRecycleUtc + _recycleInterval);

        await _supervisor.RecycleAsync("Scheduled periodic recycle", cancellationToken).ConfigureAwait(false);
        _nextRecycleUtc += _recycleInterval;
        return true;
    }

    /// <summary>Request an immediate recycle outside the schedule (e.g. MemoryRecycle hard-breach escalation).</summary>
    /// <param name="reason">The reason for requesting an immediate recycle.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>A task representing the asynchronous recycle operation.</returns>
    public Task RequestRecycleNowAsync(string reason, CancellationToken cancellationToken) =>
        _supervisor.RecycleAsync(reason, cancellationToken);
}
