using Opc.Ua;
using Opc.Ua.Client;
using Serilog;

namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Adapters;

/// <summary>
///     Production subscription adapter wrapping a real OPC UA Subscription.
/// </summary>
internal sealed class DefaultSubscriptionAdapter : ISubscriptionAdapter
{
    private static readonly ILogger Logger = Log.ForContext<DefaultSubscriptionAdapter>();
    private readonly Dictionary<uint, MonitoredItem> _monitoredItems = new();
    private readonly Subscription _subscription;

    /// <summary>
    /// Wraps a live OPC UA subscription so client code can manage monitored items through a testable abstraction.
    /// </summary>
    /// <param name="subscription">The underlying OPC UA subscription that owns monitored items for this client workflow.</param>
    public DefaultSubscriptionAdapter(Subscription subscription)
    {
        _subscription = subscription;
    }

    /// <inheritdoc />
    public uint SubscriptionId => _subscription.Id;

    /// <inheritdoc />
    public async Task<uint> AddDataChangeMonitoredItemAsync(
        NodeId nodeId, int samplingIntervalMs, Action<string, DataValue> onDataChange, CancellationToken ct)
    {
        var item = new MonitoredItem(_subscription.DefaultItem)
        {
            StartNodeId = nodeId,
            DisplayName = nodeId.ToString(),
            SamplingInterval = samplingIntervalMs
        };

        item.Notification += (_, e) =>
        {
            if (e.NotificationValue is MonitoredItemNotification notification)
                onDataChange(nodeId.ToString(), notification.Value);
        };

        _subscription.AddItem(item);
        await _subscription.ApplyChangesAsync(ct);

        var handle = item.ClientHandle;
        _monitoredItems[handle] = item;

        Logger.Debug("Added data change monitored item for {NodeId}, handle={Handle}", nodeId, handle);
        return handle;
    }

    /// <inheritdoc />
    public async Task RemoveMonitoredItemAsync(uint clientHandle, CancellationToken ct)
    {
        if (!_monitoredItems.TryGetValue(clientHandle, out var item))
            return;

        _subscription.RemoveItem(item);
        await _subscription.ApplyChangesAsync(ct);
        _monitoredItems.Remove(clientHandle);

        Logger.Debug("Removed monitored item handle={Handle}", clientHandle);
    }

    /// <inheritdoc />
    public async Task<uint> AddEventMonitoredItemAsync(
        NodeId nodeId, int samplingIntervalMs, EventFilter filter, Action<EventFieldList> onEvent, CancellationToken ct)
    {
        var item = new MonitoredItem(_subscription.DefaultItem)
        {
            StartNodeId = nodeId,
            DisplayName = "AlarmMonitor",
            SamplingInterval = samplingIntervalMs,
            NodeClass = NodeClass.Object,
            AttributeId = Attributes.EventNotifier,
            Filter = filter
        };

        item.Notification += (_, e) =>
        {
            if (e.NotificationValue is EventFieldList eventFields) onEvent(eventFields);
        };

        _subscription.AddItem(item);
        await _subscription.ApplyChangesAsync(ct);

        var handle = item.ClientHandle;
        _monitoredItems[handle] = item;

        Logger.Debug("Added event monitored item for {NodeId}, handle={Handle}", nodeId, handle);
        return handle;
    }

    /// <inheritdoc />
    public async Task ConditionRefreshAsync(CancellationToken ct)
    {
        await _subscription.ConditionRefreshAsync(ct);
    }

    /// <inheritdoc />
    public async Task DeleteAsync(CancellationToken ct)
    {
        try
        {
            // Forward the caller's token so the delete can be cancelled.
            await _subscription.DeleteAsync(true, ct);
        }
        catch (Exception ex)
        {
            Logger.Warning(ex, "Error deleting subscription");
        }

        _monitoredItems.Clear();
    }

    /// <summary>
    /// Releases the wrapped OPC UA subscription and clears tracked monitored items held by the adapter.
    /// </summary>
    public void Dispose()
    {
        try
        {
            _subscription.Delete(true);
        }
        catch
        {
        }

        _monitoredItems.Clear();
    }
}
