using Serilog.Context;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Core.Observability;

/// <summary>
///     Convenience wrapper around Serilog <see cref="LogContext"/> — attaches the set of
///     structured properties a capability call should carry (DriverInstanceId, DriverType,
///     CapabilityName, CorrelationId). Callers wrap their call-site body in a <c>using</c>
///     block; inner <c>Log.Information</c> / <c>Log.Warning</c> calls emit the context
///     automatically via the Serilog enricher chain.
/// </summary>
/// <remarks>
///     The correlation ID should be the OPC UA <c>RequestHeader.RequestHandle</c> when in-flight;
///     otherwise a short random GUID. Callers supply whichever is available.
/// </remarks>
public static class LogContextEnricher
{
    /// <summary>Attach the capability-call property set. Dispose the returned scope to pop.</summary>
    /// <param name="driverInstanceId">The unique identifier for the driver instance.</param>
    /// <param name="driverType">The driver type name.</param>
    /// <param name="capability">The driver capability being invoked.</param>
    /// <param name="correlationId">The correlation ID for tracing the call.</param>
    /// <returns>A disposable scope that pops the pushed properties when disposed.</returns>
    public static IDisposable Push(string driverInstanceId, string driverType, DriverCapability capability, string correlationId)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
        ArgumentException.ThrowIfNullOrWhiteSpace(driverType);
        ArgumentException.ThrowIfNullOrWhiteSpace(correlationId);

        var a = LogContext.PushProperty("DriverInstanceId", driverInstanceId);
        var b = LogContext.PushProperty("DriverType", driverType);
        var c = LogContext.PushProperty("CapabilityName", capability.ToString());
        var d = LogContext.PushProperty("CorrelationId", correlationId);
        return new CompositeScope(a, b, c, d);
    }

    /// <summary>
    ///     Generate a short correlation ID when no OPC UA RequestHandle is available.
    ///     12-hex-char slice of a GUID — long enough for log correlation, short enough to
    ///     scan visually.
    /// </summary>
    /// <returns>A 12-hex-character correlation ID.</returns>
    public static string NewCorrelationId() => Guid.NewGuid().ToString("N")[..12];

    private sealed class CompositeScope : IDisposable
    {
        private readonly IDisposable[] _inner;

        /// <summary>Initializes a new instance of the CompositeScope with the given inner scopes.</summary>
        /// <param name="inner">The inner scopes to composite and dispose together.</param>
        public CompositeScope(params IDisposable[] inner) => _inner = inner;

        /// <summary>Disposes all inner scopes in reverse order.</summary>
        public void Dispose()
        {
            // Reverse-order disposal matches Serilog's stack semantics.
            for (var i = _inner.Length - 1; i >= 0; i--)
                _inner[i].Dispose();
        }
    }
}
