using System.Collections.Concurrent;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms;

namespace ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms.Tests;

/// <summary>Test fake implementation of <see cref="ITagUpstreamSource"/> for verifying subscription behavior.</summary>
public sealed class FakeUpstream : ITagUpstreamSource
{
    private readonly ConcurrentDictionary<string, DataValueSnapshot> _values = new(StringComparer.Ordinal);
    private readonly ConcurrentDictionary<string, List<Action<string, DataValueSnapshot>>> _subs
        = new(StringComparer.Ordinal);
    /// <summary>Gets the current count of active subscriptions.</summary>
    public int ActiveSubscriptionCount { get; private set; }

    /// <summary>Sets a tag value without notifying subscribers.</summary>
    /// <param name="path">The tag path to set.</param>
    /// <param name="value">The value to set for the tag.</param>
    /// <param name="statusCode">The OPC UA status code for the value.</param>
    public void Set(string path, object? value, uint statusCode = 0u)
    {
        var now = DateTime.UtcNow;
        _values[path] = new DataValueSnapshot(value, statusCode, now, now);
    }

    /// <summary>Sets a tag value and notifies all current subscribers.</summary>
    /// <param name="path">The tag path to set.</param>
    /// <param name="value">The value to set for the tag.</param>
    /// <param name="statusCode">The OPC UA status code for the value.</param>
    public void Push(string path, object? value, uint statusCode = 0u)
    {
        Set(path, value, statusCode);
        if (_subs.TryGetValue(path, out var list))
        {
            Action<string, DataValueSnapshot>[] snap;
            lock (list) { snap = list.ToArray(); }
            foreach (var obs in snap) obs(path, _values[path]);
        }
    }

    /// <summary>Reads the current value of a tag, or returns a bad-status snapshot if not set.</summary>
    /// <param name="path">The tag path to read.</param>
    public DataValueSnapshot ReadTag(string path)
        => _values.TryGetValue(path, out var v) ? v
            : new DataValueSnapshot(null, 0x80340000u, null, DateTime.UtcNow);

    /// <summary>Subscribes an observer to tag changes for the given path.</summary>
    /// <param name="path">The tag path to subscribe to.</param>
    /// <param name="observer">The observer callback to invoke on tag changes.</param>
    public IDisposable SubscribeTag(string path, Action<string, DataValueSnapshot> observer)
    {
        var list = _subs.GetOrAdd(path, _ => []);
        lock (list) { list.Add(observer); }
        ActiveSubscriptionCount++;
        return new Unsub(this, path, observer);
    }

    /// <summary>Disposable subscription handle that unsubscribes the observer when disposed.</summary>
    private sealed class Unsub : IDisposable
    {
        private readonly FakeUpstream _up;
        private readonly string _path;
        private readonly Action<string, DataValueSnapshot> _observer;
        /// <summary>Initializes the unsubscription handle with references needed to clean up the subscription.</summary>
        /// <param name="up">The upstream source containing the subscription list.</param>
        /// <param name="path">The tag path to unsubscribe from.</param>
        /// <param name="observer">The observer to remove from the subscription list.</param>
        public Unsub(FakeUpstream up, string path, Action<string, DataValueSnapshot> observer)
        { _up = up; _path = path; _observer = observer; }
        /// <summary>Removes the observer from the subscription list.</summary>
        public void Dispose()
        {
            if (_up._subs.TryGetValue(_path, out var list))
            {
                lock (list)
                {
                    if (list.Remove(_observer)) _up.ActiveSubscriptionCount--;
                }
            }
        }
    }
}
