using System.Diagnostics;
using System.Runtime.Versioning;
using Serilog.Core;
using Serilog.Events;

namespace Mbproxy.Diagnostics;

/// <summary>
/// Pure message-shaping helpers for the Windows Event Log. Kept on a separate,
/// non-platform-annotated type — <em>not</em> on <see cref="EventLogBridge"/>,
/// which is <c>[SupportedOSPlatform("windows")]</c> — so the truncation logic is
/// unit-testable on any OS without tripping the platform-compatibility analyzer.
/// </summary>
internal static class EventLogMessage
{
    /// <summary>The Windows Event Log single-entry limit, in bytes (32 KB).</summary>
    public const int MaxBytes = 32 * 1024;

    /// <summary>
    /// Truncates <paramref name="message"/> so its UTF-16 byte length stays within
    /// <see cref="MaxBytes"/>, appending an ellipsis when truncation occurs. Shorter
    /// messages are returned unchanged.
    /// </summary>
    public static string TruncateToLimit(string message)
    {
        // Rough UTF-16 upper bound: 2 bytes per char.
        if (message.Length * 2 <= MaxBytes) return message;

        int charLimit = MaxBytes / 2 - 3; // leave room for the "..." suffix
        return message[..charLimit] + "...";
    }
}

/// <summary>
/// Serilog sink that writes events at level Error and above to the Windows Event Log
/// under source <c>mbproxy</c>.
///
/// <para>This sink is only active when the service is running as a Windows Service
/// (<see cref="Microsoft.Extensions.Hosting.WindowsServices.WindowsServiceHelpers.IsWindowsService"/>
/// returns <c>true</c>). Under <c>dotnet run</c> / test / interactive launch, the sink is
/// a no-op so that the Event Log source registration (which requires admin rights) is not
/// required in development.</para>
///
/// <para>The Event Log source <c>mbproxy</c> must be created by <c>install.ps1</c> before
/// the service starts. The bridge does NOT attempt to create the source at runtime — the
/// service account may not hold the required admin rights.</para>
///
/// <para>Messages are capped at 32 KB (the Windows Event Log single-entry limit).</para>
/// </summary>
[SupportedOSPlatform("windows")]
internal sealed class EventLogBridge : ILogEventSink
{
    private const string Source = "mbproxy";
    private const string LogName = "Application";

    private readonly bool _enabled;
    // Cache the source-exists check at construction so Emit doesn't hit the registry on
    // every Error+ log line. A missing source after start requires a service restart to
    // pick up; in practice install.ps1 registers it once at install.
    private readonly bool _sourceExists;

    public EventLogBridge(bool enabled)
    {
        _enabled = enabled;
        if (_enabled)
        {
            try { _sourceExists = EventLog.SourceExists(Source); }
            catch { _sourceExists = false; }
        }
    }

    /// <inheritdoc/>
    public void Emit(LogEvent logEvent)
    {
        if (!_enabled) return;
        if (logEvent.Level < LogEventLevel.Error) return;

        // Cached at construction — silently swallow if the source isn't registered.
        // The service account may not be able to create it and we must not crash the logger.
        if (!_sourceExists) return;

        string message = logEvent.RenderMessage();

        // Append exception detail when present.
        if (logEvent.Exception is not null)
        {
            message += Environment.NewLine + logEvent.Exception;
        }

        // Truncate to the Event Log single-entry limit.
        message = EventLogMessage.TruncateToLimit(message);

        var type = logEvent.Level switch
        {
            LogEventLevel.Fatal       => EventLogEntryType.Error,
            LogEventLevel.Error       => EventLogEntryType.Error,
            LogEventLevel.Warning     => EventLogEntryType.Warning,
            _                         => EventLogEntryType.Information,
        };

        try
        {
            EventLog.WriteEntry(Source, message, type);
        }
        catch
        {
            // Swallow: if the Event Log write fails (e.g., source not registered,
            // quota exceeded) we must not crash the application or recurse.
        }
    }
}
