using Akka.Actor;
using Akka.Cluster.Tools.PublishSubscribe;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Logging;
using ZB.MOM.WW.OtOpcUa.Core.Scripting;
using ZB.MOM.WW.OtOpcUa.Runtime.Telemetry;
using ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags;

namespace ZB.MOM.WW.OtOpcUa.Runtime.Scripting;

/// <summary>
///     Concrete <see cref="IScriptLogPublisher"/> that routes each <see cref="ScriptLogEntry"/>
///     onto the Akka DistributedPubSub <c>script-logs</c> topic
///     (<see cref="VirtualTagActor.ScriptLogsTopic"/>). <c>ScriptLogSignalRBridge</c> subscribes
///     to that topic so entries reach the live Script-log Admin UI page.
/// </summary>
/// <remarks>
///     <para>
///         The <see cref="ActorSystem"/> is resolved lazily through a <see cref="Func{ActorSystem}"/>
///         so constructing the publisher never races Akka startup — the system only needs to exist
///         by the time the first script logs, not at DI-registration time.
///     </para>
///     <para>
///         This type sits behind a Serilog sink (<see cref="ScriptLogTopicSink"/>); a sink must
///         never throw back into the logging pipeline, so every failure path here is swallowed
///         (best-effort logged at Debug to the main Serilog logger).
///     </para>
/// </remarks>
public sealed class DpsScriptLogPublisher : IScriptLogPublisher
{
    private readonly Func<ActorSystem> _system;
    private readonly ITelemetryLocalHub _hub;

    /// <summary>Initializes a new instance of the <see cref="DpsScriptLogPublisher"/> class.</summary>
    /// <param name="system">
    ///     Lazy accessor for the running <see cref="ActorSystem"/>. Invoked on each
    ///     <see cref="Publish"/> so registration does not depend on Akka having started yet.
    /// </param>
    /// <param name="hub">The node-local live-telemetry hub each script-log entry is also emitted into (Phase 5).</param>
    /// <exception cref="ArgumentNullException">Thrown when <paramref name="system"/> or <paramref name="hub"/> is <c>null</c>.</exception>
    public DpsScriptLogPublisher(Func<ActorSystem> system, ITelemetryLocalHub hub)
    {
        _system = system ?? throw new ArgumentNullException(nameof(system));
        _hub = hub ?? throw new ArgumentNullException(nameof(hub));
    }

    /// <inheritdoc />
    public void Publish(ScriptLogEntry entry)
    {
        try
        {
            var mediator = DistributedPubSub.Get(_system()).Mediator;
            mediator.Tell(new Publish(VirtualTagActor.ScriptLogsTopic, entry));
            // Phase 5: fan the same entry into the node-local live-telemetry hub (no-op until a gRPC
            // client subscribes). The DPS publish above is unchanged — the hub is a strictly additive tap.
            _hub.Emit(new TelemetryItem.Script(entry));
        }
        catch (Exception ex)
        {
            // A logging sink must never throw into the logging pipeline. Best-effort note to
            // the main log at Debug; nothing actionable for the script author here.
            Serilog.Log.Debug(ex, "DpsScriptLogPublisher could not route a script log entry to the cluster bus.");
        }
    }
}
