using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;

namespace ZB.MOM.WW.ScadaBridge.Communication;

public static class ServiceCollectionExtensions
{
    /// <summary>Registers communication services including options, <see cref="CommunicationService"/>, gRPC client factory, and debug stream.</summary>
    /// <param name="services">The DI service collection to register services into.</param>
    /// <returns>The same <see cref="IServiceCollection"/> to allow chaining.</returns>
    public static IServiceCollection AddCommunication(this IServiceCollection services)
    {
        // Canonical section is ScadaBridge:Communication — the section every real
        // appsettings actually writes (arch-review 08 round 2 NF8). The Host used to
        // supply the real values via a duplicate Configure<> binding; that duplicate is
        // gone, so AddCommunication must bind the canonical section itself.
        services.AddOptions<CommunicationOptions>()
            .BindConfiguration("ScadaBridge:Communication")
            .ValidateOnStart();
        services.TryAddEnumerable(
            ServiceDescriptor.Singleton<IValidateOptions<CommunicationOptions>, CommunicationOptionsValidator>());

        services.AddSingleton<CommunicationService>();

        // Explicit factory rather than AddSingleton<T>(): the ISitePskProvider dependency is
        // optional (central registers one, a site node does not), and constructor selection
        // over a nullable interface parameter is exactly the case the container cannot decide
        // for itself — GetService returns null cleanly where constructor injection would throw.
        services.AddSingleton(sp => new SiteStreamGrpcClientFactory(
            sp.GetRequiredService<ILoggerFactory>(),
            sp.GetRequiredService<IOptions<CommunicationOptions>>(),
            sp.GetService<ISitePskProvider>()));
        services.AddSingleton<DebugStreamService>();

        // Aggregated live alarm cache (plan #10, Task 4): transient, in-memory, shared
        // per-site aggregator backing the operator Alarm Summary page. Singleton so every
        // Blazor circuit shares one aggregator per site; started/stopped by viewer
        // reference-count. No persistence (locked [PERM]: no central alarm store).
        services.AddSingleton<SiteAlarmLiveCacheService>();
        services.AddSingleton<ISiteAlarmLiveCache>(sp => sp.GetRequiredService<SiteAlarmLiveCacheService>());

        // Startup reconciliation handler — scoped (holds scoped repositories), resolved
        // per-request by CentralCommunicationActor inside a DI scope. Harmless on site
        // hosts: only the central actor ever resolves it.
        services.AddScoped<ReconcileService>();

        return services;
    }

    /// <summary>Hook for registering additional DI services needed by communication actors; actor creation itself happens inside <c>AkkaHostedService</c>.</summary>
    /// <param name="services">The DI service collection to register services into.</param>
    /// <returns>The same <see cref="IServiceCollection"/> to allow chaining.</returns>
    public static IServiceCollection AddCommunicationActors(this IServiceCollection services)
    {
        // Actor registration happens in AkkaHostedService.RegisterCentralActors/RegisterSiteActors.
        // This method is a hook for any additional DI registrations needed by the communication actors.
        return services;
    }
}
