using Microsoft.Extensions.Logging;
using Opc.Ua;
using Opc.Ua.Server;
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;

namespace ZB.MOM.WW.OtOpcUa.OpcUaServer;

/// <summary>
/// Production <see cref="IServiceLevelPublisher"/> that writes the OPC UA Server object's
/// <c>ServiceLevel</c> Variable through the SDK. Clients reading
/// <c>VariableIds.Server_ServiceLevel</c> see the live value updated whenever the redundancy
/// state changes — that's the standard OPC UA non-transparent-redundancy signal callers use
/// to pick a primary.
///
/// Uses <see cref="IServerInternal.ServerObject"/> (a <see cref="ServerObjectState"/>) and
/// its <see cref="ServerObjectState.ServiceLevel"/> child variable, which the SDK populates
/// automatically during <see cref="DiagnosticsNodeManager"/> initialization. Writes are
/// guarded by <see cref="IServerInternal.DiagnosticsLock"/> so concurrent diagnostics scans
/// from the SDK don't fight with our update.
/// </summary>
public sealed class SdkServiceLevelPublisher : IServiceLevelPublisher
{
    private readonly IServerInternal _serverInternal;
    private readonly ILogger<SdkServiceLevelPublisher> _logger;

    /// <summary>Initializes a new instance of the SdkServiceLevelPublisher class.</summary>
    /// <param name="serverInternal">The OPC UA server internal interface.</param>
    /// <param name="logger">The logger instance.</param>
    public SdkServiceLevelPublisher(IServerInternal serverInternal, ILogger<SdkServiceLevelPublisher> logger)
    {
        _serverInternal = serverInternal;
        _logger = logger;
    }

    /// <inheritdoc />
    public void Publish(byte serviceLevel)
    {
        var node = _serverInternal.ServerObject?.ServiceLevel;
        if (node is null)
        {
            _logger.LogWarning("SdkServiceLevelPublisher: ServerObject.ServiceLevel unavailable; skipping write");
            return;
        }

        try
        {
            lock (_serverInternal.DiagnosticsLock)
            {
                node.Value = serviceLevel;
                node.Timestamp = DateTime.UtcNow;
                node.StatusCode = StatusCodes.Good;
                node.ClearChangeMasks(_serverInternal.DefaultSystemContext, includeChildren: false);
            }
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "SdkServiceLevelPublisher: write to Server.ServiceLevel threw");
        }
    }
}
