using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.LocalDb.Replication.Internal;

namespace ZB.MOM.WW.LocalDb.Replication;

/// <summary>DI + endpoint wiring for the 2-node gRPC replication engine.</summary>
public static class ReplicationServiceCollectionExtensions
{
    /// <summary>
    /// Binds and validates <see cref="ReplicationOptions"/> from the <c>"LocalDb:Replication"</c>
    /// configuration section, registers the passive <see cref="LocalDbSyncService"/> as a singleton
    /// (its single-stream guard must span calls), and registers the initiating
    /// <see cref="SyncBackgroundService"/> as a hosted service — on a passive node (no
    /// <see cref="ReplicationOptions.PeerAddress"/>) it starts and immediately idles. The periodic
    /// <see cref="MaintenanceBackgroundService"/> (oplog caps enforcement + tombstone pruning) is
    /// also always registered and runs on BOTH roles — passive nodes need it too. Idempotent.
    /// Requires <see cref="LocalDbServiceCollectionExtensions.AddZbLocalDb"/> to have been called
    /// for the <see cref="ILocalDb"/> the adapters depend on.
    /// </summary>
    public static IServiceCollection AddZbLocalDbReplication(
        this IServiceCollection services, IConfiguration configuration)
    {
        // Idempotent: a second call must not re-bind options or double-register the service/host.
        if (services.Any(d => d.ServiceType == typeof(LocalDbSyncService)))
            return services;

        services.AddOptions<ReplicationOptions>()
            .Bind(configuration.GetSection("LocalDb:Replication"))
            .ValidateOnStart();
        services.TryAddEnumerable(
            ServiceDescriptor.Singleton<IValidateOptions<ReplicationOptions>, ReplicationOptionsValidator>());

        // Telemetry is optional but always registered here; the sessions/adapters hold nullable
        // references and null-guard every call, so a consumer that never collects metrics is
        // unaffected. One OplogStore instance backs both the status backlog and the depth gauge
        // (it is stateless over the db, so sharing is free); its observers poll a cheap
        // synchronous SELECT.
        services.TryAddSingleton(sp =>
        {
            var db = SyncSessionFactory.RequireSqlite(sp.GetRequiredService<ILocalDb>(), nameof(OplogStore));
            var options = sp.GetRequiredService<IOptions<ReplicationOptions>>().Value;
            return new OplogStore(db, options);
        });
        services.TryAddSingleton<SyncStatus>(sp =>
            new SyncStatus { OplogBacklogProvider = sp.GetRequiredService<OplogStore>().GetOplogDepthSync });
        services.TryAddSingleton<ISyncStatus>(sp => sp.GetRequiredService<SyncStatus>());
        services.TryAddSingleton<LocalDbMetrics>(sp =>
        {
            var status = sp.GetRequiredService<SyncStatus>();
            return new LocalDbMetrics
            {
                OplogDepthProvider = sp.GetRequiredService<OplogStore>().GetOplogDepthSync,
                LastSyncUtcProvider = () => status.LastSyncUtc,
            };
        });

        services.AddSingleton<LocalDbSyncService>();
        services.AddHostedService<SyncBackgroundService>();
        // Factory registration because the ctor takes the internal shared OplogStore singleton.
        services.AddHostedService(sp => new MaintenanceBackgroundService(
            sp.GetRequiredService<OplogStore>(),
            sp.GetRequiredService<IOptions<ReplicationOptions>>(),
            sp.GetRequiredService<Microsoft.Extensions.Logging.ILoggerFactory>()));

        return services;
    }

    /// <summary>
    /// Maps the passive <see cref="LocalDbSyncService"/> gRPC endpoint. The host must have called
    /// <c>AddGrpc()</c> and set up routing.
    /// </summary>
    public static IEndpointRouteBuilder MapZbLocalDbSync(this IEndpointRouteBuilder endpoints)
    {
        endpoints.MapGrpcService<LocalDbSyncService>();
        return endpoints;
    }
}
