using Akka.Actor;
using Akka.Event;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

/// <summary>
///     Bridges a driver that implements <see cref="IDependencyConsumer"/> onto the per-node
///     <see cref="DependencyMuxActor"/>. Spawned as a child of <c>DriverHostActor</c> for each such
///     driver: on start (and on every apply via <see cref="ReRegister"/>) it registers the driver's
///     declared <see cref="IDependencyConsumer.DependencyRefs"/> with the mux, and forwards each
///     <see cref="VirtualTagActor.DependencyValueChanged"/> the mux fans out into
///     <see cref="IDependencyConsumer.OnDependencyValue"/>.
///
///     <para>
///         This is the exact seam a <c>VirtualTagActor</c> uses to receive its inputs, generalised so a
///         host-blind driver (the <c>Calculation</c> pseudo-driver) can read other tags' live values with
///         no cross-driver plumbing. The mux fans out value + timestamp only (quality is stripped at the
///         mux boundary, same as for virtual tags), so the forwarded status code is <c>Good</c> (0).
///     </para>
/// </summary>
public sealed class DependencyConsumerMuxAdapter : ReceiveActor
{
    /// <summary>Sent by the host after an apply so the adapter re-reads the driver's (possibly changed)
    /// dependency refs and re-registers them with the mux.</summary>
    public sealed record ReRegister;

    private readonly IDependencyConsumer _consumer;
    private readonly IActorRef _mux;
    private readonly ILoggingAdapter _log = Context.GetLogger();

    /// <summary>Creates props for a <see cref="DependencyConsumerMuxAdapter"/>.</summary>
    /// <param name="consumer">The dependency-consuming driver to feed.</param>
    /// <param name="mux">The per-node dependency mux to register interest with.</param>
    /// <returns>Props for the adapter.</returns>
    public static Props Props(IDependencyConsumer consumer, IActorRef mux) =>
        Akka.Actor.Props.Create(() => new DependencyConsumerMuxAdapter(consumer, mux));

    /// <summary>Initializes a new <see cref="DependencyConsumerMuxAdapter"/>.</summary>
    /// <param name="consumer">The dependency-consuming driver to feed.</param>
    /// <param name="mux">The per-node dependency mux to register interest with.</param>
    public DependencyConsumerMuxAdapter(IDependencyConsumer consumer, IActorRef mux)
    {
        _consumer = consumer ?? throw new ArgumentNullException(nameof(consumer));
        _mux = mux ?? throw new ArgumentNullException(nameof(mux));

        Receive<VirtualTagActor.DependencyValueChanged>(OnDependencyValueChanged);
        Receive<ReRegister>(_ => RegisterInterest());
    }

    /// <inheritdoc />
    protected override void PreStart() => RegisterInterest();

    /// <inheritdoc />
    protected override void PostStop() => _mux.Tell(new DependencyMuxActor.UnregisterInterest(Self));

    private void RegisterInterest()
    {
        var refs = _consumer.DependencyRefs.ToList();
        // A RegisterInterest replaces the prior interest set for this subscriber (mux is idempotent on
        // re-register), so re-registering after an apply cleanly adopts an added/removed ref.
        _mux.Tell(new DependencyMuxActor.RegisterInterest(refs, Self));
        _log.Debug("DependencyConsumerMuxAdapter: registered {Count} dependency ref(s)", refs.Count);
    }

    private void OnDependencyValueChanged(VirtualTagActor.DependencyValueChanged msg)
    {
        // Quality is stripped at the mux boundary (value + timestamp only), so forward Good (0).
        _consumer.OnDependencyValue(msg.TagId, msg.Value, statusCode: 0u, msg.TimestampUtc);
    }
}
