using Mbproxy.Admin;
using Mbproxy.Configuration;
using Mbproxy.Diagnostics;
using Mbproxy.Options;
using Microsoft.Extensions.Hosting.Systemd;
using Microsoft.Extensions.Hosting.WindowsServices;
using Serilog;
using Serilog.Events;

namespace Mbproxy;

internal static class HostingExtensions
{
    /// <summary>
    /// Registers the <c>"Mbproxy"</c> configuration section, binds it to
    /// <see cref="MbproxyOptions"/> via <c>IOptionsMonitor</c>, registers the schema-
    /// level <see cref="MbproxyOptionsValidator"/>, and registers the singleton
    /// <see cref="ServiceCounters"/> and <see cref="ConfigReconciler"/> so they can be
    /// injected into <see cref="Proxy.ProxyWorker"/>.
    /// </summary>
    public static IHostApplicationBuilder AddMbproxyOptions(this IHostApplicationBuilder builder)
    {
        builder.Services
            .AddOptions<MbproxyOptions>()
            .BindConfiguration("Mbproxy")
            .ValidateOnStart();

        builder.Services.AddSingleton<
            Microsoft.Extensions.Options.IValidateOptions<MbproxyOptions>,
            MbproxyOptionsValidator>();

        // Service-wide counters (read by the status page).
        builder.Services.AddSingleton<ServiceCounters>();

        // Per-PLC tag-value captures backing the connection-detail debug view.
        // Shared between the proxy hot path and the admin SignalR layer.
        builder.Services.AddSingleton<Proxy.TagCaptureRegistry>();

        // Hot-reload reconciler (singleton; subscribes to IOptionsMonitor.OnChange).
        builder.Services.AddSingleton<ConfigReconciler>();

        return builder;
    }

    /// <summary>
    /// Registers the admin endpoint services:
    /// <list type="bullet">
    ///   <item><see cref="AssemblyVersionAccessor"/> (singleton — reads version attribute once).</item>
    ///   <item><see cref="StatusSnapshotBuilder"/> (singleton — pure orchestration).</item>
    ///   <item><see cref="AdminEndpointHost"/> (singleton — owns the Kestrel admin server).</item>
    /// </list>
    /// Must be called after <see cref="AddMbproxyOptions"/> and after
    /// <c>AddHostedService&lt;ProxyWorker&gt;</c> (so ProxyWorker is available via DI).
    ///
    /// <para><see cref="AdminEndpointHost"/> is intentionally NOT registered via
    /// <c>AddHostedService</c>. <see cref="Proxy.ProxyWorker"/> drives its lifecycle
    /// directly so admin start/stop ordering matches the design contract (admin starts
    /// after listeners are up; admin stops AFTER the in-flight drain).</para>
    /// </summary>
    public static IHostApplicationBuilder AddMbproxyAdmin(this IHostApplicationBuilder builder)
    {
        builder.Services.AddSingleton<AssemblyVersionAccessor>();
        builder.Services.AddSingleton<StatusSnapshotBuilder>();
        // Tracks detail-page SignalR subscribers; drives on-demand capture arming.
        builder.Services.AddSingleton<PlcSubscriptionTracker>();
        builder.Services.AddSingleton<AdminEndpointHost>();
        return builder;
    }

    /// <summary>
    /// Configures Serilog from the <c>"Serilog"</c> configuration section, with console
    /// and rolling-file sinks as defaults.
    ///
    /// <para>This is the single composition-root point where the platform diagnostic
    /// sink for <c>Error</c>+ events is chosen. <see cref="DiagnosticSinkSelector"/>
    /// picks it from the current host:
    /// <list type="bullet">
    ///   <item>Windows Service → <see cref="Diagnostics.EventLogBridge"/> (Application
    ///         Event Log).</item>
    ///   <item>systemd service → <see cref="Diagnostics.SyslogBridge"/> (local syslog).</item>
    ///   <item>interactive / dev runs (any OS) → no platform sink.</item>
    /// </list>
    /// Both bridges silently no-op when their backing facility is unavailable, so a
    /// dev run never needs an Event Log source registered or a syslog socket.</para>
    /// </summary>
    public static IHostApplicationBuilder AddMbproxySerilog(this IHostApplicationBuilder builder)
    {
        var cfg = new LoggerConfiguration()
            .ReadFrom.Configuration(builder.Configuration);

        var sink = DiagnosticSinkSelector.Select(
            isWindows: OperatingSystem.IsWindows(),
            isWindowsService: WindowsServiceHelpers.IsWindowsService(),
            isSystemd: SystemdHelpers.IsSystemdService());

        cfg = sink switch
        {
            // EventLogBridge is [SupportedOSPlatform("windows")]; the extra
            // OperatingSystem.IsWindows() guard satisfies the platform analyzer
            // (DiagnosticSinkSelector already guarantees Windows for this case).
            DiagnosticSink.EventLog when OperatingSystem.IsWindows()
                => cfg.WriteTo.Sink(new EventLogBridge(enabled: true), LogEventLevel.Error),
            DiagnosticSink.Syslog
                => SyslogBridge.AttachTo(cfg),
            _ => cfg,
        };

        Log.Logger = cfg.CreateLogger();

        builder.Services.AddSerilog(dispose: true);

        return builder;
    }
}
