using Akka;
using Akka.Actor;
using Akka.Streams;
using Akka.Streams.Dsl;
using Microsoft.Extensions.Logging;
using ScadaLink.Communication.Grpc;
using ScadaLink.Commons.Messages.Streaming;

namespace ScadaLink.SiteRuntime.Streaming;

/// <summary>
/// WP-23: Site-Wide Akka Stream — manages a broadcast stream for attribute value
/// and alarm state changes. Instance Actors publish events via fire-and-forget Tell.
/// Subscribers get per-subscriber bounded buffers with drop-oldest overflow.
///
/// Filterable by instance name for debug view (WP-25).
/// Implements ISiteStreamSubscriber so the gRPC server can subscribe actors
/// to instance events without referencing SiteRuntime directly.
/// </summary>
public class SiteStreamManager : ISiteStreamSubscriber
{
    private ActorSystem? _system;
    private readonly int _bufferSize;
    private readonly ILogger<SiteStreamManager> _logger;
    private readonly object _lock = new();

    private IActorRef? _sourceActor;
    private readonly Dictionary<string, SubscriptionInfo> _subscriptions = new();

    public SiteStreamManager(
        SiteRuntimeOptions options,
        ILogger<SiteStreamManager> logger)
    {
        _bufferSize = options.StreamBufferSize;
        _logger = logger;
    }

    /// <summary>
    /// Initializes the stream source. Must be called after ActorSystem is ready.
    /// The ActorSystem is passed here rather than via the constructor so that
    /// SiteStreamManager can be created by DI before the actor system exists.
    /// </summary>
    public void Initialize(ActorSystem system)
    {
        _system = system;
        var materializer = _system.Materializer();

        var source = Source.ActorRef<ISiteStreamEvent>(
            _bufferSize,
            OverflowStrategy.DropHead);

        var (actorRef, _) = source
            .PreMaterialize(materializer);

        _sourceActor = actorRef;

        _logger.LogInformation(
            "SiteStreamManager initialized with buffer size {BufferSize}", _bufferSize);
    }

    /// <summary>
    /// Publishes an attribute value change to the stream.
    /// Fire-and-forget — never blocks the calling actor.
    /// </summary>
    public void PublishAttributeValueChanged(AttributeValueChanged changed)
    {
        _sourceActor?.Tell(changed);

        // Also forward to filtered subscribers
        ForwardToSubscribers(changed.InstanceUniqueName, changed);
    }

    /// <summary>
    /// Publishes an alarm state change to the stream.
    /// Fire-and-forget — never blocks the calling actor.
    /// </summary>
    public void PublishAlarmStateChanged(AlarmStateChanged changed)
    {
        _sourceActor?.Tell(changed);

        // Also forward to filtered subscribers
        ForwardToSubscribers(changed.InstanceUniqueName, changed);
    }

    /// <summary>
    /// WP-25: Subscribe to events for a specific instance (debug view).
    /// Returns a subscription ID for unsubscribing.
    /// </summary>
    public string Subscribe(string instanceName, IActorRef subscriber)
    {
        var subscriptionId = Guid.NewGuid().ToString();

        lock (_lock)
        {
            _subscriptions[subscriptionId] = new SubscriptionInfo(
                instanceName, subscriber, DateTimeOffset.UtcNow);
        }

        _logger.LogDebug(
            "Subscriber {SubscriptionId} registered for instance {Instance}",
            subscriptionId, instanceName);

        return subscriptionId;
    }

    /// <summary>
    /// WP-25: Unsubscribe from instance events.
    /// </summary>
    public bool Unsubscribe(string subscriptionId)
    {
        lock (_lock)
        {
            var removed = _subscriptions.Remove(subscriptionId);
            if (removed)
            {
                _logger.LogDebug("Subscriber {SubscriptionId} removed", subscriptionId);
            }
            return removed;
        }
    }

    /// <summary>
    /// WP-25: Remove all subscriptions for a specific subscriber actor.
    /// Called when connection is interrupted.
    /// </summary>
    public void RemoveSubscriber(IActorRef subscriber)
    {
        lock (_lock)
        {
            var toRemove = _subscriptions
                .Where(kvp => kvp.Value.Subscriber.Equals(subscriber))
                .Select(kvp => kvp.Key)
                .ToList();

            foreach (var id in toRemove)
            {
                _subscriptions.Remove(id);
            }

            if (toRemove.Count > 0)
            {
                _logger.LogDebug(
                    "Removed {Count} subscriptions for disconnected subscriber", toRemove.Count);
            }
        }
    }

    /// <summary>
    /// Returns the count of active subscriptions (for diagnostics/testing).
    /// </summary>
    public int SubscriptionCount
    {
        get { lock (_lock) { return _subscriptions.Count; } }
    }

    private void ForwardToSubscribers(string instanceName, object message)
    {
        lock (_lock)
        {
            foreach (var sub in _subscriptions.Values)
            {
                if (sub.InstanceName == instanceName)
                {
                    // Fire-and-forget to subscriber
                    sub.Subscriber.Tell(message);
                }
            }
        }
    }

    private record SubscriptionInfo(
        string InstanceName,
        IActorRef Subscriber,
        DateTimeOffset SubscribedAt);
}

/// <summary>
/// Marker interface for events published to the site stream.
/// </summary>
public interface ISiteStreamEvent { }
