using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace AyCode.Services.SignalRs;

/// <summary>
/// Client-side registration extension for the <see cref="AyCodeBinaryHubProtocol"/> (<c>"acbinary"</c>).
/// Mirrors the ASP.NET Core idiomatic pattern of <c>AddJsonProtocol(...)</c> /
/// <c>AddMessagePackProtocol(...)</c>.
/// <para>
/// For the server-side equivalent see <c>AcSignalRServerProtocolExtensions</c> in
/// <c>AyCode.Services.Server</c> — kept separate to avoid dragging the server SignalR assembly
/// (<c>Microsoft.AspNetCore.SignalR.Core</c>) into pure client projects (MAUI, WASM).
/// </para>
/// </summary>
public static class AcSignalRProtocolExtensions
{
    /// <summary>
    /// Registers <see cref="AyCodeBinaryHubProtocol"/> as the protocol for a client <see cref="HubConnection"/>.
    /// Resolves <see cref="AcBinaryHubProtocolOptions"/> from <see cref="IOptions{TOptions}"/> in the
    /// <see cref="HubConnectionBuilder"/>'s inner DI scope.
    /// <para>
    /// ⚠️ <b>Limitation:</b> the outer ASP.NET Core / MAUI / WASM <c>services.Configure&lt;AcBinaryHubProtocolOptions&gt;(...)</c>
    /// registrations do <i>not</i> flow into the <c>HubConnectionBuilder.Services</c> inner collection —
    /// it is an isolated container. This overload therefore always falls back to default options
    /// when used from a client host that uses <c>appsettings.json</c>-driven configuration.
    /// Prefer <see cref="AddAcBinaryProtocol(IHubConnectionBuilder, AcBinaryHubProtocolOptions, Action{AcBinaryHubProtocolOptions})"/>
    /// in that case, passing a pre-resolved <see cref="AcBinaryHubProtocolOptions"/> from the outer service provider.
    /// </para>
    /// </summary>
    public static IHubConnectionBuilder AddAcBinaryProtocol(this IHubConnectionBuilder builder, Action<AcBinaryHubProtocolOptions>? configure = null)
    {
        builder.Services.AddSingleton<IHubProtocol>(sp => BuildProtocol(sp, configure));
        return builder;
    }

    /// <summary>
    /// Registers <see cref="AyCodeBinaryHubProtocol"/> for a client <see cref="HubConnection"/> using
    /// an <see cref="AcBinaryHubProtocolOptions"/> instance supplied by the caller — typically resolved
    /// from the outer host's <see cref="IOptions{TOptions}"/> and cloned. An optional
    /// <paramref name="configure"/> action may apply last-minute overrides (e.g. attaching a logger
    /// instance) on the cloned options.
    /// <para>
    /// Use this overload when <c>AcBinaryHubProtocolOptions</c> is bound from <c>appsettings.json</c>
    /// via <c>services.Configure&lt;AcBinaryHubProtocolOptions&gt;(...)</c> in the outer host's
    /// service collection — the <see cref="HubConnectionBuilder"/>'s isolated inner DI cannot see
    /// those registrations, so the options must be passed explicitly.
    /// </para>
    /// </summary>
    public static IHubConnectionBuilder AddAcBinaryProtocol(this IHubConnectionBuilder builder, AcBinaryHubProtocolOptions options, Action<AcBinaryHubProtocolOptions>? configure = null)
    {
        if (options is null) throw new ArgumentNullException(nameof(options));

        builder.Services.AddSingleton<IHubProtocol>(sp =>
        {
            // Clone so callers can safely reuse their options instance across multiple connections
            // without the factory's Logger fallback or configure overrides leaking back.
            var opts = options.Clone();
            opts.Logger ??= sp.GetService<ILogger<AcBinaryHubProtocol>>();
            configure?.Invoke(opts);
            opts.Validate();
            return new AyCodeBinaryHubProtocol(opts);
        });

        return builder;
    }

    /// <summary>
    /// Shared factory used by both client (this file) and server
    /// (<c>AcSignalRServerProtocolExtensions</c> in AyCode.Services.Server).
    /// Resolves options from DI (<c>IOptions&lt;T&gt;</c>), clones them, applies the inline
    /// <paramref name="configure"/> override, validates, and constructs the protocol.
    /// </summary>
    public static IHubProtocol BuildProtocol(IServiceProvider sp, Action<AcBinaryHubProtocolOptions>? configure)
    {
        var diOptions = sp.GetService<IOptions<AcBinaryHubProtocolOptions>>()?.Value;
        var options = diOptions?.Clone() ?? new AcBinaryHubProtocolOptions();

        options.Logger ??= sp.GetService<ILogger<AcBinaryHubProtocol>>();

        configure?.Invoke(options);

        options.Validate();

        return new AyCodeBinaryHubProtocol(options);
    }
}
