using Serilog;
using Serilog.Core;
using Serilog.Events;

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

/// <summary>
///     Serilog sink that mirrors script log events at <see cref="LogEventLevel.Error"/>
///     or higher to a companion logger (typically the main <c>opcua-*.log</c>) at
///     <see cref="LogEventLevel.Warning"/>. Lets operators see script errors in the
///     primary server log without drowning it in Debug/Info/Warning noise from scripts.
/// </summary>
/// <remarks>
///     <para>
///         Registered alongside the dedicated <c>scripts-*.log</c> rolling file sink in
///         the root script-logger configuration — events below Error land only in the
///         scripts file; Error/Fatal events land in both the scripts file (at original
///         level) and the main log (downgraded to Warning since the main log's audience
///         is server operators, not script authors).
///     </para>
///     <para>
///         The forwarded message preserves the <c>ScriptName</c> property so operators
///         reading the main log can tell which script raised the error at a glance.
///         Original exception (if any) is attached so the main log's diagnostics keep
///         the full stack trace.
///     </para>
/// </remarks>
public sealed class ScriptLogCompanionSink : ILogEventSink
{
    private readonly ILogger _mainLogger;
    private readonly LogEventLevel _minMirrorLevel;

    /// <summary>Initializes a new instance of the ScriptLogCompanionSink.</summary>
    /// <param name="mainLogger">The companion logger to mirror events to.</param>
    /// <param name="minMirrorLevel">The minimum log level to mirror (default: Error).</param>
    public ScriptLogCompanionSink(ILogger mainLogger, LogEventLevel minMirrorLevel = LogEventLevel.Error)
    {
        _mainLogger = mainLogger ?? throw new ArgumentNullException(nameof(mainLogger));
        _minMirrorLevel = minMirrorLevel;
    }

    /// <summary>Emits a log event to the companion logger if it meets the minimum level.</summary>
    /// <param name="logEvent">The log event to emit.</param>
    public void Emit(LogEvent logEvent)
    {
        if (logEvent is null) return;
        if (logEvent.Level < _minMirrorLevel) return;

        var scriptName = "unknown";
        if ((logEvent.Properties.TryGetValue(ScriptLoggerFactory.ScriptNameProperty, out var prop)
             || logEvent.Properties.TryGetValue(ScriptLoggerFactory.ScriptIdProperty, out prop))
            && prop is ScalarValue sv && sv.Value is string s)
        {
            scriptName = s;
        }

        var rendered = logEvent.RenderMessage();
        if (logEvent.Exception is not null)
        {
            _mainLogger.Warning(logEvent.Exception,
                "[Script] {ScriptName} emitted {OriginalLevel}: {ScriptMessage}",
                scriptName, logEvent.Level, rendered);
        }
        else
        {
            _mainLogger.Warning(
                "[Script] {ScriptName} emitted {OriginalLevel}: {ScriptMessage}",
                scriptName, logEvent.Level, rendered);
        }
    }
}
