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

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

/// <summary>
///     Concrete <see cref="IDriverCapabilityInvokerFactory"/> that builds a per-driver-instance
///     <see cref="CapabilityInvoker"/> over the process-singleton pipeline builder + status tracker.
///     The fused Host registers this (it references <c>Core</c> + Polly); the Runtime dispatch layer
///     resolves the abstraction from DI and never sees this type.
/// </summary>
/// <remarks>
///     <para>Resilience policy is the driver type's <b>tier default</b> (resolved via the injected
///     tier resolver, backed by <c>DriverFactoryRegistry.GetTier</c>) with the per-instance
///     <c>DriverInstance.ResilienceConfig</c> JSON <b>layered on top</b> when the deployment artifact
///     carries one (<c>DriverInstanceSpec.ResilienceConfig</c>, threaded in by the Runtime
///     <c>DriverHostActor</c> at spawn). A missing/empty/malformed config degrades to pure tier
///     defaults — a bad ResilienceConfig never bricks a driver.</para>
///
///     <para>Options are snapshotted once per <see cref="Create"/> (a driver actor's options are fixed
///     for its lifetime — a ResilienceConfig change respawns the child), so the invoker's per-call
///     options accessor is allocation-free on the hot path. The pipeline builder caches pipelines keyed on
///     <c>(instance, host, capability, generation)</c> and ignores options on a cache hit; each
///     <see cref="Create"/> stamps a fresh monotonic <b>generation</b> (01/S-7) so the new invoker can only
///     read pipelines it built — a lingering old child's post-invalidate re-cache lands under its own older
///     generation and is unreachable. <see cref="Create"/> still
///     <see cref="DriverResiliencePipelineBuilder.Invalidate"/>s the instance's pipelines as cleanup (bounds
///     the cache), but correctness now rests on the generation key, not the invalidate timing.</para>
/// </remarks>
public sealed class DriverCapabilityInvokerFactory : IDriverCapabilityInvokerFactory
{
    private readonly DriverResiliencePipelineBuilder _builder;
    private readonly DriverResilienceStatusTracker _statusTracker;
    private readonly Func<string, DriverTier> _tierResolver;
    private readonly ILogger? _logger;
    private long _generation;

    /// <summary>Construct the factory over the shared resilience infrastructure.</summary>
    /// <param name="builder">Process-singleton Polly pipeline builder (shared pipeline cache).</param>
    /// <param name="statusTracker">Process-singleton resilience status tracker feeding Admin <c>/hosts</c>.</param>
    /// <param name="tierResolver">Resolves a driver type name to its <see cref="DriverTier"/> (e.g. <c>DriverFactoryRegistry.GetTier</c>).</param>
    /// <param name="logger">Optional logger; when non-null, a ResilienceConfig parse diagnostic (malformed JSON,
    ///     unknown capability, misapplied Tier-C recycle) is logged as a warning at <see cref="Create"/> time.</param>
    public DriverCapabilityInvokerFactory(
        DriverResiliencePipelineBuilder builder,
        DriverResilienceStatusTracker statusTracker,
        Func<string, DriverTier> tierResolver,
        ILogger? logger = null)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(statusTracker);
        ArgumentNullException.ThrowIfNull(tierResolver);
        _builder = builder;
        _statusTracker = statusTracker;
        _tierResolver = tierResolver;
        _logger = logger;
    }

    /// <inheritdoc />
    public IDriverCapabilityInvoker Create(string driverInstanceId, string driverType, string? resilienceConfigJson = null)
    {
        var tier = _tierResolver(driverType);
        var options = DriverResilienceOptionsParser.ParseOrDefaults(tier, resilienceConfigJson, out var diagnostic);
        if (diagnostic is not null)
        {
            _logger?.LogWarning(
                "Driver resilience config for instance={DriverInstanceId} type={DriverType}: {Diagnostic}",
                driverInstanceId, driverType, diagnostic);
        }

        // Stamp a fresh options generation for this invoker. The generation is part of the pipeline-cache
        // key (01/S-7), so this invoker can only ever read pipelines it built — a lingering old child's
        // late re-cache lands under its OWN (older) generation and is unreachable here. Correctness rests on
        // the epoch key, not on the timing of the invalidate below.
        var generation = Interlocked.Increment(ref _generation);

        // Cleanup (no longer the correctness mechanism): drop this instance's cached pipelines across all
        // generations so lingering old-generation entries don't accumulate. No-op on first spawn.
        _builder.Invalidate(driverInstanceId);

        return new CapabilityInvoker(
            _builder,
            driverInstanceId,
            optionsAccessor: () => options,
            driverType: driverType,
            statusTracker: _statusTracker,
            optionsGeneration: generation);
    }
}
